Skip to main content

Feature Request: Multi-Timeframe (MTF) Trading Engine

ID: FR-007
Requested by: Joco
Date: May 2026
Status: Open
Scope: Quantago bot architecture


Overview​

Joco raised the question of whether the bot could look for entries across multiple timeframes rather than a single one. The observation: higher timeframes produce fewer but higher-quality signals, while lower timeframes (1m, 5m) produce many trades with significant noise.

Joco's clarification on preferred architecture (verbatim, translated):

"B. But maybe it could be done like this: 4H looks for the main trend and the entry is on the 15-minute. 1H main trend — entry on the 5-minute..."

This led to three distinct implementations, each progressively more complex. They are named Version A, Version B, and Version C and should all be built — in sequence.


⚠️ Critical Prerequisite: Decoupled Evaluation Loops​

This must be resolved before any MTF version is built. It also independently breaks the current scalping strategy.

The problem​

The current Quantago runner has a single evaluation loop tied to the candle timeframe. One timeframe setting controls everything: how often the bot checks for entries, how often it checks for exits, and how often it re-evaluates indicators. This means:

  • A 5m bot waits up to 5 minutes to enter a trade
  • The same 5m bot also waits up to 5 minutes to exit a trade
  • The Monitor refreshes every second — but this is only a display update, not a decision trigger
  • For a scalper, this is a fundamental flaw: by the time the exit logic runs, the move has already reversed

This was confirmed in the Quantago AI conversation log:

"The bot gets one strategy decision per evaluation, and that decision is built from the same candle history every time." "Separate entry and exit frequencies: not in the current code. Can it already work that way? Not with this strategy as written."

Why this is also a prerequisite for MTF​

Version B requires a 4H bias loop and a 5m entry loop running at different cadences simultaneously. Version C requires multiple independent TF loops. Neither is possible in an architecture where a single loop drives everything. Building the MTF orchestration layer is the same work as fixing the entry/exit decoupling. They share the same root cause and the same fix.

The fix: three independent loops in the runner​

The runner must be refactored to support three separate evaluation cycles:

┌─────────────────────────────────────────────────────────────┐
│ BIAS LOOP (MTF only) │
│ Cadence: bias timeframe candle close (e.g. H4, H1) │
│ Job: recalculate higher-TF trend direction │
│ Updates: bias_direction flag used by signal loop │
│ Cost: low — runs infrequently │
└─────────────────────────────────────────────────────────────┘

┌─────────────────────────────────────────────────────────────┐
│ SIGNAL LOOP (entry decisions) │
│ Cadence: entry timeframe candle close (e.g. 5m, 15m) │
│ Job: run indicators, evaluate BUY/SELL/HOLD │
│ Checks: bias_direction before firing entry │
│ Cost: moderate — indicator recalculation per candle │
└─────────────────────────────────────────────────────────────┘

┌─────────────────────────────────────────────────────────────┐
│ EXIT LOOP (position management) │
│ Cadence: every second or every tick │
│ Job: check open position against SL, TP, trailing stop, │
│ time-based exit, or price deviation threshold │
│ No indicator recalculation — pure price comparison │
│ Cost: very low — lightweight price check only │
└─────────────────────────────────────────────────────────────┘

The exit loop is intentionally cheap. It does not recalculate RSI, Bollinger Bands, or ADX. It only asks: is price at SL? Is price at TP? Has the position been open longer than max_hold_seconds? Has price moved against the position by more than emergency_exit_pips? This runs safely every second with negligible compute cost.

First step to fix — Phase 0​

Before any MTF work begins, the runner must be refactored to separate the exit loop from the signal loop. This is Phase 0 in the roadmap — it unblocks both proper scalping and all MTF versions.

Minimum viable exit loop (Phase 0 scope):

{
"exit_loop": {
"enabled": true,
"cadence": "every_second",
"rules": [
{ "type": "sl", "pips": 10 },
{ "type": "tp", "pips": 20 },
{ "type": "max_hold", "seconds": 300 },
{ "type": "emergency_exit", "adverse_pips": 15 }
]
}
}

Configurable via: schema (exit_loop block per bot), AI chat ("How quickly should I cut a losing trade?"), UI (exit rules panel in bot config).

Sensible defaults: SL and TP carry over from existing bot parameters. max_hold_seconds defaults to 300 (5 minutes) for scalpers, configurable. Emergency exit defaults to off.



Strategy Code File Architecture​

Decision: 3 separate files + shared utils​

The strategy code must be split into three dedicated files — one per loop — plus a shared utilities file. This is not just a code style preference: it is an architectural boundary that prevents the most common form of degradation in trading bot codebases, which is indicator logic creeping into exit logic over time and making the fast exit loop slow and unreliable.

File structure:

/strategy
├── bias.js ← evaluate_bias() only
├── entry.js ← evaluate_entry() only
├── exit.js ← evaluate_exit() only
└── utils.js ← shared constants, pip math, type definitions

bias.js — Bias Loop file​

Called by: Bias loop, on higher-TF candle close (H4, H1, Daily)
Returns: "bullish" | "bearish" | "neutral"
Allowed imports: candle utilities from utils.js, no indicator libraries beyond what trend detection requires
Forbidden imports: position state, order management, RSI, Bollinger Bands, ADX

// bias.js
import { getCandles, slopeOf } from './utils.js'

export function evaluate_bias(candles, params) {
const ema = slopeOf(candles, params.bias_period)
if (ema > params.bull_threshold) return 'bullish'
if (ema < params.bear_threshold) return 'bearish'
return 'neutral'
}

Not used in Version A. Stubbed out as a passthrough returning "neutral" until Version B is built.


entry.js — Signal Loop file​

Called by: Signal loop, on entry-TF candle close (5m, 15m)
Receives: candle history, current bias from bias.js output (passed in as param)
Returns: "BUY" | "SELL" | "HOLD" with a checks[] array for the Monitor
Allowed imports: indicator libraries, candle utilities from utils.js
Forbidden imports: position state, order management, price tick data

// entry.js
import { calcRSI, calcBollinger, calcADX } from './utils.js'

export function evaluate_entry(candles, bias, params) {
// bias gate — Version A/B only, passthrough for Version C
if (params.require_bias_alignment && bias === 'neutral') {
return { decision: 'HOLD', checks: [{ label: 'Bias', value: 'neutral — blocked' }] }
}
// ... indicator logic, entry gates, checks[] construction
return { decision: 'BUY' | 'SELL' | 'HOLD', checks: [] }
}

This is the current strategy code, refactored to accept bias as an input parameter. The existing logic largely stays intact.


exit.js — Exit Loop file​

Called by: Exit loop, every second
Receives: open position object, current price — nothing else
Returns: "EXIT" | "HOLD" with reason string
Allowed imports: utils.js pip math only
Forbidden imports: indicator libraries, candle history, bias state — hard rule, enforced by linter

// exit.js
import { pipDistance } from './utils.js'

export function evaluate_exit(position, currentPrice, params) {
const pips = pipDistance(position.entry_price, currentPrice, position.direction)

if (pips <= -params.sl_pips) return { decision: 'EXIT', reason: 'SL hit' }
if (pips >= params.tp_pips) return { decision: 'EXIT', reason: 'TP hit' }

const heldSeconds = (Date.now() - position.opened_at) / 1000
if (heldSeconds > params.max_hold_seconds) return { decision: 'EXIT', reason: 'Max hold time' }

if (params.emergency_exit_pips && pips <= -params.emergency_exit_pips) {
return { decision: 'EXIT', reason: 'Emergency exit' }
}

return { decision: 'HOLD', reason: null }
}

This file must never grow beyond price-comparison logic. No candles. No indicators. No exceptions.


utils.js — Shared utilities​

Used by: all three files
Contains: pip calculation, candle slicing helpers, EMA/slope math, shared type definitions, instrument config constants
Does not contain: strategy logic of any kind


How the runner wires the three files​

On H4 candle close:
bias_state = evaluate_bias(h4_candles, bias_params) ← bias.js
store bias_state in runner memory

On 5m candle close:
decision = evaluate_entry(m5_candles, bias_state, entry_params) ← entry.js
if decision === 'BUY' or 'SELL': open position

Every second:
if position is open:
exit = evaluate_exit(position, current_price, exit_params) ← exit.js
if exit.decision === 'EXIT': close position

The runner stores bias_state between loops — bias.js runs infrequently and its output is cached until the next H4 candle close. Entry.js reads the cached value. Exit.js never needs it.


Linter enforcement rule​

Add the following to the project linter config to enforce file boundaries permanently:

{
"rules": {
"no-restricted-imports": [
{
"files": ["**/strategy/exit.js"],
"patterns": ["*indicator*", "*candle*", "*rsi*", "*bollinger*", "*adx*"],
"message": "exit.js must not import indicator or candle libraries — exit loop must stay lightweight"
}
]
}
}

Strategy Page: File System Manager​

The Strategy Page UI must be updated to reflect the 3-file architecture. The current single code editor must be replaced with a file-aware editor that shows all strategy files, makes the role of each file clear, and prevents the user from accidentally editing the wrong file.

File System Panel (left sidebar)​

A file tree panel sits to the left of the code editor on the Strategy Page:

📁 My EURUSD Scalper
├── 📄 bias.js Trend direction (H4)
├── 📄 entry.js Entry signals (5m) ← active
├── 📄 exit.js Exit rules (tick speed)
└── 📄 utils.js Shared utilities

Each file shows:

  • File name
  • One-line role description
  • Which loop calls it (Bias / Signal / Exit)
  • Last modified timestamp
  • A status badge: ✓ Valid | ⚠ Warning | ✗ Error based on linter output

Clicking a file opens it in the editor. The active file is highlighted. The editor header shows the filename and loop type prominently so the user always knows what they are editing.

Per-file validation panel​

Below the editor, a validation panel shows lint results scoped to the active file only. If exit.js has a forbidden import, the error shows here — not mixed in with errors from entry.js.

File actions​

Each file in the tree has a context menu:

  • Edit — open in editor
  • View history — git-style diff of recent changes (if version history is implemented)
  • Reset to default — restore the file to the last known-good saved version
  • View loop stats — shows execution time and call frequency for that loop in the last session

New bot creation​

When a new bot is created via the Strategy Page or AI chat, all four files are scaffolded automatically with commented templates. The user does not start from a blank editor — each file includes inline comments explaining what belongs there and what is forbidden.


AI Chat: Write Strategy Tool Call Updates​

The AI's write_strategy tool call must be updated in three ways to reflect the 3-file architecture.

1. File-scoped awareness​

The AI must know which file it is modifying before writing any code. When the user asks the AI to update the strategy, the AI determines which file(s) are affected based on the nature of the request:

User request typeFile(s) affected
"Change my SL to 12 pips"exit.js only
"Add RSI filter to my entries"entry.js only
"Use H1 trend direction instead of H4"bias.js only
"Add a pip calculation helper"utils.js only
"Rebuild the whole strategy"All files
"Make the bot safer"exit.js + possibly entry.js

If the AI is unsure which file is affected, it asks before writing — it does not default to overwriting all files.

2. Pre-write declaration​

Before writing any code, the AI must declare to the user which files it is about to modify and why. This is shown as a confirmation step in the chat before the tool call fires:

Updating strategy files:

  • exit.js — changing SL from 10 pips to 12 pips and adding max hold time of 4 minutes
  • entry.js — no changes
  • bias.js — no changes

Shall I proceed?

The user can confirm or cancel. If they confirm, the tool call fires and the UI updates the file tree to show the modified file with a "just updated" badge.

3. Post-write summary​

After a successful write, the AI shows a diff-style summary in chat — not the full code, just what changed:

Changes made to exit.js:

  • sl_pips: 10 → 12
  • Added max_hold_seconds: 240

entry.js, bias.js, and utils.js were not modified.

This gives the user full visibility into exactly what the AI touched without having to open the editor and compare manually.

4. Forbidden write prevention​

The AI tool call must enforce the same boundaries as the linter. If the AI's generated code for exit.js contains an indicator import, the tool call must reject it with an explanation:

"I can't add RSI to exit.js — the exit file must stay lightweight and cannot import indicator libraries. I've added the RSI filter to entry.js instead, which runs on the 5m signal loop. Would you like me to show you what changed?"


VersionNameDescriptionSchema changePriority
PrereqDecoupled Loops + 3-File ArchitectureSeparate exit/signal/bias loops. Split strategy into 3 files. Update Strategy Page + AI tool call.Yes — exit_loop blockPhase 0 — do first
AMTF Confluence FilterSingle pair: higher TF bias + lower TF entry. One trade at a time.NoPhase 1
BMTF Pair StackMultiple bias+entry pairs running in parallel. Joco's described architecture.Yes — timeframe_pairsPhase 2
CRaw Parallel TF ExecutionFully independent single-TF runners. No bias filter. For grid/scalping.Yes — timeframe_instancesPhase 3

Version A — MTF Confluence Filter​

What it is​

A single higher-timeframe bias check added to the existing bot as a pre-condition before any entry fires. Only one trade is open at any time. This is the simplest and most immediate improvement to the current strategy.

How it works​

  • H4 or Daily defines the trend direction and bias
  • 15m or 5m generates the entry signal as usual
  • Entry only fires when both timeframes agree on direction
  • If they disagree, the entry is blocked (or position size is reduced — configurable)
  • Dramatically reduces false signals vs single-TF approach

Schema changes required?​

No. Version A is an extension of the existing bot config. A new mtf_filter parameter block is added:

{
"mtf_filter": {
"enabled": true,
"bias_timeframe": "H4",
"bias_indicator": "ema_slope",
"bias_period": 50,
"require_alignment": true,
"misalignment_action": "block"
}
}

misalignment_action can be "block" (no trade fires) or "reduce_size" (trade fires at half position size).

Inside or outside the strategy code?​

Inside. The MTF bias check is a pre-condition in should_enter(), gated by mtf_filter.enabled. No orchestration layer needed.

AI chat configuration flow​

"Do you want to add a higher timeframe filter to reduce false signals? Which timeframe should define the trend — H4 or Daily?"

Parameters collected by AI: bias timeframe, bias indicator, bias period, misalignment action (block vs reduce size).


Version B — MTF Pair Stack (Joco's preferred architecture)​

What it is​

Two or more MTF confluence pairs (each identical in structure to Version A) run simultaneously and independently. Each pair has its own bias_timeframe and entry_timeframe. This is the architecture Joco described explicitly.

Reference configuration — Joco's 4H/15m + 1H/5m setup:

  • Pair 1 (Swing): 4H bias → 15m entry — catches medium-term trend moves
  • Pair 2 (Intraday): 1H bias → 5m entry — catches shorter intraday moves

Why this is better than Version C for trend trading​

  • Each pair has a built-in quality filter (the higher TF bias), suppressing low-TF noise before execution
  • Signal frequencies differ naturally — the 4H trend changes slower than the 1H, so both pairs firing simultaneously is less common than in raw parallel execution
  • Architecturally cleaner than fully independent single-TF runners
  • Each pair is just a parameterised Version A instance — the strategy code barely changes

How it works​

  • Each pair runs its own bias check and entry logic concurrently
  • Each pair has its own SL/TP, position sizing, and lifecycle
  • The concurrent_signal_mode (see below) governs what happens when both pairs signal in the same direction simultaneously
  • Both pairs run on the same instrument (e.g. EURUSD)

Schema changes required?​

Yes. A new timeframe_pairs array replaces the single mtf_filter block. Each entry in the array defines a complete bias+entry pair.

Full reference schema — Joco's setup, prop_safe mode:

{
"bot_id": "quantago-eurusd-mtf-pairs",
"concurrent_signal_mode": "prop_safe",
"signal_confluence_scaling": {
"2_pairs_aligned": 1.5,
"max_scaled_size_pct": 2.0
},
"risk": {
"max_aggregate_exposure_pct": 3.0,
"circuit_breaker_drawdown_pct": 4.0
},
"timeframe_pairs": [
{
"pair_id": "swing",
"bias_timeframe": "H4",
"bias_indicator": "ema_slope",
"bias_period": 50,
"entry_timeframe": "M15",
"entry_indicator": "bollinger_bands",
"adx_filter": true,
"position_size_pct": 1.0,
"sl_pips": 15,
"tp_pips": 30
},
{
"pair_id": "intraday",
"bias_timeframe": "H1",
"bias_indicator": "ema_slope",
"bias_period": 50,
"entry_timeframe": "M5",
"entry_indicator": "bollinger_bands",
"adx_filter": true,
"position_size_pct": 0.75,
"sl_pips": 8,
"tp_pips": 16
}
]
}

Key design notes:

  • pair_id labels (swing, intraday) are used in logs, dashboard P&L breakdown, and alerts
  • position_size_pct is set lower for the intraday pair — M5 entries carry more noise than M15
  • concurrent_signal_mode still applies — see the Concurrent Signal Mode section below

Inside or outside the strategy code?​

Split. The per-pair bias check and entry logic lives inside the strategy code — it is just Version A parameterised with bias_timeframe and entry_timeframe. The orchestration of multiple pairs and the risk arbitration between them lives outside in the bot orchestrator. Strategy code stays pure; risk policy is enforced at the infrastructure level.

AI chat configuration flow​

Prop account:

"You're setting up a multi-pair MTF bot on a prop firm account. I'll run two confluence pairs simultaneously — Pair 1: 4H trend → 15m entry (swing), Pair 2: 1H trend → 5m entry (intraday). When both pairs agree on direction at the same time, I'll open one scaled position rather than two separate trades — this keeps you compliant with prop firm correlation rules. Let's configure Pair 1. What indicator should I use to read the 4H trend — EMA slope, MACD direction, or price above/below MA?"

Personal live account:

"You're setting up a multi-pair MTF bot on a personal live account. How do you want me to handle it when both pairs signal in the same direction at the same time?

  • Scale up one trade (prop_safe) — merge into one position with larger size
  • Stack independent trades (stack) — open a separate position per pair, pyramid into the move
  • Grid / bidirectional (grid) — full independence, simultaneous long and short allowed"

Version C — Raw Parallel TF Execution​

What it is​

Fully independent single-TF runners with no paired bias filter. Each timeframe instance scans and trades entirely on its own. This is the most complex variant and carries the highest risk on prop accounts. Suited for grid strategies, scalpers, and mean-reversion bots that deliberately operate bidirectionally or want unfiltered signal generation per TF.

How it works​

  • Independent signal logic per single timeframe — no higher-TF bias filter
  • Concurrent open positions per TF (e.g. one on 1H, one on 15m, one on 5m)
  • Each instance has its own entry indicator, SL/TP, and position lifecycle
  • Higher noise per instance than Version B since no bias filter is applied
  • concurrent_signal_mode: grid enables simultaneous long and short on the same instrument

When to use Version C over Version B​

  • Grid strategies that need to place both long and short orders simultaneously at different price levels
  • Scalping bots where a higher-TF bias filter would block too many valid micro-entries
  • Mean-reversion systems that trade in both directions within a range, regardless of trend

Schema changes required?​

Yes. Uses a timeframe_instances array — flat single-TF configs with no bias pairing:

{
"bot_id": "quantago-eurusd-parallel",
"concurrent_signal_mode": "grid",
"risk": {
"max_open_positions_total": 10,
"max_loss_per_instance_pct": 0.5,
"circuit_breaker_drawdown_pct": 5.0
},
"timeframe_instances": [
{
"timeframe": "H1",
"entry_indicator": "bollinger_bands",
"adx_filter": false,
"position_size_pct": 1.0,
"sl_pips": 20,
"tp_pips": 40
},
{
"timeframe": "M15",
"entry_indicator": "bollinger_bands",
"adx_filter": false,
"position_size_pct": 0.75,
"sl_pips": 10,
"tp_pips": 20
},
{
"timeframe": "M5",
"entry_indicator": "rsi_reversal",
"adx_filter": false,
"position_size_pct": 0.5,
"sl_pips": 6,
"tp_pips": 12
}
]
}

Inside or outside the strategy code?​

Outside. The orchestration layer spawns one runner per TF instance. The strategy code per instance is identical to the current single-TF strategy. Risk arbitration (concurrent_signal_mode enforcement) lives in the orchestrator.

AI chat configuration flow​

Only surfaced when the user explicitly requests grid or scalping mode with no trend filter:

"You're setting up raw parallel timeframe execution — each timeframe runs independently with no trend filter. This is designed for grid and scalping strategies. Note: this mode is not recommended on prop firm accounts and will trigger a warning if applied to one. Which timeframes do you want to run?"


Concurrent Signal Mode​

Applies to both Version B and Version C. Governs what happens when multiple pairs or instances signal in the same direction simultaneously on the same instrument.

This is set at the account profile level in Quantago (not per bot), since the platform already knows the account type. Users can override per bot, but a warning fires if a non-prop-safe mode is applied to a prop account.

Mode 1: prop_safe (default for all accounts)​

For: Prop firm challenge and funded prop accounts. Recommended as default for all accounts.

Behaviour: Simultaneous same-direction signals are merged into a single position. Size is scaled up based on the number of aligned pairs/instances — 2 aligned = 1.5x, 3 aligned = 2x — subject to the aggregate exposure cap. Bidirectional (simultaneous long + short) positions on the same instrument are blocked entirely.

Why it is the default: Correlation stacking is one of the most cited causes of prop firm challenge failure. Prop firm risk systems monitor for it and may flag accounts even if drawdown limits are not breached. The correct professional approach is to treat multi-pair alignment as a confidence multiplier — one larger position, not multiple positions.

{
"concurrent_signal_mode": "prop_safe",
"signal_confluence_scaling": {
"2_pairs_aligned": 1.5,
"3_pairs_aligned": 2.0,
"max_scaled_size_pct": 2.0
}
}

Mode 2: stack (non-prop live accounts)​

For: Personal live accounts running directional trend or momentum strategies.

Behaviour: Each pair/instance opens its own independent position. Same-direction signals result in multiple concurrent positions on the same instrument — a deliberate pyramid. The aggregate exposure cap applies as a hard ceiling.

When to use: When the trader wants to build into a known trend across timeframes, treating each signal as an independent entry.

{
"concurrent_signal_mode": "stack",
"risk": {
"max_concurrent_positions": 3,
"max_aggregate_exposure_pct": 3.0
}
}

Mode 3: grid (non-prop live accounts, advanced)​

For: Personal live accounts running grid, scalping, or bidirectional mean-reversion strategies.

Behaviour: Full independence between all pairs/instances. Simultaneous long and short positions on the same instrument are allowed. Each position has its own entry, SL, TP, and size. The risk manager tracks net exposure for reporting but does not block opposite-direction positions. A global max open positions cap and per-instance max loss cap apply as circuit breakers.

{
"concurrent_signal_mode": "grid",
"risk": {
"max_open_positions_total": 10,
"max_loss_per_instance_pct": 0.5,
"circuit_breaker_drawdown_pct": 5.0
}
}

Risk Considerations by Version and Mode​

RiskVersion AVersion B (prop_safe)Version B (stack)Version C (grid)
False signal rateLow — bias filter appliedLow — each pair has bias filterLow — each pair has bias filterHigh — no bias filter per instance
Correlation stacking breachN/A — single positionPrevented by designUser's responsibilityUser's responsibility
Bidirectional positionsBlockedBlockedBlockedAllowed
Prop firm flag riskNoneMinimalHigh — do not use on propVery high — do not use on prop
Runaway exposureN/AAggregate cap + circuit breakerAggregate capMax positions + per-instance loss cap
SL/TP complexitySingle positionSingle merged position (prop_safe) or per-pairPer-pair independentPer-instance independent
Dashboard complexitySimpleSimple to moderateModerateComplex — requires net exposure view

Implementation Roadmap​

PhaseVersionActionPriority
0aPrereqRefactor runner — separate exit loop from signal loopDo first
0bPrereqSplit strategy into 3 files: bias.js, entry.js, exit.js + utils.jsDo first
0cPrereqAdd linter rule — forbid indicator imports in exit.jsDo first
0dPrereqImplement exit_loop schema block — SL, TP, max hold, emergency exit rulesDo first
0ePrereqUpdate Strategy Page — replace single editor with file system manager + per-file editorDo first
0fPrereqUpdate AI write_strategy tool call — file-scoped writes, pre-write declaration, post-write diff summaryDo first
0gPrereqValidate fast exit on current 5m scalper — confirm positions close at tick speedDo first
0hPrereqStub out bias loop interface in runner (passthrough returning neutral) — foundation for Version B/CDo first
1—Stabilise core single-TF strategy on demo with fast exit loop activeNext
2AAdd mtf_filter parameter block to existing bot schema and strategy codeNext
3ABacktest and validate — confirm improved Sharpe and reduced drawdownNext
4BDesign timeframe_pairs schema + concurrent_signal_mode + shared risk blockLater
5BBuild orchestration layer for parallel pair runners outside strategy codeLater
6BWire bias loop into orchestrator — activate H4/H1 bias checks per pairLater
7BImplement risk arbitration layer — all three concurrent signal modesLater
8BAdd account-type detection to AI chat — auto-set default concurrent_signal_modeLater
9BDeploy on demo using Joco's 4H/15m + 1H/5m reference config in prop_safe modeLater
10CDesign timeframe_instances schema for raw parallel executionDeferred
11CBuild Version C runner — reuse orchestration from Version B, remove bias filter layerDeferred
12CDeploy on personal live account for grid/scalping testingDeferred

Resolved Design Decisions​

All four open questions have been answered: everything is configurable — via schema/parameters, strategy code, UI, or AI chat — with sensible defaults where applicable.


1. Entry indicator per pair — configurable per pair​

Each pair in the timeframe_pairs array defines its own entry_indicator independently. There is no requirement for pairs to share the same indicator. The swing pair and intraday pair can use entirely different logic.

Configurable via: schema (entry_indicator field per pair), AI chat (asked per pair during setup), UI (indicator selector per pair in bot config screen).

Sensible default: Both pairs default to bollinger_bands + adx_filter: true on first creation. The user can override per pair at any time.

Schema:

{
"timeframe_pairs": [
{
"pair_id": "swing",
"entry_timeframe": "M15",
"entry_indicator": "bollinger_bands",
"adx_filter": true
},
{
"pair_id": "intraday",
"entry_timeframe": "M5",
"entry_indicator": "rsi_reversal",
"adx_filter": false
}
]
}

AI chat prompt:

"What entry indicator should I use for the Intraday pair (5m)? Options: Bollinger Bands, RSI Reversal, EMA Crossover — or keep the same as the Swing pair."


2. Confluence scale factor — configurable per bot​

The signal_confluence_scaling block is fully configurable. The defaults (1.5x for 2 pairs aligned, 2.0x for 3 pairs aligned) are sensible starting points based on standard position sizing practice, but the user can override them per bot.

Configurable via: schema (signal_confluence_scaling block), AI chat (asked during prop_safe mode setup), UI (scale factor inputs in risk settings panel).

Sensible defaults: 2 aligned = 1.5x, 3 aligned = 2.0x, hard cap at 2.0x regardless of alignment count.

Schema:

{
"concurrent_signal_mode": "prop_safe",
"signal_confluence_scaling": {
"2_pairs_aligned": 1.5,
"3_pairs_aligned": 2.0,
"max_scaled_size_pct": 2.0
}
}

AI chat prompt:

"When both pairs agree on direction at the same time, I'll scale up position size. Default is 1.5x for 2 pairs aligned. Do you want to change that, or keep the default?"


3. Max concurrent positions (Version C) — configurable with sensible defaults​

The max_open_positions_total in Version C is fully configurable. Different grid and scalping strategies have very different position count requirements. The default is set conservatively at 10 to prevent runaway exposure on first deploy, with the user able to raise or lower it.

Configurable via: schema (risk.max_open_positions_total), AI chat (asked during Version C setup), UI (numeric input in risk settings panel).

Sensible defaults:

ModeDefault max positionsRationale
prop_safe1 (merged)Always one position per signal event
stack3One per active pair, reasonable pyramid depth
grid10Conservative starting point for grid bots; user raises as needed

Schema:

{
"concurrent_signal_mode": "grid",
"risk": {
"max_open_positions_total": 10,
"max_loss_per_instance_pct": 0.5,
"circuit_breaker_drawdown_pct": 5.0
}
}

AI chat prompt:

"What's the maximum number of positions you want open at any one time across all timeframes? I'll default to 10 — you can raise this once you've validated the strategy on demo."


4. Per-pair P&L dashboard breakdown — configurable, on by default​

The pair_id label is user-facing. The dashboard shows P&L, win rate, trade count, and average RR broken down per pair by default. This can be toggled off to show only aggregate bot performance if preferred.

Configurable via: UI toggle in the bot performance dashboard (per-pair vs aggregate view), schema (dashboard.show_pair_breakdown: true/false), AI chat ("Show me performance broken down by pair" / "Show me overall totals only").

Default: Per-pair breakdown on — this is the more informative view and helps the user evaluate whether each individual pair is contributing positively. If the intraday pair is losing while the swing pair is profitable, the user needs to see that to make decisions.

Schema:

{
"dashboard": {
"show_pair_breakdown": true,
"pair_metrics": ["pnl", "win_rate", "trade_count", "avg_rr", "avg_duration"]
}
}

UI behaviour: Bot performance screen shows a top-level aggregate card, then expandable per-pair cards below. Each pair card shows its own equity curve, trade log, and metric breakdown. Toggle between views persists per user preference.

AI chat prompt examples:

"Show me how each pair is performing separately."
"Which pair made more money this week — swing or intraday?"
"Turn off the per-pair breakdown, just show me totals."