feat(agent): add strategy quality introspection
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>
This commit is contained in:
17
src/agent.rs
17
src/agent.rs
@@ -3,7 +3,7 @@ use std::time::Duration;
|
|||||||
|
|
||||||
use anyhow::{Context, Result};
|
use anyhow::{Context, Result};
|
||||||
use serde_json::Value;
|
use serde_json::Value;
|
||||||
use tracing::{error, info, warn};
|
use tracing::{debug, error, info, warn};
|
||||||
|
|
||||||
use crate::claude::{self, ClaudeClient, Message};
|
use crate::claude::{self, ClaudeClient, Message};
|
||||||
use crate::config::{Cli, Instrument};
|
use crate::config::{Cli, Instrument};
|
||||||
@@ -190,6 +190,7 @@ pub async fn run(cli: &Cli) -> Result<()> {
|
|||||||
strategy["candle_interval"].as_str().unwrap_or("?"),
|
strategy["candle_interval"].as_str().unwrap_or("?"),
|
||||||
strategy["rules"].as_array().map(|r| r.len()).unwrap_or(0)
|
strategy["rules"].as_array().map(|r| r.len()).unwrap_or(0)
|
||||||
);
|
);
|
||||||
|
debug!("strategy JSON:\n{}", serde_json::to_string_pretty(&strategy).unwrap_or_default());
|
||||||
|
|
||||||
// Save the strategy JSON
|
// Save the strategy JSON
|
||||||
let strat_path = cli.output_dir.join(format!("strategy_{iteration:03}.json"));
|
let strat_path = cli.output_dir.join(format!("strategy_{iteration:03}.json"));
|
||||||
@@ -215,10 +216,15 @@ pub async fn run(cli: &Cli) -> Result<()> {
|
|||||||
{
|
{
|
||||||
Ok(result) => {
|
Ok(result) => {
|
||||||
info!(" {}", result.summary_line());
|
info!(" {}", result.summary_line());
|
||||||
|
if result.total_positions.unwrap_or(0) == 0 {
|
||||||
|
if let Some(audit) = &result.condition_audit_summary {
|
||||||
|
info!(" condition audit: {}", serde_json::to_string_pretty(audit).unwrap_or_default());
|
||||||
|
}
|
||||||
|
}
|
||||||
results.push(result);
|
results.push(result);
|
||||||
}
|
}
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
warn!(" backtest failed for {}: {e}", inst.symbol);
|
warn!(" backtest failed for {}: {e:#}", inst.symbol);
|
||||||
results.push(BacktestResult {
|
results.push(BacktestResult {
|
||||||
run_id: uuid::Uuid::nil(),
|
run_id: uuid::Uuid::nil(),
|
||||||
instrument: inst.symbol.clone(),
|
instrument: inst.symbol.clone(),
|
||||||
@@ -278,10 +284,15 @@ pub async fn run(cli: &Cli) -> Result<()> {
|
|||||||
{
|
{
|
||||||
Ok(result) => {
|
Ok(result) => {
|
||||||
info!(" OOS {}", result.summary_line());
|
info!(" OOS {}", result.summary_line());
|
||||||
|
if result.total_positions.unwrap_or(0) == 0 {
|
||||||
|
if let Some(audit) = &result.condition_audit_summary {
|
||||||
|
info!(" OOS condition audit: {}", serde_json::to_string_pretty(audit).unwrap_or_default());
|
||||||
|
}
|
||||||
|
}
|
||||||
oos_results.push(result);
|
oos_results.push(result);
|
||||||
}
|
}
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
warn!(" OOS backtest failed for {}: {e}", inst.symbol);
|
warn!(" OOS backtest failed for {}: {e:#}", inst.symbol);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
43
src/swym.rs
43
src/swym.rs
@@ -82,7 +82,7 @@ impl BacktestResult {
|
|||||||
self.error_message.as_deref().unwrap_or("unknown error")
|
self.error_message.as_deref().unwrap_or("unknown error")
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
format!(
|
let mut s = format!(
|
||||||
"[{}] trades={} win_rate={:.1}% pf={:.2} net_pnl={:.2} sharpe={:.2} avg_bars={:.1}",
|
"[{}] trades={} win_rate={:.1}% pf={:.2} net_pnl={:.2} sharpe={:.2} avg_bars={:.1}",
|
||||||
self.instrument,
|
self.instrument,
|
||||||
self.total_positions.unwrap_or(0),
|
self.total_positions.unwrap_or(0),
|
||||||
@@ -91,7 +91,17 @@ impl BacktestResult {
|
|||||||
self.net_pnl.unwrap_or(0.0),
|
self.net_pnl.unwrap_or(0.0),
|
||||||
self.sharpe_ratio.unwrap_or(0.0),
|
self.sharpe_ratio.unwrap_or(0.0),
|
||||||
self.avg_bars_in_trade.unwrap_or(0.0),
|
self.avg_bars_in_trade.unwrap_or(0.0),
|
||||||
)
|
);
|
||||||
|
if self.total_positions.unwrap_or(0) == 0 {
|
||||||
|
if let Some(audit) = &self.condition_audit_summary {
|
||||||
|
let audit_str = format_audit_summary(audit);
|
||||||
|
if !audit_str.is_empty() {
|
||||||
|
s.push_str(" | audit: ");
|
||||||
|
s.push_str(&audit_str);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
s
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Is this result promising enough to warrant out-of-sample validation?
|
/// Is this result promising enough to warrant out-of-sample validation?
|
||||||
@@ -103,6 +113,35 @@ impl BacktestResult {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Render a condition_audit_summary Value into a compact one-line string.
|
||||||
|
/// Handles both object and array shapes we might receive from the API.
|
||||||
|
fn format_audit_summary(audit: &Value) -> String {
|
||||||
|
match audit {
|
||||||
|
Value::Object(map) => map
|
||||||
|
.iter()
|
||||||
|
.map(|(k, v)| format!("{k}={v}"))
|
||||||
|
.collect::<Vec<_>>()
|
||||||
|
.join(", "),
|
||||||
|
Value::Array(arr) => arr
|
||||||
|
.iter()
|
||||||
|
.filter_map(|item| {
|
||||||
|
let name = item.get("name").or_else(|| item.get("condition"))?.as_str()?;
|
||||||
|
// Try common field names for hit counts
|
||||||
|
if let (Some(true_count), Some(total)) = (
|
||||||
|
item.get("true_count").or_else(|| item.get("hit_count")).or_else(|| item.get("true_bars")).and_then(|v| v.as_u64()),
|
||||||
|
item.get("total").or_else(|| item.get("total_bars")).or_else(|| item.get("evaluated")).and_then(|v| v.as_u64()),
|
||||||
|
) {
|
||||||
|
Some(format!("{name}: {true_count}/{total}"))
|
||||||
|
} else {
|
||||||
|
Some(format!("{name}: {item}"))
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.collect::<Vec<_>>()
|
||||||
|
.join(", "),
|
||||||
|
other => other.to_string(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl SwymClient {
|
impl SwymClient {
|
||||||
pub fn new(base_url: &str) -> Result<Self> {
|
pub fn new(base_url: &str) -> Result<Self> {
|
||||||
let client = Client::builder()
|
let client = Client::builder()
|
||||||
|
|||||||
Reference in New Issue
Block a user