We have started the Trading Strategy podcast. Listen to us on YouTube and Spotify.
Strategy icon

Lighter AI beta

Systematic multi-strategy vault trading on Lighter

Source code

The source code of the Lighter AI strategy

"""Systematic multi-strategy vault trading Lighter perpetual futures.

Lighter AI combines trend-following, momentum breakouts, mean reversion, and
macro regime signals with fully automated dynamic capital allocation.
Complementary sub-strategies target different market conditions, with capital
redistributed according to the prevailing market environment.

Multi-timeframe signals from 5-minute to daily charts provide layered
confirmation for long and short positions across liquid Lighter perpetuals.
Risk management uses diversification across uncorrelated strategies, adaptive
stop losses, and continuous liquidity and momentum screening.

This module tracks the Safe-owned Lighter account for an Ethereum Lagoon vault.
It creates one exchange-account position and automatically moves idle Safe
USDC to Lighter while reclaiming free collateral to restore the Safe operating
buffer or fund pending Lagoon redemptions. A separate automated trading process
runs the sub-strategies and submits orders; this module holds no Lighter API
key.

The strategy description is adapted from OpenCZ:
https://tradingstrategy.ai/strategies/opencz/description

See deploy/README-lighter-ai.md for deployment and accounting instructions.
"""

import datetime
import os
from decimal import Decimal

from eth_defi.lighter.constants import LIGHTER_L1_CONTRACT
from eth_defi.token import USDC_NATIVE_TOKEN
from tradingstrategy.chain import ChainId
from tradingstrategy.exchange import Exchange, ExchangeType
from tradingstrategy.timebucket import TimeBucket
from tradingstrategy.universe import Universe

from tradeexecutor.exchange_account.cash_manager import (
    ExchangeCashManager,
    ExchangeCashSnapshot,
)
from tradeexecutor.exchange_account.lighter import create_lighter_exchange_account_pair
from tradeexecutor.exchange_account.state import (
    create_exchange_account_transfer,
    open_exchange_account_position,
)
from tradeexecutor.strategy.tag import StrategyTag
from tradeexecutor.state.identifier import AssetIdentifier
from tradeexecutor.state.trade import TradeExecution
from tradeexecutor.strategy.cycle import CycleDuration
from tradeexecutor.strategy.default_routing_options import TradeRouting
from tradeexecutor.strategy.execution_context import ExecutionContext
from tradeexecutor.strategy.pandas_trader.indicator import IndicatorSet
from tradeexecutor.strategy.pandas_trader.strategy_input import StrategyInput
from tradeexecutor.strategy.pandas_trader.trading_universe_input import (
    CreateTradingUniverseInput,
)
from tradeexecutor.strategy.reserve_currency import ReserveCurrency
from tradeexecutor.strategy.strategy_module import StrategyParameters
from tradeexecutor.strategy.strategy_type import StrategyType
from tradeexecutor.strategy.trading_strategy_universe import (
    TradingStrategyUniverse,
    create_pair_universe_from_code,
)

trading_strategy_engine_version = "0.5"
trading_strategy_type = StrategyType.managed_positions

#: Use a slower cycle because custody transactions run on expensive Ethereum mainnet.
trading_strategy_cycle = CycleDuration.cycle_6h

trade_routing = TradeRouting.default
reserve_currency = ReserveCurrency.usdc

#: Lighter custody and the Lagoon vault both reside on Ethereum mainnet.
CHAIN_ID = ChainId.ethereum


class Parameters:
    """Strategy parameters."""

    chain_id = CHAIN_ID
    initial_cash = 100_000

    #: Use a slower cycle because custody transactions run on expensive Ethereum mainnet.
    cycle_duration = CycleDuration.cycle_6h

    routing = TradeRouting.default
    required_history_period = datetime.timedelta(days=1)
    backtest_start = None
    backtest_end = None
    lighter_cash_management = True

    #: Keep the 19 USDC left in the Safe after the 1 USDC Lighter activation.
    lighter_safe_cash_buffer_usd = Decimal("19")

    lighter_free_collateral_buffer_usd = Decimal("0")
    lighter_min_transfer_usd = Decimal("1")


def create_trading_universe(input: CreateTradingUniverseInput) -> TradingStrategyUniverse:
    """Create a single Ethereum Lighter exchange-account pair."""
    # Read at universe creation so metadata can be loaded before deployment.
    # Never fall back to a test index: this value determines the posted NAV.
    raw_account_index = os.environ.get("LIGHTER_ACCOUNT_INDEX", "").strip()
    if not raw_account_index.isascii() or not raw_account_index.isdecimal():
        raise ValueError(
            "Set LIGHTER_ACCOUNT_INDEX to the non-negative integer account index "
            "from the Lighter deployment report"
        )
    account_index = int(raw_account_index)
    usdc = AssetIdentifier(
        chain_id=CHAIN_ID.value,
        address=USDC_NATIVE_TOKEN[CHAIN_ID.value],
        token_symbol="USDC",
        decimals=6,
    )
    lighter_pair = create_lighter_exchange_account_pair(
        quote=usdc,
        account_index=account_index,
    )
    pair_universe = create_pair_universe_from_code(CHAIN_ID, [lighter_pair])
    exchange = Exchange(
        chain_id=CHAIN_ID,
        chain_slug="ethereum",
        exchange_id=1,
        exchange_slug="lighter",
        address=LIGHTER_L1_CONTRACT,
        # Shared external-account exchange type, as in the upstream template.
        exchange_type=ExchangeType.derive,
        pair_count=1,
    )
    universe = Universe(
        time_bucket=TimeBucket.d1,
        chains={CHAIN_ID},
        exchanges={exchange},
        pairs=pair_universe,
        candles=None,
        liquidity=None,
    )
    return TradingStrategyUniverse(
        data_universe=universe,
        reserve_assets=[usdc],
    )


def create_indicators(
    parameters: StrategyParameters,
    indicators: IndicatorSet,
    strategy_universe: TradingStrategyUniverse,
    execution_context: ExecutionContext,
) -> None:
    """No indicators are needed for deployment or account monitoring."""
    del parameters, indicators, strategy_universe, execution_context


def decide_trades(input: StrategyInput) -> list[TradeExecution]:
    """Open the Lighter account position and manage its cash allocation."""
    pair = input.strategy_universe.get_single_pair()
    reserve_asset = input.strategy_universe.get_reserve_asset()
    position = input.state.portfolio.get_open_position_for_pair(pair)

    if position is None:
        # Position initialisation is an accounting operation, not a custody transfer.
        open_exchange_account_position(
            state=input.state,
            strategy_cycle_at=input.timestamp.to_pydatetime(),
            pair=pair,
            reserve_currency=reserve_asset,
            reserve_amount=Decimal(0),
            notes="Lighter exchange account position",
        )
        return []

    if input.execution_context.mode.is_backtesting():
        return []

    position_manager = input.get_position_manager()
    snapshot = ExchangeCashSnapshot(
        safe_usdc=Decimal(str(position_manager.get_current_cash())),
        exchange_available_usdc=Decimal(str(position_manager.get_exchange_account_available_balance(pair))),
        pending_deposits_usdc=Decimal(str(input.state.sync.treasury.pending_deposits or 0)),
        pending_redemptions_usdc=Decimal(str(input.state.sync.treasury.pending_redemptions or 0)),
    )
    cash_manager = ExchangeCashManager(
        safe_cash_buffer_usdc=Decimal(str(input.parameters.lighter_safe_cash_buffer_usd)),
        free_collateral_buffer_usdc=Decimal(str(input.parameters.lighter_free_collateral_buffer_usd)),
        minimum_transfer_usdc=Decimal(str(input.parameters.lighter_min_transfer_usd)),
    )
    transfer_pending = any(
        trade.is_external_account_transfer_pending()
        for candidate in input.state.portfolio.get_open_and_frozen_positions()
        for trade in candidate.trades.values()
    )
    decision = cash_manager.decide(snapshot, transfer_pending=transfer_pending)
    if not decision.should_transfer:
        return []

    transfer = create_exchange_account_transfer(
        state=input.state,
        strategy_cycle_at=input.timestamp.to_pydatetime(),
        position=position,
        reserve_currency=reserve_asset,
        amount=decision.amount_usdc,
        deposit=decision.direction == "deposit",
        notes="Automatic Lighter cash management",
        metadata={
            "direction": decision.direction,
            "protocol": "lighter",
            "policy": "exchange_cash_manager",
        },
    )
    return [transfer]


tags = {StrategyTag.live, StrategyTag.beta, StrategyTag.exchange_account_strategy, StrategyTag.archived}
name = "Lighter AI"
# Adapted from https://tradingstrategy.ai/strategies/opencz/description
short_description = "Systematic multi-strategy vault trading on Lighter"
icon = ""
long_description = """
Lighter AI is a systematic multi-strategy vault for Lighter perpetual futures,
combining multiple complementary trading strategies with fully automated
dynamic capital allocation.

## How it works

Multiple systematic sub-strategies run in parallel — trend-following, momentum
breakouts, mean reversion, and macro regime filtering — each targeting a
different market condition. A dynamic allocation engine continuously
redistributes capital across strategies based on prevailing market environment.

Multi-timeframe signals from 5-minute to daily charts provide layered
confirmation before entering positions. The vault trades both long and short
across the most liquid perpetual futures on Lighter.

All execution is fully automated.

## Risk framework

- Dynamic capital allocation across uncorrelated strategies
- Adaptive stop losses
- Trade both crypto and real-world assets

## Backtesting results

TODO

*Metrics are based on a specific historical simulation setup and period,
and should not be interpreted as a guarantee of future outcomes.
"""