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|✗ Errorbased 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 type | File(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 minutesentry.js— no changesbias.js— no changesShall 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, andutils.jswere 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 toentry.jsinstead, which runs on the 5m signal loop. Would you like me to show you what changed?"
| Version | Name | Description | Schema change | Priority |
|---|---|---|---|---|
| Prereq | Decoupled Loops + 3-File Architecture | Separate exit/signal/bias loops. Split strategy into 3 files. Update Strategy Page + AI tool call. | Yes — exit_loop block | Phase 0 — do first |
| A | MTF Confluence Filter | Single pair: higher TF bias + lower TF entry. One trade at a time. | No | Phase 1 |
| B | MTF Pair Stack | Multiple bias+entry pairs running in parallel. Joco's described architecture. | Yes — timeframe_pairs | Phase 2 |
| C | Raw Parallel TF Execution | Fully independent single-TF runners. No bias filter. For grid/scalping. | Yes — timeframe_instances | Phase 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_idlabels (swing,intraday) are used in logs, dashboard P&L breakdown, and alertsposition_size_pctis set lower for the intraday pair — M5 entries carry more noise than M15concurrent_signal_modestill 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: gridenables 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
| Risk | Version A | Version B (prop_safe) | Version B (stack) | Version C (grid) |
|---|---|---|---|---|
| False signal rate | Low — bias filter applied | Low — each pair has bias filter | Low — each pair has bias filter | High — no bias filter per instance |
| Correlation stacking breach | N/A — single position | Prevented by design | User's responsibility | User's responsibility |
| Bidirectional positions | Blocked | Blocked | Blocked | Allowed |
| Prop firm flag risk | None | Minimal | High — do not use on prop | Very high — do not use on prop |
| Runaway exposure | N/A | Aggregate cap + circuit breaker | Aggregate cap | Max positions + per-instance loss cap |
| SL/TP complexity | Single position | Single merged position (prop_safe) or per-pair | Per-pair independent | Per-instance independent |
| Dashboard complexity | Simple | Simple to moderate | Moderate | Complex — requires net exposure view |
Implementation Roadmap
| Phase | Version | Action | Priority |
|---|---|---|---|
| 0a | Prereq | Refactor runner — separate exit loop from signal loop | Do first |
| 0b | Prereq | Split strategy into 3 files: bias.js, entry.js, exit.js + utils.js | Do first |
| 0c | Prereq | Add linter rule — forbid indicator imports in exit.js | Do first |
| 0d | Prereq | Implement exit_loop schema block — SL, TP, max hold, emergency exit rules | Do first |
| 0e | Prereq | Update Strategy Page — replace single editor with file system manager + per-file editor | Do first |
| 0f | Prereq | Update AI write_strategy tool call — file-scoped writes, pre-write declaration, post-write diff summary | Do first |
| 0g | Prereq | Validate fast exit on current 5m scalper — confirm positions close at tick speed | Do first |
| 0h | Prereq | Stub out bias loop interface in runner (passthrough returning neutral) — foundation for Version B/C | Do first |
| 1 | — | Stabilise core single-TF strategy on demo with fast exit loop active | Next |
| 2 | A | Add mtf_filter parameter block to existing bot schema and strategy code | Next |
| 3 | A | Backtest and validate — confirm improved Sharpe and reduced drawdown | Next |
| 4 | B | Design timeframe_pairs schema + concurrent_signal_mode + shared risk block | Later |
| 5 | B | Build orchestration layer for parallel pair runners outside strategy code | Later |
| 6 | B | Wire bias loop into orchestrator — activate H4/H1 bias checks per pair | Later |
| 7 | B | Implement risk arbitration layer — all three concurrent signal modes | Later |
| 8 | B | Add account-type detection to AI chat — auto-set default concurrent_signal_mode | Later |
| 9 | B | Deploy on demo using Joco's 4H/15m + 1H/5m reference config in prop_safe mode | Later |
| 10 | C | Design timeframe_instances schema for raw parallel execution | Deferred |
| 11 | C | Build Version C runner — reuse orchestration from Version B, remove bias filter layer | Deferred |
| 12 | C | Deploy on personal live account for grid/scalping testing | Deferred |
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:
| Mode | Default max positions | Rationale |
|---|---|---|
prop_safe | 1 (merged) | Always one position per signal event |
stack | 3 | One per active pair, reasonable pyramid depth |
grid | 10 | Conservative 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."