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 conversation: Vec<Message> = Vec::new();
let mut best_strategy: Option<(f64, Value)> = None; // (avg_sharpe, strategy) let mut best_strategy: Option<(f64, Value)> = None; // (avg_sharpe, strategy)
let mut consecutive_failures = 0u32; 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(); 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) // Run backtests against all instruments (in-sample)
let mut results: Vec<BacktestResult> = Vec::new(); 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}}}} "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: **4. Exit rules** — use `position_quantity` to close the exact open size:
```json ```json
{{"kind":"position_quantity"}} {{"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. - **Zero trades**: The entry conditions are too restrictive or never co-occur.
Relax thresholds, simplify conditions, or check if the indicator periods make 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 - **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 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. - 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. - 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. - 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). - `{{"method":"position_quantity"}}` is WRONG for exit rules — use `{{"kind":"position_quantity"}}` (see Quantity section above).
{futures_examples}"##, {futures_examples}"##,
futures_examples = if has_futures { FUTURES_SHORT_EXAMPLES } else { "" }, futures_examples = if has_futures { FUTURES_SHORT_EXAMPLES } else { "" },