use proptest::prelude::*;
use wickra_backtest_core::{run_json, run_with_capital, Candle, StrategySpec};
fn valid_candle() -> impl Strategy<Value = Candle> {
(
1.0f64..1_000_000.0,
0.0f64..10_000.0,
0.0f64..10_000.0,
0.0f64..1.0,
0.0f64..1.0,
)
.prop_map(|(base, up, down, open_frac, close_frac)| {
let low = (base - down).max(0.0001);
let high = (base + up).max(low + 0.0001);
Candle {
time: 0,
open: low + open_frac * (high - low),
high,
low,
close: low + close_frac * (high - low),
volume: 0.0,
}
})
}
fn price_threshold_spec() -> StrategySpec {
StrategySpec::parse(
r#"{"symbol":"x","timeframe":"1h","indicators":{},
"entry":{"gt":[{"price":"close"},500]},
"exit":{"lt":[{"price":"close"},500]},
"sizing":{"type":"fixed_fraction","fraction":0.5}}"#,
)
.unwrap()
}
proptest! {
#[test]
fn parse_never_panics(s in ".*") {
let _ = StrategySpec::parse(&s);
}
#[test]
fn run_json_never_panics(s in ".*") {
let _ = run_json(&s);
}
#[test]
fn engine_never_panics_on_arbitrary_candles(
raw in proptest::collection::vec(
(any::<f64>(), any::<f64>(), any::<f64>(), any::<f64>(), any::<f64>()), 1..40)
) {
let candles: Vec<Candle> = raw
.into_iter()
.enumerate()
.map(|(i, (o, h, l, c, v))| Candle {
time: i64::try_from(i).unwrap(), open: o, high: h, low: l, close: c, volume: v,
})
.collect();
let spec = price_threshold_spec();
let _ = run_with_capital(&spec, &candles, 10_000.0);
}
#[test]
fn engine_never_panics_on_valid_candles(
mut candles in proptest::collection::vec(valid_candle(), 1..60)
) {
for (i, c) in candles.iter_mut().enumerate() {
c.time = i64::try_from(i).unwrap();
}
let spec = price_threshold_spec();
let report = run_with_capital(&spec, &candles, 10_000.0).expect("run");
let m = &report.metrics;
prop_assert!(m.pnl.is_finite());
prop_assert!(m.return_pct.is_finite());
prop_assert!(m.sharpe.is_finite());
prop_assert!(m.sortino.is_finite());
prop_assert!(m.max_drawdown.is_finite());
prop_assert!(!m.calmar.is_nan());
prop_assert_eq!(report.equity.len(), candles.len());
}
}