use crate::testing::{create_standalone_paper_exchange_with_id, ArcExchange};
use crate::{
build_instrument_meta, build_strategy, config::BotConfig, Engine, EngineConfig, EngineRunner,
RunnerConfig,
};
use bot_core::{
AssetId, Exchange, ExchangeId, ExchangeInstance, InstrumentId, InstrumentKind, InstrumentMeta,
Market, MarketIndex, Price, Qty, Quote,
};
use rust_decimal::Decimal;
use rust_decimal_macros::dec;
use serde::{Deserialize, Serialize};
use std::collections::HashMap;
use std::str::FromStr;
use std::sync::Arc;
use wasm_bindgen::prelude::*;
use wasm_bindgen_futures::future_to_promise;
#[wasm_bindgen(start)]
pub fn main() {
console_error_panic_hook::set_once();
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct JsPricePoint {
pub ts_ms: i64,
pub price: String,
}
#[wasm_bindgen]
pub fn get_version() -> String {
env!("CARGO_PKG_VERSION").to_string()
}
#[wasm_bindgen]
pub fn run_backtest(prices_json: String, config_json: String) -> js_sys::Promise {
future_to_promise(async move {
web_sys::console::log_1(&"[WASM Runner] run_backtest started".into());
web_sys::console::log_1(&"[WASM Runner] Parsing config...".into());
let config: BotConfig = serde_json::from_str(&config_json)
.map_err(|e| JsValue::from_str(&format!("Failed to parse config: {}", e)))?;
web_sys::console::log_1(&"[WASM Runner] Parsing prices...".into());
let prices: Vec<JsPricePoint> = serde_json::from_str(&prices_json)
.map_err(|e| JsValue::from_str(&format!("Failed to parse prices: {}", e)))?;
web_sys::console::log_1(&format!("[WASM Runner] Parsed {} prices", prices.len()).into());
web_sys::console::log_1(&"[WASM Runner] Building strategy...".into());
let strategy = build_strategy(&config)
.map_err(|e| JsValue::from_str(&format!("Failed to build strategy: {}", e)))?;
web_sys::console::log_1(&"[WASM Runner] Strategy built successfully".into());
web_sys::console::log_1(&"[WASM Runner] Building instrument meta...".into());
let instrument_meta = build_instrument_meta(&config);
let instrument_id = config.instrument_id();
let environment = config.parse_environment();
web_sys::console::log_1(&format!("[WASM Runner] Environment: {:?}", environment).into());
let sim_config = config.effective_simulation_config();
let simulation_balance =
Decimal::from_str(&sim_config.starting_balance_usdc).unwrap_or(Decimal::new(10_000, 0));
let metrics_starting_balance = config
.strategy_allocated_capital_usdc()
.unwrap_or(simulation_balance);
let exchange_starting_balance = if simulation_balance > metrics_starting_balance {
simulation_balance
} else {
metrics_starting_balance
};
web_sys::console::log_1(
&format!(
"[WASM Runner] Metrics capital: {} USDC, exchange balance: {} USDC",
metrics_starting_balance, exchange_starting_balance
)
.into(),
);
let mut initial_balances = HashMap::new();
initial_balances.insert(AssetId::new("USDC"), exchange_starting_balance);
let paper = Arc::new(create_standalone_paper_exchange_with_id(
initial_balances,
"hyperliquid",
environment,
));
let fee_rate = Decimal::from_str(&sim_config.fee_rate).unwrap_or(dec!(0.00025));
paper.set_fee_rate(fee_rate).await;
let spread = dec!(1.0); let quotes: Vec<Quote> = prices
.iter()
.filter_map(|p| {
let price = Decimal::from_str(&p.price).ok()?;
let half_spread = spread / dec!(2);
Some(Quote {
instrument: instrument_id.clone(),
bid: Price::new(price - half_spread),
ask: Price::new(price + half_spread),
bid_size: Qty::new(dec!(1000)),
ask_size: Qty::new(dec!(1000)),
ts: p.ts_ms,
})
})
.collect();
web_sys::console::log_1(
&format!("[WASM Runner] Queueing {} quotes...", quotes.len()).into(),
);
paper.queue_quotes(quotes).await;
web_sys::console::log_1(&"[WASM Runner] Quotes queued".into());
let exchange = paper.clone() as Arc<dyn Exchange>;
web_sys::console::log_1(&"[WASM Runner] Creating engine...".into());
let mut engine = Engine::new(EngineConfig::default());
engine.register_exchange(exchange.clone());
engine.register_instrument(instrument_meta.clone());
engine.register_strategy(strategy);
web_sys::console::log_1(&"[WASM Runner] Creating runner...".into());
let runner_config = RunnerConfig {
min_poll_delay_ms: 0, cleanup_delay_ms: 0, metrics_mode: "backtest".to_string(),
metrics_starting_balance_usdc: Some(metrics_starting_balance),
..RunnerConfig::default()
};
let mut runner = EngineRunner::new(engine, runner_config);
runner.add_exchange(exchange);
runner.add_instrument(instrument_id.clone());
web_sys::console::log_1(&"[WASM Runner] Starting runner.run()...".into());
runner.run().await;
web_sys::console::log_1(&"[WASM Runner] runner.run() completed".into());
let results = runner.get_backtest_results(&instrument_id);
let result_json = serde_json::to_string(&results)
.map_err(|e| JsValue::from_str(&format!("Failed to serialize result: {}", e)))?;
Ok(JsValue::from_str(&result_json))
})
}