The API result_summary is a flat object with top-level fields
(total_positions, win_rate, profit_factor, net_pnl, sharpe_ratio, etc.)
not a nested backtest_metadata/instruments map. This was causing all
metrics to parse as None/zero for every completed run.
- Rewrite BacktestResult::from_response() to read flat fields directly
- Replace parse_ratio_value/parse_decimal_str with a single parse_number()
that accepts both JSON numbers and decimal strings
- Populate winning_positions, losing_positions, total_fees, avg_bars_in_trade
(previously always None)
- Simplify from_response signature — exchange/base/quote no longer needed
- Add expected_count and coverage_pct to CandleCoverage struct
- Update all example sell rules to use position_quantity instead of "0.01"
- Note that "9999" is a valid sell-all alias (auto-capped by the API)
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Add /api/v1/strategies/validate client to SwymClient; wire into agent loop
before submission so all DSL errors are surfaced in one round-trip
- Update dsl-schema.json to upstream: quantity is now oneOf[DecimalString, Expr],
ExprApplyFunc uses "input" field (renamed from "expr")
- Update prompts: document expression-based quantity sizing (fixed-fraction and
ATR-based examples), fix apply_func to use "input" not "expr" throughout
- Remove unused ValidationError import
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Three related improvements to help the model learn and explore effectively:
Strategy JSON in history: include the compact strategy JSON in each
IterationRecord::summary() so the LLM knows exactly what was tested in
every past iteration, not just the outcome metrics. Without this the model
had no record of what it tried once conversation history was trimmed.
Rule comment in audit: include rule_comment from the condition audit in
the formatted audit string so the LLM can correlate hit-rate data with
the rule's stated purpose.
Convergence detection and anti-anchoring: diagnose_history() now returns
(String, bool) where the bool signals that the last 3 iterations had
avg_sharpe spread < 0.03 (model stuck in local optimum). When converged:
- Emit a ⚠ CONVERGENCE DETECTED note listing untried candle intervals
- Suppress best_so_far JSON to break the anchoring effect that was
causing the model to produce near-identical strategies for 13+ iterations
- Targeted "try a different approach" instruction
Also add volume-as-field clarification to the DSL mistakes section in
the system prompt, fixing the "unknown variant `volume`" submit error.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
The swym API response structure differs from what the code previously
assumed. Fix all field extraction to match the real shape:
- total_positions: backtest_metadata.position_count (not top-level)
- sharpe_ratio, win_rate, profit_factor: instruments.{key}.{field}.value
wrapped decimal strings (not plain floats); treat Decimal::MAX sentinel
(~7.9e28) as None
- net_pnl: instruments.{key}.pnl (plain decimal string)
- instrument key derived as "{exchange_no_underscores}-{base}_{quote}"
Also fix coverage-based backtest_from clamping: after the coverage
check, compute the effective backtest start as the max first_open across
all instruments × common intervals, so strategies never fail with
"requested range outside available data". Log per-interval date ranges
for each instrument at startup.
Additionally:
- Compact format_audit_summary to handle {"rules":[...],"total_bars":N}
structure with per-condition true_count/evaluated breakdown
- Drop avg_bars from summary_line (field absent from API)
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Log full strategy JSON at debug level, show full anyhow cause chain on
submit failures, surface condition_audit_summary for 0-trade results in
both logs and the summary fed back to the AI each iteration.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>