feat: validate endpoint integration, Expr quantity sizing, apply_func input field fix

- 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>
This commit is contained in:
2026-03-10 09:12:12 +02:00
parent 5146b3f764
commit 7e1ff51ae0
4 changed files with 171 additions and 24 deletions

View File

@@ -4,6 +4,20 @@ use serde::{Deserialize, Serialize};
use serde_json::Value;
use uuid::Uuid;
/// Response from `POST /api/v1/strategies/validate`.
#[derive(Debug, Deserialize)]
pub struct ValidationResponse {
pub valid: bool,
#[serde(default)]
pub errors: Vec<ValidationError>,
}
#[derive(Debug, Deserialize, Clone)]
pub struct ValidationError {
pub path: String,
pub message: String,
}
/// Client for the swym backtesting API.
pub struct SwymClient {
client: Client,
@@ -254,6 +268,32 @@ impl SwymClient {
resp.json().await.context("parse candle coverage")
}
/// Validate a strategy against the swym DSL schema.
///
/// Calls `POST /api/v1/strategies/validate` and returns a structured list
/// of all validation errors. Returns `Ok(vec![])` when the strategy is valid.
/// Returns `Err` only on network or parse failures, not on DSL errors.
pub async fn validate_strategy(&self, strategy: &Value) -> Result<Vec<ValidationError>> {
let url = format!("{}/strategies/validate", self.base_url);
let resp = self
.client
.post(&url)
.json(strategy)
.send()
.await
.context("validate strategy request")?;
if !resp.status().is_success() {
let status = resp.status();
let body = resp.text().await.unwrap_or_default();
anyhow::bail!("validate strategy {status}: {body}");
}
let parsed: ValidationResponse =
resp.json().await.context("parse validation response")?;
Ok(parsed.errors)
}
/// Submit a backtest run.
pub async fn submit_backtest(
&self,