PORTFOLIO files¶
A PORTFOLIO file composes N strategy files into one deployable unit. Each child runs independently, gated by a RUN <child> rule at the portfolio level. The daemon fans out into one observability port + log file per child.
Use a portfolio when:
- You want regime-gated strategy switching — different strategies for different market conditions
- You want multi-asset / multi-timeframe strategies running concurrently as one unit
- You need per-strategy operator control while keeping deployment as one artifact
Shape¶
PORTFOLIO <name> VERSION <int>
[ SYMBOLS
<alias> = <BROKER>:<symbol> EVERY <timeframe>
[ ... ]
]
IMPORT '<path>' AS <alias> [ HOLD ]
[ ... more imports ... ]
[ LET <name> = <expression> ]
RULES
[ WHEN <condition> ] RUN <alias>
[ ... more RUN rules ... ]
The shape is similar to STRATEGY, but with IMPORT declaring child strategies and RUN <alias> actions.
Basic — regime-gated switching¶
PORTFOLIO btc_regimes VERSION 1
SYMBOLS
btc = BACKTEST:BTCUSDT EVERY 1h
IMPORT 'trend.qkt' AS trend
IMPORT 'meanrev.qkt' AS meanrev
IMPORT 'breakout.qkt' AS breakout
LET adxValue = adx(btc, 14)
RULES
WHEN adxValue > 30 RUN trend -- strong trend
WHEN adxValue < 20 RUN meanrev -- ranging
WHEN adxValue BETWEEN 20 AND 30 RUN breakout -- transitional
Only one child runs at a time. When ADX moves from ≥30 to <20, trend deactivates (positions closed unless HOLD) and meanrev activates.
IMPORT syntax¶
- Path is relative to the portfolio file. Use forward slashes.
- Alias is how the portfolio's
RUNrules reference the child. HOLD(optional): keep the child's positions when it deactivates. Without HOLD, deactivation flattens.
IMPORT 'trend.qkt' AS trend -- closes positions on deactivate
IMPORT 'longterm.qkt' AS longterm HOLD -- keeps positions when deactivated
HOLD mode is useful for long-horizon children whose entries take days to play out. You don't want a regime flip to flush a 3-day position.
RUN <alias> action¶
The portfolio's only action verb. Activates the named child.
- The child becomes "active" — it processes ticks, evaluates its own rules, emits signals.
- An "active" child whose condition transitions to false becomes "inactive" — pauses processing, flattens (or holds) positions.
- Multiple children can be active simultaneously if multiple conditions hold.
Always-on (no gating)¶
Activates someChild always — used for the "always running" parts of a portfolio.
Mutually exclusive (regime gates)¶
Above example with ADX. Make sure your conditions are mutually exclusive (BETWEEN/<>) so exactly one child is active at any time.
Concurrent (multi-asset)¶
Three children running in parallel, each trading its own asset. The portfolio is just a deployment wrapper here — no regime logic.
Operator control¶
Children expose their own ports + logs. The daemon's qkt list shows them indented under the portfolio:
NAME KIND PORT TRADES STATE
btc-regimes portfolio 47291 - running
trend child 47292 8 active
meanrev child 47293 23 inactive
breakout child 47294 5 inactive
Operators can override gating:
qkt start btc-regimes/meanrev -- force meanrev active regardless of regime
qkt stop btc-regimes/trend -- force trend inactive
Manual overrides clear when you qkt resume <portfolio>.
Shared symbol declarations¶
Portfolio-level SYMBOLS is inherited by every child that doesn't redeclare. Useful when all children trade the same underlying:
PORTFOLIO multi_strat VERSION 1
SYMBOLS
btc = BACKTEST:BTCUSDT EVERY 1h -- all children see this
IMPORT 'a.qkt' AS a
IMPORT 'b.qkt' AS b
-- if a.qkt references 'btc', it picks up the portfolio's declaration
-- if a.qkt has its own SYMBOLS block, that wins
The portfolio's symbols are also the only ones whose data the portfolio rules can read (for regime detection).
Risk and the portfolio¶
Daemon-level risk rules apply to the whole portfolio, not per-child. If one child triggers max-daily-loss, the entire daemon halts — every other strategy too.
To express per-child risk limits, use qkt.config.yaml's risk.per_strategy block:
These apply independently to each strategy hosted in the daemon — including portfolio children.
LET in portfolios¶
LET works the same as in STRATEGY files — name an expression for reuse in RUN conditions:
PORTFOLIO mybook VERSION 1
SYMBOLS
btc = BACKTEST:BTCUSDT EVERY 1h
IMPORT 'trend.qkt' AS trend
IMPORT 'meanrev.qkt' AS meanrev
LET adxStrong = adx(btc, 14) > 30
LET adxWeak = adx(btc, 14) < 20
RULES
WHEN adxStrong RUN trend
WHEN adxWeak RUN meanrev
LET names make portfolio rules read like English.
What children inherit¶
| Inherited | Notes |
|---|---|
SYMBOLS declarations |
Children can override |
| Daemon-level risk rules | Apply across all children |
Broker configurations from qkt.config.yaml |
Children resolve broker prefixes the same way |
--param overrides |
Apply to all children unless qualified |
| Not inherited | |
|---|---|
DEFAULTS from portfolio |
Children have their own |
LET from portfolio |
Children have their own |
Common gotchas¶
- Non-mutually-exclusive regime gates leave gaps. If your rules cover only
> 30and< 20, the range[20, 30]has no child active. UseBETWEEN ... AND ...orELSEpatterns to cover everything. - Children with their own
SYMBOLSblocks override. A child that redeclaresbtcwith a different timeframe than the portfolio works fine, but it costs an extra aggregator. HOLDdoesn't mean "keep entering." A held child has its positions preserved but stops generating new signals when inactive. It only manages existing positions.- Cascade stop.
qkt stop <portfolio>cascades to every child. Useqkt stop <portfolio>/<child>to stop a specific child. - Portfolio file is itself a strategy. It has
VERSION,RULES, can haveLET. It just usesRUNactions instead ofBUY/SELL.
What this composes with¶
- STRATEGY block — child files are regular strategies
- Conditions —
RUNrules use the same condition grammar - Indicators — regime detectors are usually indicator-based
- Portfolio example — full deployment with three regimes
- Phase 13b, Phase 14 — design notes on portfolio fan-out