chore: attempt dedupe guidance in prompt

This commit is contained in:
2026-03-11 18:15:24 +02:00
parent 75c95f7935
commit fcb9a2f553
2 changed files with 40 additions and 1 deletions

View File

@@ -218,6 +218,8 @@ pub async fn run(cli: &Cli) -> Result<()> {
let mut conversation: Vec<Message> = Vec::new();
let mut best_strategy: Option<(f64, Value)> = None; // (avg_sharpe, strategy)
let mut consecutive_failures = 0u32;
// Deduplication: track canonical strategy JSON → first iteration it was tested.
let mut tested_strategies: std::collections::HashMap<String, u32> = std::collections::HashMap::new();
let instrument_names: Vec<String> = instruments.iter().map(|i| i.symbol.clone()).collect();
@@ -392,6 +394,27 @@ pub async fn run(cli: &Cli) -> Result<()> {
}
}
// Deduplication check: skip strategies identical to one already tested this run.
let strategy_key = serde_json::to_string(&strategy).unwrap_or_default();
if let Some(&first_iter) = tested_strategies.get(&strategy_key) {
warn!("duplicate strategy (identical to iteration {first_iter}), skipping backtest");
let record = IterationRecord {
iteration,
strategy: strategy.clone(),
results: vec![],
validation_notes: vec![format!(
"DUPLICATE: this exact strategy was already tested in iteration {first_iter}. \
You submitted identical JSON. You MUST design a completely different strategy — \
different indicator family, different entry conditions, or different timeframe. \
Do NOT submit the same JSON again."
)],
};
info!("{}", record.summary());
history.push(record);
continue;
}
tested_strategies.insert(strategy_key, iteration);
// Run backtests against all instruments (in-sample)
let mut results: Vec<BacktestResult> = Vec::new();

View File

@@ -103,6 +103,14 @@ Buy a fixed number of base units (semantic alias for a decimal string):
"right":{{"kind":"func","name":"atr","period":14}}}}
```
CRITICAL — ATR sizing and balance limits: `N/atr(14)` expresses quantity in BASE asset units.
For BTC, 4h ATR ≈ $15003000. So `1000/atr(14)` ≈ 0.40.7 BTC ≈ $32k56k notional —
silently rejected on a $10k account (fill returns None, 0 positions open, no error shown).
The numerator N represents your intended dollar risk per trade. For a $10k account keep N ≤ 200.
`200/atr(14)` ≈ 0.070.13 BTC ≈ $5.6k10k notional — fits within a $10k account.
Prefer `percent_of_balance` for most sizing. Only reach for ATR-based Expr sizing when you need
volatility-scaled position risk, and keep the numerator proportional to your risk tolerance.
**4. Exit rules** — use `position_quantity` to close the exact open size:
```json
{{"kind":"position_quantity"}}
@@ -189,7 +197,11 @@ When I share results from previous iterations, use them to guide your next strat
- **Zero trades**: The entry conditions are too restrictive or never co-occur.
Relax thresholds, simplify conditions, or check if the indicator periods make
sense for the candle interval.
sense for the candle interval. Also check your position sizing — if using an
ATR-based Expr quantity (`N/atr(14)`), a large N can produce a notional value
exceeding your account balance (e.g. `1000/atr(14)` on BTC ≈ 0.4 BTC ≈ $32k),
which is silently rejected by the fill engine. Switch to `percent_of_balance`
or reduce N to ≤ 200 for a $10k account.
- **Many trades but negative PnL**: The entry signal has no edge, or the exit
logic is poor. Try different indicator combinations, add trend filters, or
@@ -516,6 +528,10 @@ CRITICAL: `apply_func` uses `"input"`, not `"expr"`. Writing `"expr":` will be r
- Spot markets are long-only: gate buy (entry) rules with state "flat" and sell (exit) rules with state "long". Never add a short-entry (sell when flat) rule on spot.
- Futures markets support both directions: long entry = buy when flat; long exit = sell when long; short entry = sell when flat; short exit (cover) = buy when short. Always include a stop-loss and time exit for both long and short legs.
- Never use a placeholder string for `quantity` — `"ATR_SIZED"`, `"FULL_BALANCE"`, `"dynamic"`, etc. are all invalid and will be rejected.
- Don't use large ATR-based sizing numerators. `N/atr(14)` gives BASE units; for BTC (ATR ≈ $2000
on 4h), `1000/atr(14)` ≈ 0.5 BTC ≈ $40k — silently rejected on a $10k account. Keep N ≤ 200
or use `percent_of_balance`. The condition audit may show entry conditions firing while 0 positions
open — this is the typical symptom of an oversized ATR quantity.
- `{{"method":"position_quantity"}}` is WRONG for exit rules — use `{{"kind":"position_quantity"}}` (see Quantity section above).
{futures_examples}"##,
futures_examples = if has_futures { FUTURES_SHORT_EXAMPLES } else { "" },