use napi_derive::napi; use napi::{bindgen_prelude::*, JsObject}; use crate::{ TradingCore, TradingMode, Order, OrderType, TimeInForce, Side, MarketUpdate, Quote, Trade, MarketMicrostructure, core::{create_market_data_source, create_execution_handler, create_time_provider}, }; use crate::risk::RiskLimits; use std::sync::Arc; use parking_lot::Mutex; use chrono::{DateTime, Utc}; #[napi] pub struct TradingEngine { core: Arc>, } #[napi] impl TradingEngine { #[napi(constructor)] pub fn new(mode: String, config: JsObject) -> Result { let mode = parse_mode(&mode, config)?; let market_data_source = create_market_data_source(&mode); let execution_handler = create_execution_handler(&mode); let time_provider = create_time_provider(&mode); let core = TradingCore::new(mode, market_data_source, execution_handler, time_provider); Ok(Self { core: Arc::new(Mutex::new(core)), }) } #[napi] pub fn get_mode(&self) -> String { let core = self.core.lock(); match core.get_mode() { TradingMode::Backtest { .. } => "backtest".to_string(), TradingMode::Paper { .. } => "paper".to_string(), TradingMode::Live { .. } => "live".to_string(), } } #[napi] pub fn get_current_time(&self) -> i64 { let core = self.core.lock(); core.get_time().timestamp_millis() } #[napi] pub fn submit_order(&self, order_js: JsObject) -> Result { let order = parse_order(order_js)?; // For now, return a mock result - in real implementation would queue the order let result = crate::ExecutionResult { order_id: order.id.clone(), status: crate::OrderStatus::Accepted, fills: vec![], }; Ok(serde_json::to_string(&result).unwrap()) } #[napi] pub fn check_risk(&self, order_js: JsObject) -> Result { let order = parse_order(order_js)?; let core = self.core.lock(); // Get current position for the symbol let position = core.position_tracker.get_position(&order.symbol); let current_quantity = position.map(|p| p.quantity); let result = core.risk_engine.check_order(&order, current_quantity); Ok(serde_json::to_string(&result).unwrap()) } #[napi] pub fn update_quote(&self, symbol: String, bid: f64, ask: f64, bid_size: f64, ask_size: f64) -> Result<()> { let quote = Quote { bid, ask, bid_size, ask_size }; let core = self.core.lock(); let timestamp = core.get_time(); core.orderbooks.update_quote(&symbol, quote, timestamp); // Update unrealized P&L let mid_price = (bid + ask) / 2.0; core.position_tracker.update_unrealized_pnl(&symbol, mid_price); Ok(()) } #[napi] pub fn update_trade(&self, symbol: String, price: f64, size: f64, side: String) -> Result<()> { let side = match side.as_str() { "buy" | "Buy" => Side::Buy, "sell" | "Sell" => Side::Sell, _ => return Err(Error::from_reason("Invalid side")), }; let trade = Trade { price, size, side }; let core = self.core.lock(); let timestamp = core.get_time(); core.orderbooks.update_trade(&symbol, trade, timestamp); Ok(()) } #[napi] pub fn get_orderbook_snapshot(&self, symbol: String, depth: u32) -> Result { let core = self.core.lock(); let snapshot = core.orderbooks.get_snapshot(&symbol, depth as usize) .ok_or_else(|| Error::from_reason("Symbol not found"))?; Ok(serde_json::to_string(&snapshot).unwrap()) } #[napi] pub fn get_best_bid_ask(&self, symbol: String) -> Result> { let core = self.core.lock(); let (bid, ask) = core.orderbooks.get_best_bid_ask(&symbol) .ok_or_else(|| Error::from_reason("Symbol not found"))?; Ok(vec![bid, ask]) } #[napi] pub fn get_position(&self, symbol: String) -> Result> { let core = self.core.lock(); let position = core.position_tracker.get_position(&symbol); Ok(position.map(|p| serde_json::to_string(&p).unwrap())) } #[napi] pub fn get_all_positions(&self) -> Result { let core = self.core.lock(); let positions = core.position_tracker.get_all_positions(); Ok(serde_json::to_string(&positions).unwrap()) } #[napi] pub fn get_open_positions(&self) -> Result { let core = self.core.lock(); let positions = core.position_tracker.get_open_positions(); Ok(serde_json::to_string(&positions).unwrap()) } #[napi] pub fn get_total_pnl(&self) -> Result> { let core = self.core.lock(); let (realized, unrealized) = core.position_tracker.get_total_pnl(); Ok(vec![realized, unrealized]) } #[napi] pub fn process_fill(&self, symbol: String, price: f64, quantity: f64, side: String, commission: f64) -> Result { let side = match side.as_str() { "buy" | "Buy" => Side::Buy, "sell" | "Sell" => Side::Sell, _ => return Err(Error::from_reason("Invalid side")), }; let core = self.core.lock(); let timestamp = core.get_time(); let fill = crate::Fill { timestamp, price, quantity, commission, }; let update = core.position_tracker.process_fill(&symbol, &fill, side); // Update risk engine with new position core.risk_engine.update_position(&symbol, update.resulting_position.quantity); // Update daily P&L if update.resulting_position.realized_pnl != 0.0 { core.risk_engine.update_daily_pnl(update.resulting_position.realized_pnl); } Ok(serde_json::to_string(&update).unwrap()) } #[napi] pub fn update_risk_limits(&self, limits_js: JsObject) -> Result<()> { let limits = parse_risk_limits(limits_js)?; let core = self.core.lock(); core.risk_engine.update_limits(limits); Ok(()) } #[napi] pub fn reset_daily_metrics(&self) -> Result<()> { let core = self.core.lock(); core.risk_engine.reset_daily_metrics(); Ok(()) } #[napi] pub fn get_risk_metrics(&self) -> Result { let core = self.core.lock(); let metrics = core.risk_engine.get_risk_metrics(); Ok(serde_json::to_string(&metrics).unwrap()) } // Backtest-specific methods #[napi] pub fn advance_time(&self, _to_timestamp: i64) -> Result<()> { let core = self.core.lock(); if let TradingMode::Backtest { .. } = core.get_mode() { // In real implementation, would downcast and advance time // For now, return success in backtest mode Ok(()) } else { Err(Error::from_reason("Can only advance time in backtest mode")) } } #[napi] pub fn set_microstructure(&self, _symbol: String, microstructure_json: String) -> Result<()> { let _microstructure: MarketMicrostructure = serde_json::from_str(µstructure_json) .map_err(|e| Error::from_reason(format!("Failed to parse microstructure: {}", e)))?; let _core = self.core.lock(); // Store microstructure for use in fill simulation // In real implementation, would pass to execution handler Ok(()) } #[napi] pub fn load_historical_data(&self, data_json: String) -> Result<()> { let _data: Vec = serde_json::from_str(&data_json) .map_err(|e| Error::from_reason(format!("Failed to parse data: {}", e)))?; // In real implementation, would load into historical data source Ok(()) } } // Helper functions to parse JavaScript objects fn parse_mode(mode_str: &str, config: JsObject) -> Result { match mode_str { "backtest" => { let start_time: i64 = config.get_named_property("startTime")?; let end_time: i64 = config.get_named_property("endTime")?; let speed_multiplier: f64 = config.get_named_property("speedMultiplier") .unwrap_or(1.0); Ok(TradingMode::Backtest { start_time: DateTime::::from_timestamp_millis(start_time) .ok_or_else(|| Error::from_reason("Invalid start time"))?, end_time: DateTime::::from_timestamp_millis(end_time) .ok_or_else(|| Error::from_reason("Invalid end time"))?, speed_multiplier, }) } "paper" => { let starting_capital: f64 = config.get_named_property("startingCapital")?; Ok(TradingMode::Paper { starting_capital }) } "live" => { let broker: String = config.get_named_property("broker")?; let account_id: String = config.get_named_property("accountId")?; Ok(TradingMode::Live { broker, account_id }) } _ => Err(Error::from_reason("Invalid mode")), } } fn parse_order(order_js: JsObject) -> Result { let id: String = order_js.get_named_property("id")?; let symbol: String = order_js.get_named_property("symbol")?; let side_str: String = order_js.get_named_property("side")?; let side = match side_str.as_str() { "buy" | "Buy" => Side::Buy, "sell" | "Sell" => Side::Sell, _ => return Err(Error::from_reason("Invalid side")), }; let quantity: f64 = order_js.get_named_property("quantity")?; let order_type_str: String = order_js.get_named_property("orderType")?; let order_type = match order_type_str.as_str() { "market" => OrderType::Market, "limit" => { let price: f64 = order_js.get_named_property("limitPrice")?; OrderType::Limit { price } } _ => return Err(Error::from_reason("Invalid order type")), }; let time_in_force_str: String = order_js.get_named_property("timeInForce") .unwrap_or_else(|_| "DAY".to_string()); let time_in_force = match time_in_force_str.as_str() { "DAY" => TimeInForce::Day, "GTC" => TimeInForce::GTC, "IOC" => TimeInForce::IOC, "FOK" => TimeInForce::FOK, _ => TimeInForce::Day, }; Ok(Order { id, symbol, side, quantity, order_type, time_in_force, }) } fn parse_risk_limits(limits_js: JsObject) -> Result { Ok(RiskLimits { max_position_size: limits_js.get_named_property("maxPositionSize")?, max_order_size: limits_js.get_named_property("maxOrderSize")?, max_daily_loss: limits_js.get_named_property("maxDailyLoss")?, max_gross_exposure: limits_js.get_named_property("maxGrossExposure")?, max_symbol_exposure: limits_js.get_named_property("maxSymbolExposure")?, }) }