Skip to content
All library documents

Composite Market Making from Equity Quotes to a Crypto Perpetual

Code NautilusTrader

Summary

This example configures a composite market-making strategy that uses NVDA equity quotes as its signal and quotes an NVDA perpetual contract on Lighter. The settings specify a maximum position and trade size, a half-spread, inventory and signal skew factors, a requote threshold, and whether canceled orders are resubmitted. The design illustrates how a market maker can use one instrument's prices to guide quoting in a related instrument, while adjusting quotes for inventory and signal information.

The script connects to Databento for the equity feed and Lighter for target-market data and execution. It is configured for the Lighter test environment, but the documentation warns that a mainnet configuration can place real orders; on shutdown, the strategy cancels target orders and submits reduce-only market orders to close target positions. This is an implementation example, not a performance study: it supplies no backtest, profitability evidence, or rationale for the chosen parameter values. Its behavior also depends on valid incoming quotes, account configuration, venue conditions, and the strategy's operational safeguards.

Key ideas

  • The strategy uses NVDA equity quotes to inform market making in an NVDA perpetual contract.
  • Inventory and signal skew factors influence quotes alongside a configured half-spread.
  • The configuration sets limits for position size, trade size, and quote replacement sensitivity.
  • The example is live-capable and can place real orders when configured for mainnet.
  • No performance results or evidence for the parameter choices are provided.

Tags

Full text
# nvda_composite_mm.py


```py
#!/usr/bin/env python3
# -------------------------------------------------------------------------------------------------
#  Copyright (C) 2015-2026 Nautech Systems Pty Ltd. All rights reserved.
#  https://nautechsystems.io
#
#  Licensed under the GNU Lesser General Public License Version 3.0 (the "License");
#  You may not use this file except in compliance with the License.
#  You may obtain a copy of the License at https://www.gnu.org/licenses/lgpl-3.0.en.html
#
#  Unless required by applicable law or agreed to in writing, software
#  distributed under the License is distributed on an "AS IS" BASIS,
#  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
#  See the License for the specific language governing permissions and
#  limitations under the License.
# -------------------------------------------------------------------------------------------------
"""
Run a Lighter NVDA RWA composite market maker with the built-in CompositeMarketMaker strategy: Databento ``NVDA.EQUS`` quotes drive the signal and ``NVDA-PERP.LIGHTER`` is the quoted target. This is the Python counterpart of the Rust tutorial binary ``examples/tutorials/src/bin/lighter_nvda_composite_mm.rs``.

WARNING: Running this script connects to the configured Lighter environment and
places REAL post-only orders after a valid Databento signal quote arrives. With the
default testnet environment no real funds are at risk; with `LighterEnvironment.MAINNET`
the orders use real funds.
On stop, it cancels its target orders and submits reduce-only market orders to
close its target positions.
Run only against an account you intend to test. The strategy is a demonstration and
is not intended for production trading.

Settings are the module-level constants below. Required environment variables:
- DATABENTO_API_KEY.
- Lighter credentials from the namespace selected by the deployment and environment:
  `LIGHTER_*`, `LIGHTER_TESTNET_*`, `LIGHTER_ROBINHOOD_*`, or
  `LIGHTER_ROBINHOOD_TESTNET_*`.

"""

from __future__ import annotations

import os
from pathlib import Path

from nautilus_trader.adapters.databento import DatabentoDataClientConfig
from nautilus_trader.adapters.databento import DatabentoDataClientFactory
from nautilus_trader.adapters.lighter import LIGHTER
from nautilus_trader.adapters.lighter import LIGHTER_ROBINHOOD
from nautilus_trader.adapters.lighter import LighterDataClientConfig
from nautilus_trader.adapters.lighter import LighterDataClientFactory
from nautilus_trader.adapters.lighter import LighterDeployment
from nautilus_trader.adapters.lighter import LighterEnvironment
from nautilus_trader.adapters.lighter import LighterExecutionClientConfig
from nautilus_trader.adapters.lighter import LighterExecutionClientFactory
from nautilus_trader.common import Environment
from nautilus_trader.config import LiveExecutionEngineConfig
from nautilus_trader.live import LiveNode
from nautilus_trader.model import AccountId
from nautilus_trader.model import InstrumentId
from nautilus_trader.model import Quantity
from nautilus_trader.model import StrategyId
from nautilus_trader.model import TraderId
from nautilus_trader.trading import CompositeMarketMakerConfig


LIGHTER_ENVIRONMENT = LighterEnvironment.TESTNET
LIGHTER_DEPLOYMENT = LighterDeployment.LIGHTER
VENUE = LIGHTER if LIGHTER_DEPLOYMENT == LighterDeployment.LIGHTER else LIGHTER_ROBINHOOD
TRADER_ID = TraderId.from_str("TESTER-001")
ACCOUNT_ID = AccountId.from_str(f"{VENUE}-001")
STRATEGY_ID = StrategyId.from_str("NVDA_COMPOSITE_MM-001")
INSTRUMENT_ID = InstrumentId.from_str(f"NVDA-PERP.{VENUE}")
SIGNAL_INSTRUMENT_ID = InstrumentId.from_str("NVDA.EQUS")

MAX_POSITION = "0.20"
TRADE_SIZE = "0.05"
HALF_SPREAD_BPS = 25
INVENTORY_SKEW_FACTOR = 2.0
SIGNAL_SKEW_FACTOR = 55.0
REQUOTE_THRESHOLD_BPS = 5
ON_CANCEL_RESUBMIT = False

DATABENTO_API_KEY = os.environ.get("DATABENTO_API_KEY", "")
PUBLISHERS_FILEPATH = (
    Path(__file__).resolve().parents[3] / "crates/adapters/databento/publishers.json"
)


def main() -> None:
    """
    Run the example.
    """
    if not DATABENTO_API_KEY:
        raise SystemExit("DATABENTO_API_KEY must be set")

    node = (
        LiveNode.builder("LIGHTER-NVDA-COMPOSITE-MM-001", TRADER_ID, Environment.LIVE)
        .with_exec_engine_config(
            LiveExecutionEngineConfig(
                reconciliation_lookback_mins=60,
                reconciliation_instrument_ids=[str(INSTRUMENT_ID)],
            ),
        )
        .with_reconciliation(reconciliation=True)
        .with_delay_post_stop_secs(5)
        .add_data_client(
            None,
            DatabentoDataClientFactory(),
            DatabentoDataClientConfig(
                api_key=DATABENTO_API_KEY,
                publishers_filepath=PUBLISHERS_FILEPATH,
                use_exchange_as_venue=True,
            ),
        )
        .add_data_client(
            VENUE,
            LighterDataClientFactory(),
            LighterDataClientConfig(
                environment=LIGHTER_ENVIRONMENT,
                deployment=LIGHTER_DEPLOYMENT,
            ),
        )
        .add_exec_client(
            VENUE,
            LighterExecutionClientFactory(),
            LighterExecutionClientConfig(
                account_id=ACCOUNT_ID,
                environment=LIGHTER_ENVIRONMENT,
                deployment=LIGHTER_DEPLOYMENT,
            ),
        )
        .build()
    )
    node.add_builtin_strategy(
        "CompositeMarketMaker",
        CompositeMarketMakerConfig(
            instrument_id=INSTRUMENT_ID,
            signal_instrument_id=SIGNAL_INSTRUMENT_ID,
            max_position=Quantity.from_str(MAX_POSITION),
            strategy_id=STRATEGY_ID,
            order_id_tag="001",
            trade_size=Quantity.from_str(TRADE_SIZE),
            half_spread_bps=HALF_SPREAD_BPS,
            inventory_skew_factor=INVENTORY_SKEW_FACTOR,
            signal_skew_factor=SIGNAL_SKEW_FACTOR,
            requote_threshold_bps=REQUOTE_THRESHOLD_BPS,
            on_cancel_resubmit=ON_CANCEL_RESUBMIT,
        ),
    )

    node.run()


if __name__ == "__main__":
    main()

```

Shown in full with attribution under the source's licence. Licence: LGPL-3.0

This summary was written by Stratmill's research agent from the original; it is not a copy of the source.