Testing Order Execution Through a Sandbox Strategy
Summary
This example configures an execution test strategy for a centralized exchange sandbox. At startup, the strategy can open a position with an immediate-or-cancel order, then maintain post-only limit orders on both sides of the book. On shutdown, it is configured to cancel outstanding orders and close positions. The setup also enables quote and trade subscriptions and configures reconciliation and periodic position checks in the live execution engine.
The example is explicitly an integration test with no intended alpha advantage. Its useful lesson is operational: a sandbox workflow can exercise order submission, quote handling, reconciliation, and cleanup behavior before production use. The configuration includes a dry-run switch, but with that switch disabled it may submit orders to its configured environment; the sample points at a sandbox. It does not present performance evidence or a trading signal, and its shutdown behavior and risk settings should be understood before adapting it to another environment.
Key ideas
- The example uses a sandbox to exercise an exchange execution adapter.
- It opens a position with an immediate-or-cancel order and then posts limit quotes on both sides.
- Shutdown settings cancel open orders and close positions.
- Quote and trade subscriptions, reconciliation, and position checks support lifecycle testing.
- The sample has no alpha purpose and its dry-run setting affects whether orders are submitted.
Tags
Full text
# exec_tester.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.
# -------------------------------------------------------------------------------------------------
"""
Test Architect AX execution with the built-in ExecTester strategy.
This example connects to the AX sandbox and places live sandbox orders. On start it
opens a position with an IOC order, then maintains post-only limit quotes on both sides
of the book. On stop it cancels all orders and closes all positions. The strategy has no
alpha advantage whatsoever and is not intended for production trading.
"""
from __future__ import annotations
from decimal import Decimal
from nautilus_trader.adapters.architect_ax import AX
from nautilus_trader.adapters.architect_ax import AxDataClientConfig
from nautilus_trader.adapters.architect_ax import AxDataClientFactory
from nautilus_trader.adapters.architect_ax import AxEnvironment
from nautilus_trader.adapters.architect_ax import AxExecutionClientConfig
from nautilus_trader.adapters.architect_ax import AxExecutionClientFactory
from nautilus_trader.common import Environment
from nautilus_trader.config import LiveExecutionEngineConfig
from nautilus_trader.config import LiveRiskEngineConfig
from nautilus_trader.live import LiveNode
from nautilus_trader.model import AccountId
from nautilus_trader.model import ClientId
from nautilus_trader.model import InstrumentId
from nautilus_trader.model import Quantity
from nautilus_trader.model import StrategyId
from nautilus_trader.model import TimeInForce
from nautilus_trader.model import TraderId
from nautilus_trader.testkit import ExecTesterConfig
# WARNING: With DRY_RUN = False, this tester submits orders to the configured
# environment and may use real funds. Set DRY_RUN = True to connect without
# submitting orders or sending shutdown cancel/close commands.
DRY_RUN = False
TRADER_ID = TraderId.from_str("TESTER-001")
ACCOUNT_ID = AccountId.from_str("AX-001")
STRATEGY_ID = StrategyId.from_str("EXEC_TESTER-001")
INSTRUMENT_ID = InstrumentId.from_str(f"XAG-PERP.{AX}")
ORDER_QTY = "1"
TOB_OFFSET_TICKS = 1
def main() -> None:
"""
Run the example.
"""
node = (
LiveNode.builder("AX-EXEC-TESTER-001", TRADER_ID, Environment.LIVE)
.with_exec_engine_config(
LiveExecutionEngineConfig(
reconciliation_instrument_ids=[str(INSTRUMENT_ID)],
open_check_interval_secs=10,
position_check_interval_secs=30,
),
)
.with_reconciliation(reconciliation=True)
.with_risk_engine_config(LiveRiskEngineConfig(bypass=True))
.with_timeout_disconnection_secs(10)
.with_delay_post_stop_secs(5)
.add_data_client(
None,
AxDataClientFactory(),
AxDataClientConfig(environment=AxEnvironment.SANDBOX),
)
.add_exec_client(
None,
AxExecutionClientFactory(),
AxExecutionClientConfig(
account_id=ACCOUNT_ID,
environment=AxEnvironment.SANDBOX,
),
)
.build()
)
node.add_builtin_strategy(
"ExecTester",
ExecTesterConfig(
strategy_id=STRATEGY_ID,
instrument_id=INSTRUMENT_ID,
client_id=ClientId.from_str(AX),
external_order_instrument_ids=[INSTRUMENT_ID],
order_qty=Quantity.from_str(ORDER_QTY),
subscribe_quotes=True,
subscribe_trades=True,
open_position_on_start_qty=Decimal(ORDER_QTY),
open_position_on_first_quote=True,
open_position_time_in_force=TimeInForce.IOC,
enable_limit_buys=True,
enable_limit_sells=True,
tob_offset_ticks=TOB_OFFSET_TICKS,
use_post_only=True,
cancel_orders_on_stop=True,
close_positions_on_stop=True,
reduce_only_on_stop=False,
dry_run=DRY_RUN,
log_data=False,
),
)
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.