neural_trader/
portfolio.rs1use napi::bindgen_prelude::*;
4use napi_derive::napi;
5use std::collections::HashMap;
6use std::sync::Arc;
7use tokio::sync::Mutex;
8
9#[napi(object)]
11#[derive(Clone)]
12pub struct Position {
13 pub symbol: String,
14 pub quantity: f64,
15 pub avg_cost: f64,
16 pub market_value: f64,
17 pub unrealized_pnl: f64,
18 pub realized_pnl: f64,
19}
20
21#[napi(object)]
23pub struct PortfolioOptimization {
24 pub allocations: HashMap<String, f64>,
25 pub expected_return: f64,
26 pub risk: f64,
27 pub sharpe_ratio: f64,
28}
29
30#[napi(object)]
32pub struct RiskMetrics {
33 pub var_95: f64, pub cvar_95: f64, pub beta: f64,
36 pub sharpe_ratio: f64,
37 pub max_drawdown: f64,
38}
39
40#[napi(object)]
42pub struct OptimizerConfig {
43 pub risk_free_rate: f64,
44 pub max_position_size: Option<f64>,
45 pub min_position_size: Option<f64>,
46}
47
48#[napi]
50pub struct PortfolioOptimizer {
51 config: OptimizerConfig,
52}
53
54#[napi]
55impl PortfolioOptimizer {
56 #[napi(constructor)]
58 pub fn new(config: OptimizerConfig) -> Self {
59 tracing::info!("Creating portfolio optimizer with risk-free rate: {}", config.risk_free_rate);
60 Self { config }
61 }
62
63 #[napi]
65 pub async fn optimize(
66 &self,
67 symbols: Vec<String>,
68 returns: Vec<f64>,
69 covariance: Vec<f64>,
70 ) -> Result<PortfolioOptimization> {
71 tracing::info!("Optimizing portfolio for {} symbols", symbols.len());
72
73 let mut allocations = HashMap::new();
80 let equal_weight = 1.0 / symbols.len() as f64;
81
82 for symbol in symbols {
83 allocations.insert(symbol, equal_weight);
84 }
85
86 let avg_return = returns.iter().sum::<f64>() / returns.len() as f64;
87 let avg_risk = (covariance.iter().sum::<f64>() / covariance.len() as f64).sqrt();
88
89 Ok(PortfolioOptimization {
90 allocations,
91 expected_return: avg_return,
92 risk: avg_risk,
93 sharpe_ratio: (avg_return - self.config.risk_free_rate) / avg_risk,
94 })
95 }
96
97 #[napi]
99 pub fn calculate_risk(&self, positions: HashMap<String, f64>) -> Result<RiskMetrics> {
100 tracing::debug!("Calculating risk metrics for {} positions", positions.len());
101
102 Ok(RiskMetrics {
109 var_95: 0.05,
110 cvar_95: 0.07,
111 beta: 1.2,
112 sharpe_ratio: 1.5,
113 max_drawdown: 0.15,
114 })
115 }
116}
117
118#[napi]
120pub struct PortfolioManager {
121 positions: Arc<Mutex<HashMap<String, Position>>>,
122 cash: Arc<Mutex<f64>>,
123}
124
125#[napi]
126impl PortfolioManager {
127 #[napi(constructor)]
129 pub fn new(initial_cash: f64) -> Self {
130 tracing::info!("Creating portfolio manager with initial cash: ${}", initial_cash);
131
132 Self {
133 positions: Arc::new(Mutex::new(HashMap::new())),
134 cash: Arc::new(Mutex::new(initial_cash)),
135 }
136 }
137
138 #[napi]
140 pub async fn get_positions(&self) -> Result<Vec<Position>> {
141 let positions = self.positions.lock().await;
142 Ok(positions.values().cloned().collect())
143 }
144
145 #[napi]
147 pub async fn get_position(&self, symbol: String) -> Result<Option<Position>> {
148 let positions = self.positions.lock().await;
149 Ok(positions.get(&symbol).cloned())
150 }
151
152 #[napi]
154 pub async fn update_position(
155 &self,
156 symbol: String,
157 quantity: f64,
158 price: f64,
159 ) -> Result<Position> {
160 let mut positions = self.positions.lock().await;
161 let mut cash = self.cash.lock().await;
162
163 let position = positions.entry(symbol.clone()).or_insert(Position {
164 symbol: symbol.clone(),
165 quantity: 0.0,
166 avg_cost: 0.0,
167 market_value: 0.0,
168 unrealized_pnl: 0.0,
169 realized_pnl: 0.0,
170 });
171
172 let old_quantity = position.quantity;
174 let old_avg_cost = position.avg_cost;
175
176 position.quantity += quantity;
177
178 if position.quantity != 0.0 {
179 position.avg_cost = ((old_quantity * old_avg_cost) + (quantity * price))
180 / position.quantity;
181 }
182
183 position.market_value = position.quantity * price;
184 position.unrealized_pnl = (price - position.avg_cost) * position.quantity;
185
186 *cash -= quantity * price;
188
189 tracing::info!(
190 "Updated position: {} shares of {} @ ${} (avg cost: ${})",
191 position.quantity, symbol, price, position.avg_cost
192 );
193
194 Ok(position.clone())
195 }
196
197 #[napi]
199 pub async fn get_cash(&self) -> Result<f64> {
200 Ok(*self.cash.lock().await)
201 }
202
203 #[napi]
205 pub async fn get_total_value(&self) -> Result<f64> {
206 let positions = self.positions.lock().await;
207 let cash = *self.cash.lock().await;
208
209 let positions_value: f64 = positions.values()
210 .map(|p| p.market_value)
211 .sum();
212
213 Ok(cash + positions_value)
214 }
215
216 #[napi]
218 pub async fn get_total_pnl(&self) -> Result<f64> {
219 let positions = self.positions.lock().await;
220 Ok(positions.values().map(|p| p.unrealized_pnl).sum())
221 }
222}