Appearance

Personalize your experience

Mode
Accent Color
Direction
Writing DSL by Hand

Writing DSL by Hand

Coding

Authoring strategy JSON directly: the minimum document, small constructors that keep it readable, validating before anything runs, generating variants, and moving between hand-written JSON and the builder.

When to write DSL directly

  • Generating many variants (parameter sweeps, the same rules on many symbols) — a loop is clearer than clicking
  • Reviewing and versioning strategies as files, with diffs
  • Building strategies from another system and sending them to the engine or the API

For one , the builder is faster and catches mistakes as you go. Both produce the same document: the builder's shows exactly what you would write by .

The minimum viable strategy

Only entry.signals is needed to trade; give it a symbol.timeframe and a stop. Everything else has a default ( size of 1, 100,000 capital, one position, no costs).

ema-pullback.strategy.json
{
  "symbol": { "symbol": "BTCUSDT", "exchange": "binance", "timeframe": "15m" },
  "entry": { "signals": [{
    "id": "pullback",
    "conditions": { "and": [
      { "indicator": "EMA", "params": { "period": 21 }, "operator": ">", "value": { "indicator": "EMA", "params": { "period": 50 } } },
      { "indicator": "Close", "operator": "crossesAbove", "value": { "indicator": "EMA", "params": { "period": 21 } } }
    ] }
  }] },
  "exit": {
    "signals": [{ "id": "stretched", "conditions": { "indicator": "RSI", "operator": ">=", "value": 70 } }],
    "stopLoss":   { "enabled": true, "type": "percentage", "value": 1 },
    "takeProfit": { "enabled": true, "type": "riskReward", "value": 3 }
  }
}

Making it readable

A few constructors turn nested JSON into something you can read and review.

constructors
type Value = number | string | Operand;interface Operand { indicator: string; params?: Record<string, number | string>; output?: string; offset?: number; interval?: string }type Logic = (Operand & { operator: string; value: Value }) | { and: Logic[] } | { or: Logic[] } | { not: Logic };const I = (indicator: string, params?: Operand['params'], extra: Partial<Operand> = {}): Operand =>  ({ indicator, ...(params ? { params } : {}), ...extra });const C = (left: Operand, operator: string, value: Value): Logic => ({ ...left, operator, value });const and = (...items: Logic[]): Logic => ({ and: items });const or  = (...items: Logic[]): Logic => ({ or: items });const ema = (period: number, extra?: Partial<Operand>) => I('EMA', { period }, extra);const rsi = (period = 14) => I('RSI', { period });const entry = and(  C(ema(9), 'crossesAbove', ema(21)),  C(I('Close'), '>', ema(50, { interval: '1h' })),     // higher-timeframe filter  or(C(rsi(), '<', 70), C(I('RelativeVolume'), '>', 2)),);

The 49 in apps/hft/dsl/bots are written this way (scripts/make-bots.mjs) and are a good set of worked .

Validating before you run

  1. 1
    Validate with the engine
    dslx_cli --validate -s strategy.json every error and with its JSON : unknown names (with suggestions), parameters out of range, conditions that can never be true, in the wrong order.
  2. 2
    Or paste it into the JSON editor
    The builder’s code runs the same checks in the browser, underlines the , and Apply loads it into the form and node editors.
  3. 3
    Look at the signal statistics
    After a backtest, a signal with fired 0 never had its conditions met on that ; fired much larger than acted means cooldown, position limits, the or is blocking it. The result's warnings name both cases.
!
Caution:There is no silent fallback: an unknown indicator, a misspelt parameter or an stops the from loading. Fix the reported rather than working around it.

Generating variants

a parameter sweep
import base from './ema-trend.strategy.json';

const variants = [9, 12, 20].flatMap((fast) =>
  [21, 50].map((slow) => ({
    ...base,
    name: `EMA ${fast}/${slow}`,
    entry: { ...base.entry, signals: [{ ...base.entry.signals[0],
      conditions: C(ema(fast), 'crossesAbove', ema(slow)) }] },
  })),
);
// 6 variants — run them as one batch so the EMA series are computed once:
//   dslx_cli -c candles.json -s v1.json -s v2.json …   or one "backtest" request with "strategies": variants
!
Caution:Six variants means the best result is partly luck even with no edge. A finds the parameters that best fit the noise in your test — validate the winner on the sweep never saw, and expect it to do worse.

Round-tripping with the builder

A hand-written document loads into the builder through the 's Apply. The builder in (for example every parameter of an indicator, and a single condition becomes an AND of one), so the JSON it shows afterwards is longer but means the same thing and backtests identically. From then on, form, node and JSON edits stay in sync.

See also

Related articles

  • Strategy DSL — Conditions That Can Never Be True

    Coding / Strategy DSL

    Which conditions the engine and the editors reject because no bar could ever satisfy them, how the check works, what it deliberately does not reject, and…

  • Strategy DSL — Execution Model

    Coding / Strategy DSL

    What the engine does on every bar: protective exits, exit signals, entries, layers and re-entries, sizing and costs, and where every statistic in the result…

  • Strategy DSL — Language Reference

    Coding / Strategy DSL

    The strategy JSON the C++ engine executes: every top-level block, signals and flow, operands and the 94-indicator catalog, the eleven operators, and the…

  • Strategy DSL — The Engine

    Coding / Strategy DSL

    The native C++ engine (dslx) that parses, validates, backtests and runs strategy DSL: its modules, the catalog the editors are generated from, indicator…

  • Strategy DSL — The Live Engine

    Coding / Strategy DSL

    How deployed DSL strategies run: one engine process, one connection per client, shared bar feeds and indicators per symbol and timeframe, the request…

  • Strategy DSL Format

    Automation / Strategy Builder / Reference

    The engine JSON a strategy saves as. The form, node and JSON editors all edit this one document, and the C++ engine runs it unchanged in backtests and live.

  • QuaScript C++ Engine & Quant DSL

    Coding / QuaScript

    Sub-microsecond quantitative algorithmic scripting engine powered by C++20 bytecode VM, SIMD AVX2 acceleration, cache-aligned columnar memory, and direct…

  • Script Best Practices

    Coding / IDE / Best Practices

    Follow these guidelines to write performant, reliable, and maintainable trading scripts.

  • Overfitting & Validation

    Backtesting / Validation

    Why searching for a good backtest reliably produces one even when no edge exists, how to recognise a fitted strategy, and the validation designs that…

Browse all knowledge base articles →