DCA Investor
A boring strategist. Each Monday morning, it buys a fixed dollar amount split across a fixed basket. It does not try to time entries. It does not skip weeks because "the market looks toppy." It does not double up after a drawdown. The only signals it consults are the calendar and the buying-power floor.
This is the simplest possible strategist — and on purpose. It's the proof-of-pattern for the suite, and it's the most realistic live candidate for a $1k cash account.
Required reading before authoring/modifying:
_shared/intent-schema.md,_shared/tick-decision-emitter.md,_shared/circuit-breakers.md,_shared/position-sizing.md. The numeric rules below live inreferences/decision-rules.md.
Strategy summary
- Universe: SPY, QQQ, BND (broad equity + tech-equity + total-bond).
- Weights: SPY 50% · QQQ 30% · BND 20%.
- Tranche: $50/week total. Per-symbol: SPY $25, QQQ $15, BND $10.
- Cadence: weekly, Monday after market open. Cron hint
0 14 * * 1(10am ET). - Mode: review-only until the human reviews tick logs and promotes to live.
- Exit rule: none. DCA is one-way. Sells are out of scope for this strategist.
- Director call:
regime-intelligenceis called only in light-gate mode — its output is logged innotesbut does not gate any action. A future v2 may add a regime gate; v1 is intentionally pure.
6-phase protocol
Phase 1 — Pre-flight (BLOCKING)
Run _shared/executor's pre-flight via the executor — but capture inputs first so this strategist can short-circuit before invoking it. Specifically:
- Determine cadence eligibility:
- Read the last successful DCA tick timestamp from
~/.hermes/profiles/autotrader/state/dca-investor.json(if it exists). - If less than 6 days have elapsed since the last placement-eligible tick: skip without invoking the executor. Emit
tick_decisionwithactions: []andnotes: "cadence_not_due; next_at <ISO>".
- Read the last successful DCA tick timestamp from
- If cadence IS due, proceed to phase 2.
(The executor will still run its full pre-flight when invoked in phase 5. The cadence check is strategist-side because the executor doesn't know about per-strategist cadences.)
Phase 2 — Compose context
Invoke regime-intelligence via the Skill tool with the prompt:
"Three-line summary of the current macro regime — growth direction, inflation direction, monetary stance. No prose."
Capture the response into a regime_summary string (≤200 chars). It will be embedded in tick_decision.notes. It does NOT affect any sizing or skip decision in v1.
Phase 3 — Signal gather
Call get_equity_quotes(symbols=["SPY", "QQQ", "BND"]) to capture current prices. Used purely for intent_price (the strategist's reference price at decision time). No filtering on this — DCA proceeds at any price.
Phase 4 — Decide
Construct three intents (one per basket member):
[
{
"symbol": "SPY",
"side": "buy",
"quantity_type": "notional",
"notional_usd": 25.00,
"order_type": "market",
"intent_price": <SPY quote.ask_price>,
"max_slippage_pct": 1.0,
"time_in_force": "gfd",
"reason": "DCA weekly tranche — SPY 50% of $50 = $25",
"expires_at": "<today 15:55 ET as ISO>",
"asset_class": "equity",
"option_leg": null
},
/* QQQ $15, BND $10 — same shape */
]
Notes on the intent shape:
quantity_type: notionalbecause the basket weights are designed in dollars, not shares. The executor routes this to Robinhood'sdollar_amountparameter.max_slippage_pct: 1.0is loose on purpose — DCA does not care about precise entry; we just need a sanity check that the quote hasn't fallen off a cliff.expires_atis end-of-RTH today. If the tick somehow runs in extended hours, the intent expires before the executor can place.
If the tick lands inside a circuit_breaker_tripped window, the executor will drop these buys with status: skipped. That's fine — DCA simply waits for the next week.
Phase 5 — Hand off to executor
Invoke _shared/executor via the Skill tool with:
{
"strategy": "dca-investor",
"mode": "<mode from frontmatter>",
"context_hint": "<best guess>",
"intents": [ ...three intents... ]
}
The executor returns:
{
"aborted": null | "<reason>",
"circuit_breaker_tripped": <bool>,
"account_value": <number>,
"buying_power": <number>,
"actions": [ <three RealizedAction objects> ]
}
Phase 6 — Emit & persist
Write the ## tick_decision block. If any action was status: placed, update ~/.hermes/profiles/autotrader/state/dca-investor.json with the placement timestamp (so phase 1 can read it next tick).
If aborted is set: do not update state. The next tick will retry from scratch.
Interactive mode
In Claude Code or autotrader chat:
- "What does DCA say about this week?" — run phases 1-5; emit
tick_decision. All actions will bereviewed(claude-code never places; chat needsEXECUTE:). - "Skip this week" — emit
tick_decisionwithactions: []andnotes: "user_skipped". Do NOT update the state file (so next week's tick is unaffected). - "Increase tranche to $100" — refuse. Tranche size is a frontmatter constant. The user must edit
tranche_size_usdin the SKILL.md frontmatter, not at runtime.
Promoting to live
- Run paper ticks for at least 4 consecutive Mondays. Inspect the
tick_decisionlog for each. - Confirm:
- No
abortedoutcomes you don't understand. - Slippage on
realized_review_pricevsintent_priceis consistently under 0.3%. - The basket math comes out right: SPY notional 50%, QQQ 30%, BND 20% of the tranche.
- No
- Edit this skill's frontmatter:
mode: review→mode: live. - Create cron:
hermes --profile autotrader cron create '0 14 * * 1' --skills strategists/dca-investor. - Watch the first live tick. Verify the order lands in
get_equity_orders.
What this strategist will never do
- Skip a week because the market is "expensive" or "looks toppy."
- Buy extra after a drawdown.
- Sell — even to "take profits" or "rebalance."
- Trade anything outside the basket.
- Override the buying-power cap or per-name cap.
- Run on a non-Monday.
- Run from a profile other than
autotrader.