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.