fix: ValidationError.path optional, correct position_quantity usage in prompts

- ValidationError.path is Option<String> — the API omits it for top-level
  structural errors. The required String was causing every validate call to
  fail to deserialize, falling through to submission instead of catching errors.

- Log path as "(top-level)" when absent

- Prompts: add explicit CRITICAL note that {"method":"position_quantity"} is
  wrong — position_quantity is an Expr (uses "kind") not a SizingMethod (uses
  "method"). The new SizingMethod examples caused the model to over-apply
  "method" to exits universally across the entire run.

- Prompts: note that fixed_sum has no multiplier field (additionalProperties)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-03-10 09:45:17 +02:00
parent ee260ea4d5
commit 85896752f2
3 changed files with 11 additions and 5 deletions

View File

@@ -345,11 +345,11 @@ pub async fn run(cli: &Cli) -> Result<()> {
match swym.validate_strategy(&strategy).await { match swym.validate_strategy(&strategy).await {
Ok(api_errors) if !api_errors.is_empty() => { Ok(api_errors) if !api_errors.is_empty() => {
for e in &api_errors { for e in &api_errors {
warn!(" DSL error at {}: {}", e.path, e.message); warn!(" DSL error at {}: {}", e.path.as_deref().unwrap_or("(top-level)"), e.message);
} }
let error_notes: Vec<String> = api_errors let error_notes: Vec<String> = api_errors
.iter() .iter()
.map(|e| format!("DSL error at {}: {}", e.path, e.message)) .map(|e| format!("DSL error at {}: {}", e.path.as_deref().unwrap_or("(top-level)"), e.message))
.collect(); .collect();
validation_notes.extend(error_notes); validation_notes.extend(error_notes);
let record = IterationRecord { let record = IterationRecord {

View File

@@ -110,8 +110,13 @@ Buy a fixed number of base units (semantic alias for a decimal string):
Alternatively, `"9999"` works for exits: sell quantities are automatically capped to the open Alternatively, `"9999"` works for exits: sell quantities are automatically capped to the open
position size, so a large fixed number is equivalent to `position_quantity`. position size, so a large fixed number is equivalent to `position_quantity`.
NEVER use placeholder strings like `"ATR_SIZED"`, `"FULL_BALANCE"`, `"all"`, `"dynamic"` — CRITICAL mistakes to never make:
these are rejected immediately. - `{{"method":"position_quantity"}}` is WRONG — `position_quantity` is an Expr, not a SizingMethod.
CORRECT: `{{"kind":"position_quantity"}}`. The `"method"` field belongs ONLY to the three
declarative sizing objects (`fixed_sum`, `percent_of_balance`, `fixed_units`).
- `{{"method":"fixed_sum","amount":"100","multiplier":"2.0"}}` is WRONG — `fixed_sum` has no
`multiplier` field. Only `amount` is accepted alongside `method`.
- NEVER add extra fields to SizingMethod objects — they use `additionalProperties: false`.
### Multi-timeframe ### Multi-timeframe
Any expression can reference a different timeframe via "timeframe" field. Any expression can reference a different timeframe via "timeframe" field.

View File

@@ -14,7 +14,8 @@ pub struct ValidationResponse {
#[derive(Debug, Deserialize, Clone)] #[derive(Debug, Deserialize, Clone)]
pub struct ValidationError { pub struct ValidationError {
pub path: String, /// Dotted JSON path to the offending field. Absent for top-level structural errors.
pub path: Option<String>,
pub message: String, pub message: String,
} }