stock-bot/apps/stock/core/src/api/mod.rs

326 lines
No EOL
11 KiB
Rust

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<Mutex<TradingCore>>,
}
#[napi]
impl TradingEngine {
#[napi(constructor)]
pub fn new(mode: String, config: JsObject) -> Result<Self> {
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<String> {
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<String> {
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<String> {
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<Vec<f64>> {
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<Option<String>> {
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<String> {
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<String> {
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<Vec<f64>> {
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<String> {
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<String> {
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(&microstructure_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<MarketUpdate> = 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<TradingMode> {
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::<Utc>::from_timestamp_millis(start_time)
.ok_or_else(|| Error::from_reason("Invalid start time"))?,
end_time: DateTime::<Utc>::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<Order> {
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<RiskLimits> {
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")?,
})
}