# QuantDinger Script Code Development Guide This guide explains how to write QuantDinger script code that can be backtested, deployed live, and maintained safely. Script code is for stateful strategies such as layered entries, basket average-cost take profit, trailing stops, pyramiding, cooldowns, and duplicate-order protection. ## 1. Core Boundary Script code owns strategy logic only. Values chosen by the user at run time should not be duplicated as code parameters. The run panel owns: - Symbol, such as BTC/USDT - Market type, spot or swap - Trade direction, long, short, or both - Investment amount - Contract leverage - Account, notifications, and live risk controls Script code owns: - Entry conditions - Add, reduce, take-profit, and stop-loss rules - Layer count, spacing, multipliers, periods, and cooldowns - Persistent state and duplicate-order protection - Logs and basket checkpoints Do not write these as script parameters: ```python ctx.param('direction', 'long') ctx.param('market_type', 'swap') ctx.param('investment_amount', 1000) ctx.param('leverage', 3) ctx.param('base_notional', 50) ``` Read run-panel values directly when needed: ```python direction = ctx.direction market_type = ctx.market_type budget = ctx.investment_amount leverage = ctx.leverage ``` ## 2. Lifecycle Every script must define: ```python def on_init(ctx): pass def on_bar(ctx, bar): pass ``` `on_init(ctx)` runs once when the script starts. Use it to read strategy parameters and initialize defaults. `on_bar(ctx, bar)` runs once per K-line bar. The platform uses a fixed 1m bar stream for script strategies; live execution also checks the latest price every 10 seconds to reduce take-profit and stop-loss latency. Backtests are K-line based, so do not write tick-level strategies that require every market tick. `bar` supports: ```python bar['open'] bar['high'] bar['low'] bar['close'] bar['volume'] bar['timestamp'] ``` ## 3. Parameter Design Only put strategy knobs into `ctx.param(...)`: ```python def on_init(ctx): ctx.fast_period = ctx.param('fast_period', 12) ctx.slow_period = ctx.param('slow_period', 36) ctx.take_profit_pct = ctx.param('take_profit_pct', 0.006) ctx.max_layers = ctx.param('max_layers', 5) ``` Percent defaults in Python code use 0-1 ratios: - `0.006` means 0.6% - `0.02` means 2% - `0.8` means 80% The frontend may display 0-100 percent values, but the generated Python default literal should remain a ratio. ## 4. Investment Amount and Order Sizing For stateful scripts, prefer `ctx.basket(side).open_child_order(..., notional=quote_amount)`. `notional` means quote-currency amount, for example USDT. Backtest and live execution convert it into base quantity using market type, price, and leverage. Example: ```python def _run_budget(ctx): try: budget = float(ctx.investment_amount or 0.0) except Exception: budget = 0.0 if budget > 0: return budget return 0.0 def _planned_base_notional(ctx): total_weight = 1 + 1.8 + 3.24 return _run_budget(ctx) / total_weight def on_bar(ctx, bar): price = float(bar['close']) side = 'short' if str(ctx.direction).lower() == 'short' else 'long' basket = ctx.basket(side) quote_amount = _planned_base_notional(ctx) basket.open_child_order( layer=1, order=1, notional=quote_amount, price=price, action='open', payload={'reason': 'first_entry'}, ) ``` Do not expose "base order amount" as a template parameter. A clearer model is: the user enters one investment amount, and the script derives child order amounts from layer count, multipliers, and total weights. ## 5. Direction Handling Direction is selected in the run panel. The script reads it, but should not hard-code it. Recommended helper: ```python def _side(ctx): try: direction = str(ctx.direction) except Exception: direction = 'long' return 'short' if direction.lower() == 'short' else 'long' ``` Spot can only run long. That constraint is enforced by the run panel and execution layer; scripts do not need a second complicated branch. ## 6. State Management Layered, basket, or cooldown-based strategies must use `ctx.state`. Common state keys: - Current layer: `layer` - Current child order: `order` - Average cost: `avg_cost` - Total quantity: `qty` - Next trigger price: `next_trigger` - Cooldown-until bar: `cooldown_until` - Last order bar: `last_order_bar` Example: ```python bar_no = int(ctx.current_index) last_order_bar = int(ctx.state.get('last_order_bar', -999999) or -999999) if last_order_bar == bar_no: return ctx.state.set('last_order_bar', bar_no) ``` This prevents duplicate orders on the same bar. ## 7. Basket API `ctx.basket(side)` is the preferred API for strategies with basket semantics. Common usage: ```python basket = ctx.basket('long') basket.open_child_order( layer=1, order=1, notional=50, price=price, action='open', payload={'reason': 'entry'}, ) basket.open_child_order( layer=2, order=1, notional=80, price=price, action='add', payload={'reason': 'add_layer'}, ) basket.close_all(reason='take_profit') ``` Use `checkpoint` to expose the current strategy state: ```python basket.checkpoint( status='opening', current_layer=layer, current_order_in_layer=order, total_qty=qty, total_notional=qty * avg_cost, avg_entry_price=avg_cost, next_entry_trigger=next_trigger, take_profit_price=take_profit, max_layer=max_layers, max_orders_per_layer=orders_per_layer, ) ``` ## 8. Backtest and Live Alignment Script backtest and live execution share the same `on_init/on_bar` semantics. Keep these points in mind: - Backtests are based on historical K-line bars and do not simulate every 10-second live price check. - Live execution additionally checks latest price for faster price-condition handling. - Use basket `notional` sizing so leverage behavior stays consistent across backtest and live. - Do not use future data. Use `ctx.bars(n)` for current and historical bars only. - Every scale-in needs a price distance, max layer/order limit, and duplicate-order guard. ## 9. Sandbox Restrictions Scripts run inside a safety sandbox. Do not use: - `getattr`, `setattr`, `delattr` - `eval`, `exec`, `open`, `compile` - `globals`, `vars`, `dir` - `__builtins__` or dunder attributes - File, network, database, process, or thread APIs - `os`, `sys`, `requests`, `urllib`, `socket`, `subprocess`, `threading`, `multiprocessing`, `sqlite3`, `psycopg`, `sqlalchemy`, `pathlib`, `tempfile`, `glob`, `io`, `operator`, `pickle`, or `ctypes` For optional fields, use direct access with `try/except`: ```python try: direction = str(ctx.direction) except Exception: direction = 'long' ``` Do not write: ```python direction = getattr(ctx, 'direction', 'long') ``` ## 10. Recommended Template Types Professional script templates should cover different market regimes instead of repeating the same scale-in idea: - EMA ATR trend risk: trend following with ATR hard stop and ATR trailing stop. - Donchian breakout pyramid: channel breakout entry and favorable pyramiding. - Bollinger mean reversion basket: layered entries around volatility-band extremes and average-cost reversion exit. - Layered basket martingale: multiple layers, martingale child sizing, average-cost take profit, and hard stop. These four cover trend, breakout, range reversion, and high-risk basket models without showing several duplicate "add-on-dip" templates. ## 11. Recommended Architecture for Complex Scripts Do not write complex scripts as one large `if/else` block. Split the logic into five layers: 1. Signal layer: decide whether entry, add, or exit is allowed. 2. Sizing layer: split the run-panel investment amount into planned child order amounts. 3. State layer: persist phase, layer, child order, average cost, trigger price, and cooldown. 4. Execution layer: emit only `basket.open_child_order` or `basket.close_all`. 5. Risk layer: hard stop, max layers, max child orders, cooldown, and duplicate-order guards. The script is still a single file, but the internal structure should look like this: ```python def on_init(ctx): # 1. Read strategy knobs pass def _side(ctx): # 2. Read run-panel direction pass def _run_budget(ctx): # 3. Read run-panel investment amount pass def _planned_notional(ctx, layer, order): # 4. Calculate planned quote amount for each child order pass def _entry_signal(ctx, bar, bars): # 5. Entry signal pass def _risk_exit(ctx, price, avg_cost): # 6. Risk exit pass def _place_child(ctx, basket, layer, order, price, action): # 7. Place order and update state pass def on_bar(ctx, bar): # 8. Main orchestration only pass ``` This makes it much easier to add conditions, change sizing, or replace exit logic without rewriting the whole strategy. ## 12. State Machine Model Complex strategies should start with a state machine design. Recommended phases: | State | Meaning | Allowed actions | | --- | --- | --- | | `idle` | Flat and waiting | Check entry | | `opening` | First order placed or basket building | Add, take profit, stop | | `active` | Position is running | Take profit, stop, trailing exit | | `closing` | Close triggered | Wait for execution layer | | `cooldown` | Post-exit cooldown | No re-entry | The script does not have to store these exact strings, but the logic should follow this model. At minimum persist: ```python ctx.state.set('layer', layer) ctx.state.set('order', order) ctx.state.set('avg_cost', avg_cost) ctx.state.set('qty', qty) ctx.state.set('next_trigger', next_trigger) ctx.state.set('cooldown_until', cooldown_until) ctx.state.set('last_order_bar', bar_no) ``` Recommended `on_bar` flow: ```python def on_bar(ctx, bar): # 1. Return if not enough data # 2. Read direction, price, and state # 3. If flat, repair/reset stale state # 4. If in cooldown, return # 5. If flat, check entry # 6. If in position, check take-profit/stop first # 7. If still in position, check add logic # 8. After any order, update checkpoint and state ``` Do not add first and check stop later. In complex scripts, this order matters. ## 13. Sizing Models Users enter one investment amount. The script should split it according to the strategy structure. ### 13.1 Equal Layers Useful for breakout pyramids: ```python base_notional = ctx.investment_amount / ctx.max_layers ``` If investment is 1000 USDT and there are 4 layers, each layer is 250 USDT. ### 13.2 Mild Geometric Layers Useful for Bollinger mean reversion: ```python weights = [ctx.layer_multiplier ** i for i in range(ctx.max_layers)] base_notional = ctx.investment_amount / sum(weights) layer_notional = base_notional * (ctx.layer_multiplier ** (layer - 1)) ``` If investment is 1000 USDT, 4 layers, multiplier 1.25, weights are 1, 1.25, 1.56, and 1.95. ### 13.3 Layered Martingale Useful for 5 layers and 3 child orders per layer: ```python one_layer_weight = sum([ctx.martingale_multiplier ** i for i in range(ctx.orders_per_layer)]) total_weight = one_layer_weight * ctx.max_layers base_notional = ctx.investment_amount / total_weight child_notional = base_notional * (ctx.martingale_multiplier ** (order - 1)) ``` If investment is 1000 USDT, 5 layers, 3 orders per layer, multiplier 1.8: - One layer weight: `1 + 1.8 + 3.24 = 6.04` - Total weight: `6.04 * 5 = 30.2` - Base child order amount: `1000 / 30.2 = 33.11 USDT` - Each layer has roughly `33.11 / 59.60 / 107.27 USDT` This removes the conflict between "base order amount in code" and "investment amount in the run panel". ## 14. Signal Layer Design Complex entries should usually be events, not persistent states. Not recommended: ```python if not has_position and fast > slow: open_position() ``` That condition remains true for many bars and relies on other guards to avoid repeated entry attempts. Recommended: ```python cross_up = prev_fast <= prev_slow and fast > slow if not has_position and cross_up: open_position() ``` Common signal models: - Trend: EMA cross, price crossing and confirming above/below a moving average. - Breakout: close breaks the highest high or lowest low of the past N bars. - Mean reversion: price touches an outer Bollinger band with RSI confirmation. - Layered martingale: first order may start immediately or use a filter; later orders must be triggered by price distance only. ## 15. Risk Layer Design Every complex strategy needs at least three types of risk control: 1. Structure risk: max layers, max child orders, one order per bar. 2. Price risk: hard stop, average-cost take profit, trailing stop, channel exit. 3. Time risk: cooldown, timeout exit, stale-state reset. Example: ```python if last_order_bar == bar_no: return if layer >= ctx.max_layers and order >= ctx.orders_per_layer: # No more adds. Wait for take-profit or hard stop. return if pnl <= -ctx.hard_stop_pct: basket.close_all(reason='hard_stop') _reset_cycle(ctx, bar_no + ctx.cooldown_bars) return ``` ## 16. Pre-Backtest Checklist Before running a complex script backtest, check: - `on_init(ctx)` and `on_bar(ctx, bar)` are defined. - No `getattr`, file, network, database, process, or other sandbox-blocked capability is used. - Direction, market type, investment amount, and leverage are not defined as `ctx.param`. - Percent parameters in Python are 0-1 ratios. - Every add has max count and price distance. - Every order has a same-bar duplicate guard. - Order sizing is derived from `ctx.investment_amount`. - Orders use `basket.open_child_order(..., notional=...)` for quote amount sizing. - State is reset after take-profit/stop and cooldown is set. - Every state variable can be explained. ## 17. Pre-Live Checklist A successful backtest does not mean the script is ready for live trading. Also check: - Spot scripts only run long. - Contract leverage fits exchange limits. - Investment amount can cover the maximum planned layers. - Max order count does not violate exchange rate limits or minimum order size. - Hard stop exists and is not so wide that it is meaningless. - Account-level risk controls, notifications, and emergency stop are configured. - The script has completed at least one full open/add/close cycle with small capital or demo trading. ## 18. AI Assistant Prompt Examples When asking AI to generate complex script code, state the boundary explicitly: ```text Write a QuantDinger script code: 1. Use on_init/on_bar. 2. The run panel owns symbol, spot/swap, direction, investment amount, and leverage. Do not create ctx.param for these. 3. Use ctx.basket(side).open_child_order(..., notional=...) for orders. 4. Split the investment amount into 5 layers, 3 child orders per layer, martingale multiplier 1.8. 5. Use child-order spacing, average-cost take profit, hard stop, and same-bar duplicate protection. 6. Do not use getattr, file/network/database APIs, or unsafe imports. ``` When modifying an existing template: ```text Based on the current template, only adjust parameters and risk controls. Do not change the run-panel boundary. Change take-profit to 0.8%, make inter-layer spacing grow with depth, and add a 12% hard stop. ``` ## 19. Minimal Skeleton ```python """ My Script Strategy """ def on_init(ctx): ctx.lookback = ctx.param('lookback', 20) ctx.take_profit_pct = ctx.param('take_profit_pct', 0.01) def _side(ctx): try: direction = str(ctx.direction) except Exception: direction = 'long' return 'short' if direction.lower() == 'short' else 'long' def on_bar(ctx, bar): bars = ctx.bars(ctx.lookback + 1) if len(bars) < ctx.lookback + 1: return side = _side(ctx) price = float(bar['close']) basket = ctx.basket(side) # Add entry, state, risk, and exit logic here. ```