1use napi::bindgen_prelude::*;
12use napi_derive::napi;
13use std::sync::Arc;
14use tokio::sync::Mutex;
15
16#[napi(object)]
18pub struct BrokerConfig {
19 pub broker_type: String, pub api_key: String,
21 pub api_secret: String,
22 pub base_url: Option<String>,
23 pub paper_trading: bool,
24 pub exchange: Option<String>, }
26
27#[napi(object)]
29pub struct OrderRequest {
30 pub symbol: String,
31 pub side: String, pub order_type: String, pub quantity: f64,
34 pub limit_price: Option<f64>,
35 pub stop_price: Option<f64>,
36 pub time_in_force: String, }
38
39#[napi(object)]
41pub struct OrderResponse {
42 pub order_id: String,
43 pub broker_order_id: String,
44 pub status: String, pub filled_quantity: f64,
46 pub filled_price: Option<f64>,
47 pub timestamp: String,
48}
49
50#[napi(object)]
52pub struct AccountBalance {
53 pub cash: f64,
54 pub equity: f64,
55 pub buying_power: f64,
56 pub currency: String,
57}
58
59#[napi]
61pub struct BrokerClient {
62 config: Arc<BrokerConfig>,
63 _connection: Arc<Mutex<Option<String>>>, }
65
66#[napi]
67impl BrokerClient {
68 #[napi(constructor)]
70 pub fn new(config: BrokerConfig) -> Self {
71 tracing::info!("Creating broker client for: {}", config.broker_type);
72
73 Self {
74 config: Arc::new(config),
75 _connection: Arc::new(Mutex::new(None)),
76 }
77 }
78
79 #[napi]
81 pub async fn connect(&self) -> Result<bool> {
82 let broker_type = &self.config.broker_type;
83 tracing::info!("Connecting to broker: {}", broker_type);
84
85 let mut conn = self._connection.lock().await;
88 *conn = Some(format!("connected-{}", broker_type));
89
90 Ok(true)
91 }
92
93 #[napi]
95 pub async fn disconnect(&self) -> Result<()> {
96 tracing::info!("Disconnecting from broker");
97
98 let mut conn = self._connection.lock().await;
99 *conn = None;
100
101 Ok(())
102 }
103
104 #[napi]
106 pub async fn place_order(&self, order: OrderRequest) -> Result<OrderResponse> {
107 tracing::info!(
108 "Placing order: {} {} {} @ {}",
109 order.side,
110 order.quantity,
111 order.symbol,
112 order.order_type
113 );
114
115 Ok(OrderResponse {
118 order_id: generate_uuid(),
119 broker_order_id: format!("broker-{}", generate_uuid()),
120 status: "pending".to_string(),
121 filled_quantity: 0.0,
122 filled_price: None,
123 timestamp: chrono::Utc::now().to_rfc3339(),
124 })
125 }
126
127 #[napi]
129 pub async fn cancel_order(&self, order_id: String) -> Result<bool> {
130 tracing::info!("Cancelling order: {}", order_id);
131
132 Ok(true)
134 }
135
136 #[napi]
138 pub async fn get_order_status(&self, order_id: String) -> Result<OrderResponse> {
139 tracing::debug!("Getting order status: {}", order_id);
140
141 Ok(OrderResponse {
143 order_id: order_id.clone(),
144 broker_order_id: format!("broker-{}", order_id),
145 status: "filled".to_string(),
146 filled_quantity: 100.0,
147 filled_price: Some(150.50),
148 timestamp: chrono::Utc::now().to_rfc3339(),
149 })
150 }
151
152 #[napi]
154 pub async fn get_account_balance(&self) -> Result<AccountBalance> {
155 tracing::debug!("Getting account balance");
156
157 Ok(AccountBalance {
159 cash: if self.config.paper_trading { 100000.0 } else { 0.0 },
160 equity: if self.config.paper_trading { 100000.0 } else { 0.0 },
161 buying_power: if self.config.paper_trading { 200000.0 } else { 0.0 },
162 currency: "USD".to_string(),
163 })
164 }
165
166 #[napi]
168 pub async fn list_orders(&self) -> Result<Vec<OrderResponse>> {
169 tracing::debug!("Listing open orders");
170
171 Ok(vec![])
173 }
174
175 #[napi]
177 pub async fn get_positions(&self) -> Result<Vec<crate::JsPosition>> {
178 tracing::debug!("Getting positions");
179
180 Ok(vec![])
182 }
183}
184
185#[napi]
187pub fn list_broker_types() -> Vec<String> {
188 vec![
189 "alpaca".to_string(),
190 "ibkr".to_string(),
191 "ccxt".to_string(),
192 "oanda".to_string(),
193 "questrade".to_string(),
194 "lime".to_string(),
195 ]
196}
197
198#[napi]
200pub fn validate_broker_config(config: BrokerConfig) -> Result<bool> {
201 let valid_types = list_broker_types();
202
203 if !valid_types.contains(&config.broker_type) {
204 return Err(Error::from_reason(format!(
205 "Invalid broker type: {}. Valid types: {:?}",
206 config.broker_type, valid_types
207 )));
208 }
209
210 if config.api_key.is_empty() {
211 return Err(Error::from_reason("API key is required"));
212 }
213
214 if config.api_secret.is_empty() {
215 return Err(Error::from_reason("API secret is required"));
216 }
217
218 if config.broker_type == "ccxt" && config.exchange.is_none() {
219 return Err(Error::from_reason("Exchange is required for CCXT broker"));
220 }
221
222 Ok(true)
223}
224
225fn generate_uuid() -> String {
227 use std::time::{SystemTime, UNIX_EPOCH};
228 let nanos = SystemTime::now()
229 .duration_since(UNIX_EPOCH)
230 .unwrap()
231 .as_nanos();
232 format!("{:x}", nanos)
233}