Depends on: #109
Design doc: docs/strategy_composable_engine_design.md (§1.4–1.5, §1.8)
Implement the rule-evaluation layer. Same plugin pattern as series and predicates.
Predicates (P0) — @register_predicate:
gt, lt, gte, lte, cross_above, cross_below, all, any, not,
has_position, profit_pct, loss_pct
Rules:
scope (flat | long | always), priority, when, then
Filter by scope → sort by priority (higher first) → first match wins
Actions (P0) — @register_action:
buy — open long; optional size, stop_loss_pct, take_profit_pct
If SL/TP set, attach PositionEffect on portfolio (API from #111)
sell — close position (size: all in MVP)
hold — no-op
Each action handler: (ctx, action) → Signal
SL/TP are NOT separate action types in P0.
They are optional fields on buy, or explicit rules (loss_pct / profit_pct → sell).
Out of scope (for Week 6 / P1):
set_stop_loss, move_stop, sell_partial, wait
ComposableStrategy.on_candle() → one Signal per bar.
Definition of Done:
- @register_predicate and @register_action registries
- EvaluationContext (portfolio + precomputed series at bar i)
- Rules evaluator wired to action handlers
- Unit tests: scope, priority, each P0 action
- Integration test: fixture YAML fires expected BUY / SELL / HOLD
Depends on: #109
Design doc: docs/strategy_composable_engine_design.md (§1.4–1.5, §1.8)
Implement the rule-evaluation layer. Same plugin pattern as series and predicates.
Predicates (P0) — @register_predicate:
gt, lt, gte, lte, cross_above, cross_below, all, any, not,
has_position, profit_pct, loss_pct
Rules:
scope (flat | long | always), priority, when, then
Filter by scope → sort by priority (higher first) → first match wins
Actions (P0) — @register_action:
buy — open long; optional size, stop_loss_pct, take_profit_pct
If SL/TP set, attach PositionEffect on portfolio (API from #111)
sell — close position (size: all in MVP)
hold — no-op
Each action handler: (ctx, action) → Signal
SL/TP are NOT separate action types in P0.
They are optional fields on buy, or explicit rules (loss_pct / profit_pct → sell).
Out of scope (for Week 6 / P1):
set_stop_loss, move_stop, sell_partial, wait
ComposableStrategy.on_candle() → one Signal per bar.
Definition of Done: