# Idea file template (strategy) An idea file is ONE Python file. Allowed imports: numpy, pandas, math, datetime, zoneinfo, statistics, collections, itertools, functools. Not allowed: files, network, open/eval/exec/getattr/type/dir, classes, names or attributes starting with `_`, strings containing `__`. Limits per run: CPU 120 s, memory ~2.5 GB, wall time 180 s. ## Data you get `df` = pandas DataFrame of CLOSED bars of the chosen timeframe, BID prices. Columns: `time` (UTC, tz-aware), `open, high, low, close, volume, ticks` (volume = the file's volume, or tick count for the bundled gold data). It includes warm-up bars before the start date (>= 10 days and >= 300 bars); the engine only trades from the start date. ## Tunable parameters (optional, shown as inputs on the site) ```python PARAMS = {"fast": 9, "slow": 21, "sl_atr": 3.0} # plain numbers / strings / True-False ``` Read them inside your functions as `PARAMS["fast"]`. The user can change them on the site without editing code. ## Library options (indicators added on the site) Every option of the site library is available as a function on the global object `opt` (no import needed): `opt.moving_averages(df)` -> dict of numpy arrays, e.g. `["ma1"]`. Keyword arguments override the option's parameters: `opt.moving_averages(df, len1=9, len2=21, kind="ema")["ma1"]`. Calling without arguments uses the values the user set on the site. ## Style A - vectorised: `generate_signals(df) -> list of dicts` Each dict is an instruction acted on at the NEXT bar (the signal bar is closed): ```python {"i": row_index, # or "time": timestamp of the signal bar "side": 1 or -1, # 1 = buy, -1 = sell "type": "market" | "limit", "price": 2650.0, # limit price (limit only) "sl": price or None, "tp": price or None, # absolute, or: "sl_dist": 3.0, "tp_dist": 1.5, # price distance from the actual fill price (> 0) "exit_after_bars": 10, # optional time exit: closed at the open of bar (fill bar + N) "expire_bars": 20, # limit only: cancelled if not filled within N bars "tag": "text"} {"i": row_index, "action": "close"} # close the open position at the next bar open {"i": row_index, "action": "cancel"} # cancel the pending order ``` Put `close` before a new entry on the same bar to reverse. Only rows >= the warm-up matter; signals on warm-up rows are ignored. ## Style B - bar by bar: `on_bar(ctx)` (+ optional `prepare(df) -> dict of arrays`) `prepare(df)` computes indicator columns once (must be causal: value at row i may only use rows <= i). `on_bar(ctx)` is called after every closed bar from the start date: - `ctx.i` current bar index; `ctx.open/high/low/close/volume` arrays up to i; `ctx.time` (unix seconds, bar open, UTC) - `ctx.ind("name")` your prepare() column up to i - `ctx.position` None or {side, entry, sl, tp, ...}; `ctx.pending` None or the pending order; `ctx.closing` True if a close is queued - `ctx.buy(**order)` / `ctx.sell(**order)` with the same keys as above (without i/side); `ctx.exit()`; `ctx.cancel()` - `ctx.state` a dict for your own memory (cooldowns etc.) ## Engine rules (honest fills) - Decisions only on closed bars; fills at the next bar or later. One position and one pending order at a time (extra orders are ignored and counted). - Market: next bar open (buy at ask, sell at bid). Limit: fills when ask <= L - point (buy) / bid >= L + point (sell), at L (or the open if price gapped through). - SL/TP are checked on the dataset's base bars (M1 for the bundled gold). If SL and TP are both inside one base bar, SL wins (or real ticks decide, when the site has them). - Spread: fixed (ask = bid + spread) or the dataset's real ask + extra. No commission, swap, slippage, margin or stop-out model. - P&L = price move x contract size x lot (gold: contract 100 -> 0.1 lot = $10 per $1 move). - Look-ahead check: prepare()/generate_signals() are re-run on truncated data; if earlier outputs change you get a warning.