neural_trader/lib.rs
1//! # Neural Trader - Node.js FFI Bindings
2//!
3//! This crate provides high-performance Node.js bindings using napi-rs for the Neural Trader
4//! Rust core. It exposes async APIs that map directly to JavaScript Promises.
5//!
6//! ## Architecture
7//!
8//! ```text
9//! JavaScript (Node.js)
10//! ↓
11//! napi-rs (auto-generated)
12//! ↓
13//! This crate (manual wrappers)
14//! ↓
15//! Rust core (nt-core, nt-strategies, etc.)
16//! ```
17//!
18//! ## Features
19//!
20//! - Zero-copy buffer operations for large datasets
21//! - Async/await with automatic Promise conversion
22//! - Type-safe error marshaling
23//! - Resource management with Arc for shared state
24//! - Thread-safe concurrent operations
25//!
26//! ## Modules
27//!
28//! - `broker` - Broker integrations (Alpaca, IBKR, CCXT, Oanda, Questrade, Lime)
29//! - `strategy` - Trading strategy implementations
30//! - `neural` - Neural network models for forecasting
31//! - `risk` - Risk management (VaR, Kelly, drawdown)
32//! - `backtest` - Backtesting engine
33//! - `market_data` - Market data streaming and indicators
34//! - `portfolio` - Portfolio management
35
36use napi::bindgen_prelude::*;
37use napi_derive::napi;
38use std::sync::Arc;
39
40// Re-export core types with explicit paths to avoid ambiguity
41use nt_core::prelude::*;
42
43// Module declarations
44pub mod broker;
45pub mod neural;
46pub mod risk;
47pub mod backtest;
48pub mod market_data;
49
50// Re-export existing modules
51pub mod strategy;
52pub mod portfolio;
53
54// Type aliases for clarity
55type NapiResult<T> = napi::Result<T>;
56
57// =============================================================================
58// JavaScript Type Wrappers
59// =============================================================================
60
61/// JavaScript-compatible bar data
62#[napi(object)]
63pub struct JsBar {
64 pub symbol: String,
65 pub timestamp: String,
66 pub open: String,
67 pub high: String,
68 pub low: String,
69 pub close: String,
70 pub volume: String,
71}
72
73impl From<Bar> for JsBar {
74 fn from(bar: Bar) -> Self {
75 Self {
76 symbol: bar.symbol.to_string(),
77 timestamp: bar.timestamp.to_rfc3339(),
78 open: bar.open.to_string(),
79 high: bar.high.to_string(),
80 low: bar.low.to_string(),
81 close: bar.close.to_string(),
82 volume: bar.volume.to_string(),
83 }
84 }
85}
86
87/// JavaScript-compatible signal data
88#[napi(object)]
89pub struct JsSignal {
90 pub id: String,
91 pub strategy_id: String,
92 pub symbol: String,
93 pub direction: String,
94 pub confidence: f64,
95 pub entry_price: Option<String>,
96 pub stop_loss: Option<String>,
97 pub take_profit: Option<String>,
98 pub quantity: Option<String>,
99 pub reasoning: String,
100 pub timestamp: String,
101}
102
103impl From<Signal> for JsSignal {
104 fn from(signal: Signal) -> Self {
105 Self {
106 id: signal.id.to_string(),
107 strategy_id: signal.strategy_id,
108 symbol: signal.symbol.to_string(),
109 direction: signal.direction.to_string(),
110 confidence: signal.confidence,
111 entry_price: signal.entry_price.map(|p| p.to_string()),
112 stop_loss: signal.stop_loss.map(|p| p.to_string()),
113 take_profit: signal.take_profit.map(|p| p.to_string()),
114 quantity: signal.quantity.map(|q| q.to_string()),
115 reasoning: signal.reasoning,
116 timestamp: signal.timestamp.to_rfc3339(),
117 }
118 }
119}
120
121/// JavaScript-compatible order data
122#[napi(object)]
123pub struct JsOrder {
124 pub id: String,
125 pub symbol: String,
126 pub side: String,
127 pub order_type: String,
128 pub quantity: String,
129 pub limit_price: Option<String>,
130 pub stop_price: Option<String>,
131 pub time_in_force: String,
132}
133
134/// JavaScript-compatible position data
135#[napi(object)]
136pub struct JsPosition {
137 pub symbol: String,
138 pub quantity: String,
139 pub avg_entry_price: String,
140 pub current_price: String,
141 pub unrealized_pnl: String,
142 pub side: String,
143 pub market_value: String,
144}
145
146impl From<Position> for JsPosition {
147 fn from(pos: Position) -> Self {
148 Self {
149 symbol: pos.symbol.to_string(),
150 quantity: pos.quantity.to_string(),
151 avg_entry_price: pos.avg_entry_price.to_string(),
152 current_price: pos.current_price.to_string(),
153 unrealized_pnl: pos.unrealized_pnl.to_string(),
154 side: pos.side.to_string(),
155 market_value: pos.market_value().to_string(),
156 }
157 }
158}
159
160/// JavaScript-compatible configuration
161#[napi(object)]
162pub struct JsConfig {
163 pub api_key: Option<String>,
164 pub api_secret: Option<String>,
165 pub base_url: Option<String>,
166 pub paper_trading: bool,
167}
168
169// =============================================================================
170// Error Conversion Helper
171// =============================================================================
172
173/// Convert TradingError to napi Error
174fn to_napi_error(err: TradingError) -> Error {
175 match err {
176 TradingError::MarketData { message, .. } => {
177 Error::from_reason(format!("Market data error: {}", message))
178 }
179 TradingError::Strategy {
180 strategy_id,
181 message,
182 ..
183 } => Error::from_reason(format!("Strategy error ({}): {}", strategy_id, message)),
184 TradingError::Execution {
185 message, order_id, ..
186 } => {
187 let msg = if let Some(id) = order_id {
188 format!("Execution error (order {}): {}", id, message)
189 } else {
190 format!("Execution error: {}", message)
191 };
192 Error::from_reason(msg)
193 }
194 TradingError::RiskLimit {
195 message,
196 violation_type,
197 } => Error::from_reason(format!("Risk violation ({:?}): {}", violation_type, message)),
198 TradingError::Validation { message } => {
199 Error::from_reason(format!("Validation error: {}", message))
200 }
201 TradingError::NotFound {
202 resource_type,
203 resource_id,
204 } => Error::from_reason(format!("Not found: {} '{}'", resource_type, resource_id)),
205 TradingError::Timeout {
206 operation,
207 timeout_ms,
208 } => Error::from_reason(format!(
209 "Operation '{}' timed out after {}ms",
210 operation, timeout_ms
211 )),
212 _ => Error::from_reason(err.to_string()),
213 }
214}
215
216// =============================================================================
217// Main Trading System Interface
218// =============================================================================
219
220/// Main Neural Trader system instance
221///
222/// This is the primary interface for interacting with the trading system from Node.js.
223/// It manages strategies, execution, and portfolio state.
224///
225/// # Example
226///
227/// ```javascript
228/// const { NeuralTrader } = require('@neural-trader/rust-core');
229///
230/// const trader = new NeuralTrader({
231/// apiKey: process.env.ALPACA_API_KEY,
232/// apiSecret: process.env.ALPACA_API_SECRET,
233/// paperTrading: true
234/// });
235///
236/// await trader.start();
237/// const positions = await trader.getPositions();
238/// await trader.stop();
239/// ```
240#[napi]
241pub struct NeuralTrader {
242 // Inner state wrapped in Arc for shared ownership across async operations
243 // This will be populated with actual trading system implementation
244 _config: Arc<JsConfig>,
245}
246
247#[napi]
248impl NeuralTrader {
249 /// Create a new Neural Trader instance
250 ///
251 /// # Arguments
252 ///
253 /// * `config` - Configuration object with API credentials and settings
254 #[napi(constructor)]
255 pub fn new(config: JsConfig) -> Self {
256 // TODO: Initialize actual trading system
257 // For now, just validate and store config
258 Self {
259 _config: Arc::new(config),
260 }
261 }
262
263 /// Start the trading system
264 ///
265 /// Initializes connections to market data providers and brokers.
266 /// Returns a Promise that resolves when the system is ready.
267 #[napi]
268 pub async fn start(&self) -> NapiResult<()> {
269 // TODO: Implement actual start logic
270 // - Connect to market data
271 // - Initialize strategies
272 // - Start event loop
273 Ok(())
274 }
275
276 /// Stop the trading system gracefully
277 ///
278 /// Closes all positions, cancels open orders, and disconnects from services.
279 #[napi]
280 pub async fn stop(&self) -> NapiResult<()> {
281 // TODO: Implement actual stop logic
282 // - Cancel open orders
283 // - Close positions (if configured)
284 // - Disconnect from services
285 Ok(())
286 }
287
288 /// Get current portfolio positions
289 ///
290 /// Returns all open positions with real-time P&L.
291 #[napi]
292 pub async fn get_positions(&self) -> NapiResult<Vec<JsPosition>> {
293 // TODO: Implement actual position retrieval
294 Ok(vec![])
295 }
296
297 /// Place a new order
298 ///
299 /// # Arguments
300 ///
301 /// * `order` - Order details (symbol, side, quantity, etc.)
302 ///
303 /// # Returns
304 ///
305 /// Broker's order ID as a string
306 #[napi]
307 pub async fn place_order(&self, _order: JsOrder) -> NapiResult<String> {
308 // TODO: Implement actual order placement
309 // - Validate order
310 // - Check risk limits
311 // - Submit to broker
312 Ok("order-id-placeholder".to_string())
313 }
314
315 /// Get current account balance
316 ///
317 /// Returns cash balance in account currency
318 #[napi]
319 pub async fn get_balance(&self) -> NapiResult<String> {
320 // TODO: Implement actual balance retrieval
321 Ok("0.00".to_string())
322 }
323
324 /// Get current portfolio equity (cash + positions)
325 #[napi]
326 pub async fn get_equity(&self) -> NapiResult<String> {
327 // TODO: Implement actual equity calculation
328 Ok("0.00".to_string())
329 }
330}
331
332// =============================================================================
333// Standalone Utility Functions
334// =============================================================================
335
336/// Fetch historical market data
337///
338/// # Arguments
339///
340/// * `symbol` - Trading symbol (e.g., "AAPL")
341/// * `start` - Start timestamp (RFC3339 format)
342/// * `end` - End timestamp (RFC3339 format)
343/// * `timeframe` - Bar timeframe (e.g., "1Min", "1Hour", "1Day")
344///
345/// # Returns
346///
347/// Array of bar objects with OHLCV data
348///
349/// # Example
350///
351/// ```javascript
352/// const bars = await fetchMarketData('AAPL', '2024-01-01T00:00:00Z', '2024-01-31T23:59:59Z', '1Day');
353/// console.log(`Fetched ${bars.length} bars`);
354/// ```
355#[napi]
356pub async fn fetch_market_data(
357 _symbol: String,
358 _start: String,
359 _end: String,
360 _timeframe: String,
361) -> NapiResult<Vec<JsBar>> {
362 // TODO: Implement actual market data fetching
363 // - Parse timestamps
364 // - Connect to data provider
365 // - Fetch and convert bars
366 Ok(vec![])
367}
368
369/// Calculate technical indicators
370///
371/// # Arguments
372///
373/// * `bars` - Array of bar data
374/// * `indicator` - Indicator name (e.g., "SMA", "RSI", "MACD")
375/// * `params` - JSON string with indicator parameters
376///
377/// # Returns
378///
379/// Array of indicator values (same length as input bars, with NaN for warmup period)
380#[napi]
381pub async fn calculate_indicator(
382 _bars: Vec<JsBar>,
383 _indicator: String,
384 _params: String,
385) -> NapiResult<Vec<f64>> {
386 // TODO: Implement indicator calculation
387 // - Parse parameters
388 // - Calculate indicator
389 // - Return values
390 Ok(vec![])
391}
392
393/// Encode bars to MessagePack buffer for efficient transfer
394///
395/// For large datasets (>1000 bars), encoding to binary format provides
396/// significant performance improvement over JSON serialization.
397///
398/// # Example
399///
400/// ```javascript
401/// const buffer = encodeBarsToBuffer(bars);
402/// // Transfer buffer over network
403/// const decodedBars = decodeBarsFromBuffer(buffer);
404/// ```
405#[napi]
406pub fn encode_bars_to_buffer(_bars: Vec<JsBar>) -> NapiResult<Buffer> {
407 // TODO: Implement MessagePack encoding
408 // - Serialize to MessagePack
409 // - Return as Buffer
410 Ok(Buffer::from(vec![]))
411}
412
413/// Decode bars from MessagePack buffer
414///
415/// Companion function to `encodeBarsToBuffer` for efficient binary transfer.
416#[napi]
417pub fn decode_bars_from_buffer(_buffer: Buffer) -> NapiResult<Vec<JsBar>> {
418 // TODO: Implement MessagePack decoding
419 // - Deserialize from MessagePack
420 // - Convert to JsBar array
421 Ok(vec![])
422}
423
424/// Initialize tokio runtime with custom thread count
425///
426/// Call this once at application startup to configure the async runtime.
427/// If not called, defaults to number of CPU cores.
428///
429/// # Arguments
430///
431/// * `num_threads` - Number of worker threads (None = auto-detect)
432#[napi]
433pub fn init_runtime(num_threads: Option<u32>) -> NapiResult<()> {
434 let threads = num_threads.unwrap_or_else(|| num_cpus::get() as u32);
435 tracing::info!("Initializing tokio runtime with {} threads", threads);
436 // Note: In practice, napi-rs manages the runtime automatically
437 // This is here for documentation and future customization
438 Ok(())
439}
440
441/// Get version information
442///
443/// Returns version strings for all components
444#[napi(object)]
445pub struct VersionInfo {
446 pub rust_core: String,
447 pub napi_bindings: String,
448 pub rust_compiler: String,
449}
450
451#[napi]
452pub fn get_version_info() -> NapiResult<VersionInfo> {
453 Ok(VersionInfo {
454 rust_core: env!("CARGO_PKG_VERSION").to_string(),
455 napi_bindings: env!("CARGO_PKG_VERSION").to_string(),
456 rust_compiler: rustc_version_runtime::version().to_string(),
457 })
458}
459
460// =============================================================================
461// Module Registration
462// =============================================================================
463
464// Module registration happens automatically via napi-rs macros.
465// The generated `index.js` and `index.d.ts` will export all `#[napi]` items.