Write a new trading strategy as a STRATEGY.md document - frontmatter schema, required sections, validation, and promotion from paper to active. Use when the user describes a strategy idea, ports one from code or a backtest, or edits an existing strategy.
Install
npx skillscat add johnamcruz/futures-trading-harness/strategy-authoring Install via the SkillsCat registry.
Strategy Authoring
When to Use
- "Add a strategy that ..." or porting a strategy from code or a backtest.
- Changing an existing strategy's rules or status.
How It Works
Copy
strategies/_template/STRATEGY.mdtostrategies/<name>/STRATEGY.md
(or to a folder inFTH_STRATEGIES_DIRSfor private strategies).<name>
is lowercase with_or-; it becomes the journal tagsetup:<name>.Frontmatter (checked by code):
Field Meaning name,descriptionFolder name; what/when, ≥ 40 chars statuspaper(new),active(live entries allowed),disabledinstrumentsContract roots, e.g. [MNQ, MES]. Arulesstrategy trades one leg of each micro/mini family it lists (MNQ/NQ, MES/ES, MYM/YM, M2K/RTY, MGC/GC, SIL/SI): the selected one (the runner'ssymbols, orFTH_SYMBOLS), never the other, so list one leg per family (listing MNQ covers a selected NQ). With no leg selected, exactly the roots listed (listing both legs of a family, neither until one is selected). The order gate,order_plan, the scan, and the backtester (its symbols) all match this way. The contract ordered sets the tick specs and the dollars (max_risk_usdon an NQ entry is measured at NQ's $5.00 a tick).manualandpolicystrategies trade exactly the roots listedtimeframeTrigger timeframe, e.g. 3msessionsOptional windows, entries only inside: named sessions asia(18:00-03:00 ET),london(03:00-09:30 ET),ny(09:30-16:00 ET), or"HH:MM-HH:MM@Zone". Without it the strategy trades the whole market session, from 18:00 ET until 16:00 ET the next day (closed only 16:00-18:00 ET and weekends; no position is carried past 16:00 ET)signalrules(trigger written inrules, checked by code) ormanual(agents judge the body). Every shipped single-setup strategy, the ported ones included, isrules; the prop strategies (prop_*) arepolicy(below)connectorsOptional data the strategy needs beyond bars, like a skill's tools: order_flow(aggressor buy/sell volume per bar, forofi/delta, from recorded flow files; without them a bar-shape estimate). Required when the rules use that dataregimesOptional list of regimes the strategy fits: trend-up,trend-down,trend,range,transition,high-vol,normal-vol,low-vol(any match fits). Out-of-regime strategies are never scan candidatesmtftrend(the default) orreversal. A trend strategy never enters against the prevailing higher-timeframe trend (the highest of 4h, 1h, 15m with one): the scan drops the signal, the backtester skips it, and the order gate refuses the entry. Setreversalonly when the setup is a raid or exhaustion at a higher-timeframe level that fades that trend by design (crt_1h,crt_4h,cisd_ote,ofi_absorption,value_area_reentry); say why in the body. Policy strategies have nomtf: each setup keeps its strategy'sregime_gateOptional true: the MCP gateway also refuses entries when the live regime (from that strategy's timeframe bars) doesn't fitrulesWith signal: rules:long:and/orshort:lists of conditions, all of which must hold on the closed barparamsOptional overrides of the snapshot and rules series (e.g. orbMinutes: 30); periods must be whole numbersfiltersOptional adx_min,adx_max,adx_slope_min,max_vwap_distance_atr; checked by the scan only, not at order timeexitOptional: trail_activate_r+trail_giveback_r(trail the stop from +NR, giving back MR; trend setups use 2 / 0.5),target_r(fixed target),target(a distance expression from the signal close, or{ long, short }, e.g.crt_target(60): a target at a level instead of R),max_bars(time stop). Without it the target isrisk.min_rr. The backtester and the live runner apply itsignal: policyA policy strategy: the prop challenge as a strategy (see strategies/prop_portfolio).strategieslists the rules strategies whose setups it trades, in priority order, all on its timeframe;accountnamesaccounts/<name>/ACCOUNT.md(the gate enforces its floor, daily limits, and size budget);sizingsets risk per trade from the cushion (cushion_frac≤ 1,cap_usd,clock_k,r_per_session,min_size_guard,drawdown_halve_usd: halve the budget while the balance is at least that far below its peak,stop_slippage_ticks: the stop's expected slippage counted in each contract's risk, at least 1, the default and the backtester's fill slippage);contractsismicro,mini, orauto(minis once the size reaches one mini);exitneeds a trail (it exits every trade the policy strategy takes);risk.stop: strategykeeps each setup's own stop. Each setup is taken at the largest size the budget allows, or skipped. Only policy strategies have these keysriskstop(atr:<k>= k × ATR(20), a distance expression such as0.5 * atr(20)orcisd_ote_risk, a map{ long: <expr>, short: <expr> }when each side's stop sits somewhere else (beyond a swept low or high, as incrt_1h),structure,swing,manual),min_rr, optionalmax_risk_usd. The order gate checks that a stop exists and, whenmax_risk_usdis set, refuses an entry whose risk (size × stop ticks × the broker's tick value) is over it, or whose risk it can't compute (a market entry with its stop only in the rationale: givestopLossBracket.ticks);min_rris applied by the agents (risk-manager), not by code. In a policy strategy, only its ownmax_risk_usdapplies to its entries, not its components'source,versionWhere it came from; bump version on rule changes Body (read by the agents):
## When to Use,## How It Works(context
filter, trigger, entry/stop/targets, skip when),## Exampleswith
tick-correct numbers and a sample rationale.Port faithfully. When porting code, copy the exact rules and parameters and
flag suspected bugs to the user instead of silently fixing them.Validate: the harness tool
strategies_validate(a person runs the
strategies script'svalidate).Promote only on evidence: paper-trade it (reviews tagged
paper), run
setup-expectancy, and setstatus: activeonly with the user's approval.Write mechanical triggers as
rules, not code. Every item in a side
must hold (AND); an item can be a group,any: [...](one of them holds,
OR) orall: [...](every one), nested three deep, so one strategy can take
two setups or two confirmations (seevalue_area: a POC rejection or
breakout, confirmed by order flow or expansion). The scan shows each
group'sparts, so you can see which branch held. A condition is<expr> <op> <expr>with>,>=,<,<=,crosses_above,crosses_below. Expressions use series and numbers joined by+,-, andnumber *:open high low close volume,ema(n) sma(n) atr(n) adx(n) highest(n) lowest(n)(n up to 500; adx up to 250), order flowofi(n) delta(n) vol_sma(n),supertrend supertrend_dir,keltner_upper/mid/lower,vwap_session vwap_rth or_high or_low swing_high swing_low,prior_high prior_low prior_close overnight_high overnight_low,rth_open
(today's 09:30 ET open, until 16:00 ET),rth_high rth_low(today's RTH
high and low so far),ib_high ib_low(today's initial balance, the
first hour, from the bar that completes it),adr(n)(the average RTH
range of the last n complete days),minute_et,cisd_ote_dir cisd_ote_risk(the original bot's CISD + OTE detector), and
higher-timeframe candleshtf_open(m) htf_high(m) htf_low(m) htf_close(m)
(the previous m-minute candle) andhtfc_open(m) htfc_high(m) htfc_low(m)
(the one in progress, up to this bar); m divides a day (60 = 1 hour, 240 =
4 hours, candles aligned to the 18:00 ET open: 18, 22, 02, 06, 10, 14 ET).htfc_low(60) < htf_low(60)says this hour swept the last hour's low.crt_dir(m) crt_risk(m) crt_target(m)are the Candle Range Theory sweep
detector on m-minute candles (one setup per candle, tuned by thecrt*params; seecrt_1h).mtf_bias(m)is the m-minute trend as of
the last m-minute candle completed before the bar (1 up, -1 down, 0 range;
themulti-timeframe-analysisskill):mtf_bias(240) >= 0keeps a long
out of a 4-hour downtrend.
Volume profile (bar-based; thevwap-volume-profileskill):prior_poc prior_vah prior_val(the last complete RTH day, fixed for the day),session_poc session_vah session_val(this Globex session, developing),vp_poc(n) vp_vah(n) vp_val(n)(the last n bars), and the nearest node
above or below the close,prior_hvn_above prior_lvn_below(and_hvn_below,_lvn_above,session_*,hvn_above(n)...). Use nodes
as targets and distances, not crosses: they move as price passes them.
Tune with paramsvpRows(default 100),vpRowSize(points per row
instead),vpValueArea(70),vpNodePct(9),vpTroughPct(7),vpThreshold(1).[n]looks back n bars:highest(20)[1]is the 20-bar high before this
bar (without it the current bar is included, so a close can never cross
above it).minute_etis the bar's open time in New York minutes (9:45 =
585).prior_*is the last completed RTH day andovernight_*this Globex
session before 9:30 ET up to the previous bar, as each bar saw them; a
session the bars start mid-way through has none (likewisevwap_session
andvwap_rth). The runner scans three trading days of bars, enough
for these and for long indicators to match a backtest.ofi(n)is order-flow imbalance
over n bars, from -1 (all selling) to +1 (all buying): real buy and sell
volume from recorded flow files (<FTH_HOME>/flow/), or, for a bar without it,
volume signed by where the bar closed in its range.delta(n)is that signed volume
summed,vol_sma(n)the average volume per bar. A value
that doesn't exist yet (indicator warm-up, no opening range yet) makes the
condition false and the scan marks itmissing. At most 12 conditions per
side. A rules strategy with onlylongrules can't be used to sell into an
entry.validatereports typos, unknown keys and series. Only a pattern the
rules can't express (multi-bar zone logic like cisd_ote) needsmanualor
a new series inscripts/lib/trading/rules.js(with tests), used from the
rules likecisd_ote_dir.Backtest it before paper trading (the user, or the
strategy-researcher
role in an interactive authoring session; never a trading run):node <root>/scripts/backtest.js --data <bars.parquet|.xlsx|.csv> --symbol MNQ --strategy <name>
(docs/BACKTESTING.md). Check that it fires where you expect (trades.csv)
and that its R statistics hold up with harness rules on.
Examples
---
name: donchian_break
description: 20-bar Donchian breakout on MNQ/MES 3-minute bars with an ADX trend filter, written entirely as rules.
version: 1
status: paper
instruments: [MNQ, MES]
timeframe: 3m
sessions: ["09:45-15:00@America/New_York"]
signal: rules
rules:
long:
- close crosses_above highest(20)[1]
- adx(14) >= 20
- close > vwap_rth
short:
- close crosses_below lowest(20)[1]
- adx(14) >= 20
- close < vwap_rth
risk:
stop: atr:1
min_rr: 2
source: user idea, 2026-10-08
---See strategies/vwap_reclaim/STRATEGY.md for a complete rules strategy.