Skip to main content

qualia_core_db/specialized_libs/financial_modeling/
rebalancing.rs

1use super::*;
2
3/// Rebalancing engine
4pub struct RebalancingEngine {
5    rebalancing_strategies: HashMap<String, RebalancingStrategy>,
6    optimization_engine: OptimizationEngine,
7    execution_engine: ExecutionEngine,
8}
9
10/// Rebalancing strategies
11#[derive(Debug, Clone)]
12pub struct RebalancingStrategy {
13    pub strategy_id: String,
14    pub strategy_name: String,
15    pub strategy_type: RebalancingStrategyType,
16    pub parameters: RebalancingParameters,
17    pub constraints: RebalancingConstraints,
18    /// Target portfolio weights keyed by `asset_id`, summing to ~1.0. Used by
19    /// `RebalancingEngine::rebalance` to compute drift away from the target
20    /// allocation. Assets without an entry are treated as target weight 0.0.
21    pub target_weights: HashMap<String, f64>,
22}
23
24/// A single rebalance trade produced by `RebalancingEngine::rebalance`.
25#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
26pub struct RebalanceTrade {
27    /// The asset to trade.
28    pub asset_id: String,
29    /// Whether to buy or sell.
30    pub action: TradeAction,
31    /// Number of units to trade (always positive; direction is in `action`).
32    pub quantity: f64,
33    /// The target weight this trade moves the asset towards.
34    pub target_weight: f64,
35}
36
37/// Direction of a `RebalanceTrade`.
38#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
39pub enum TradeAction {
40    Buy,
41    Sell,
42}
43
44/// Rebalancing strategy types
45#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
46pub enum RebalancingStrategyType {
47    TimeBased,
48    ThresholdBased,
49    OptimizationBased,
50    Hybrid,
51}
52
53/// Rebalancing parameters
54#[derive(Debug, Clone, Serialize, Deserialize)]
55pub struct RebalancingParameters {
56    pub rebalance_frequency: u32,
57    pub deviation_threshold: f64,
58    pub min_trade_size: f64,
59    pub max_trade_size: f64,
60    pub transaction_costs: TransactionCosts,
61}
62
63/// Transaction costs
64#[derive(Debug, Clone, Serialize, Deserialize)]
65pub struct TransactionCosts {
66    pub commission_rate: f64,
67    pub spread_cost: f64,
68    pub market_impact: f64,
69    pub tax_rate: f64,
70}
71
72/// Rebalancing constraints
73#[derive(Debug, Clone)]
74pub struct RebalancingConstraints {
75    pub asset_class_limits: HashMap<String, f64>,
76    pub sector_limits: HashMap<String, f64>,
77    pub liquidity_constraints: LiquidityConstraints,
78    pub regulatory_constraints: RegulatoryConstraints,
79}
80
81/// Liquidity constraints
82#[derive(Debug, Clone)]
83pub struct LiquidityConstraints {
84    pub max_daily_volume: f64,
85    pub min_liquidity_score: f64,
86    pub liquidity_buffer: f64,
87}
88
89/// Regulatory constraints
90#[derive(Debug, Clone)]
91pub struct RegulatoryConstraints {
92    pub concentration_limits: HashMap<String, f64>,
93    pub reporting_requirements: Vec<String>,
94    pub compliance_deadlines: Vec<u64>,
95}
96
97/// Optimization engine
98pub struct OptimizationEngine {
99    optimization_algorithms: HashMap<String, OptimizationAlgorithm>,
100    objective_functions: HashMap<String, ObjectiveFunction>,
101    constraints: Vec<OptimizationConstraint>,
102}
103
104/// Optimization algorithms
105#[derive(Debug, Clone)]
106pub struct OptimizationAlgorithm {
107    pub algorithm_id: String,
108    pub algorithm_type: OptimizationAlgorithmType,
109    pub parameters: OptimizationParameters,
110}
111
112/// Optimization algorithm types
113#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
114pub enum OptimizationAlgorithmType {
115    MeanVariance,
116    BlackLitterman,
117    RiskParity,
118    EqualWeight,
119    Custom,
120}
121
122/// Optimization parameters
123#[derive(Debug, Clone, Serialize, Deserialize)]
124pub struct OptimizationParameters {
125    pub risk_aversion: f64,
126    pub expected_returns: Vec<f64>,
127    pub covariance_matrix: Vec<Vec<f64>>,
128    pub constraints: Vec<OptimizationConstraint>,
129}
130
131/// Objective functions
132#[derive(Debug, Clone)]
133pub struct ObjectiveFunction {
134    pub function_id: String,
135    pub function_type: ObjectiveFunctionType,
136    pub parameters: HashMap<String, f64>,
137}
138
139/// Objective function types
140#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
141pub enum ObjectiveFunctionType {
142    MaximizeReturn,
143    MinimizeRisk,
144    MaximizeSharpe,
145    MinimizeDrawdown,
146    Custom,
147}
148
149/// Optimization constraints
150#[derive(Debug, Clone, Serialize, Deserialize)]
151pub struct OptimizationConstraint {
152    pub constraint_id: String,
153    pub constraint_type: ConstraintType,
154    pub bounds: ConstraintBounds,
155}
156
157/// Constraint types
158#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
159pub enum ConstraintType {
160    Equality,
161    Inequality,
162    Bound,
163    Linear,
164    Nonlinear,
165}
166
167/// Constraint bounds
168#[derive(Debug, Clone, Serialize, Deserialize)]
169pub struct ConstraintBounds {
170    pub lower_bound: f64,
171    pub upper_bound: f64,
172}
173
174impl RebalancingEngine {
175    pub fn new() -> Self {
176        Self {
177            rebalancing_strategies: HashMap::new(),
178            optimization_engine: OptimizationEngine::new(),
179            execution_engine: ExecutionEngine::new(),
180        }
181    }
182
183    pub fn initialize(&mut self) -> Result<(), FinancialError> {
184        self.optimization_engine.initialize()?;
185        self.execution_engine.initialize()?;
186        Ok(())
187    }
188
189    /// Register a rebalancing strategy, keyed by `strategy.strategy_id`.
190    pub fn register_strategy(&mut self, strategy: RebalancingStrategy) {
191        self.rebalancing_strategies
192            .insert(strategy.strategy_id.clone(), strategy);
193    }
194
195    /// Look up a registered strategy by id.
196    pub fn get_strategy(&self, strategy_id: &str) -> Option<&RebalancingStrategy> {
197        self.rebalancing_strategies.get(strategy_id)
198    }
199
200    /// Compute the current portfolio weights (`market_value / total`) keyed by
201    /// `asset_id`. These are the "drifted" weights that `rebalance` compares
202    /// against a strategy's `target_weights`. Returns an empty map when the
203    /// portfolio has no positive market value.
204    pub fn calculate_drift(portfolio: &Portfolio) -> HashMap<String, f64> {
205        let total: f64 = portfolio.assets.iter().map(|a| a.market_value).sum();
206        let mut weights = HashMap::new();
207        if !(total > 0.0) {
208            return weights;
209        }
210        for asset in &portfolio.assets {
211            weights.insert(asset.asset_id.clone(), asset.market_value / total);
212        }
213        weights
214    }
215
216    /// Compute drift against `strategy.target_weights` and, for any asset whose
217    /// drift exceeds `strategy.parameters.deviation_threshold`, generate a
218    /// `RebalanceTrade` that would move the asset back to its target weight.
219    ///
220    /// Trades are sized in units: `quantity = |target_value − current_value| /
221    /// current_price`, where `target_value = target_weight · total_value`. The
222    /// portfolio is **not** mutated by this method — it only proposes trades;
223    /// applying them (and their costs) is the execution layer's job.
224    pub fn rebalance(
225        &self,
226        portfolio: &mut Portfolio,
227        strategy: &RebalancingStrategy,
228    ) -> Result<Vec<RebalanceTrade>, FinancialError> {
229        let total_value: f64 = portfolio.assets.iter().map(|a| a.market_value).sum();
230        if !(total_value > 0.0) {
231            return Err(FinancialError::PortfolioError(
232                "cannot rebalance: total portfolio market value is not positive".to_string(),
233            ));
234        }
235
236        let current_weights = Self::calculate_drift(portfolio);
237        let threshold = strategy.parameters.deviation_threshold;
238        let mut trades = Vec::new();
239
240        for asset in &portfolio.assets {
241            let current_weight = current_weights.get(&asset.asset_id).copied().unwrap_or(0.0);
242            let target_weight = strategy
243                .target_weights
244                .get(&asset.asset_id)
245                .copied()
246                .unwrap_or(0.0);
247            let drift = current_weight - target_weight;
248
249            if drift.abs() > threshold {
250                if asset.current_price <= 0.0 {
251                    return Err(FinancialError::AssetError(format!(
252                        "asset '{}' has non-positive current price; cannot size a trade",
253                        asset.asset_id
254                    )));
255                }
256                let target_value = target_weight * total_value;
257                let value_diff = target_value - asset.market_value;
258                let quantity = value_diff / asset.current_price;
259                let action = if quantity >= 0.0 {
260                    TradeAction::Buy
261                } else {
262                    TradeAction::Sell
263                };
264                trades.push(RebalanceTrade {
265                    asset_id: asset.asset_id.clone(),
266                    action,
267                    quantity: quantity.abs(),
268                    target_weight,
269                });
270            }
271        }
272
273        Ok(trades)
274    }
275}
276
277impl OptimizationEngine {
278    pub fn new() -> Self {
279        Self {
280            optimization_algorithms: HashMap::new(),
281            objective_functions: HashMap::new(),
282            constraints: Vec::new(),
283        }
284    }
285
286    pub fn initialize(&mut self) -> Result<(), FinancialError> {
287        Ok(())
288    }
289
290    pub fn add_algorithm(&mut self, algorithm: OptimizationAlgorithm) {
291        self.optimization_algorithms
292            .insert(algorithm.algorithm_id.clone(), algorithm);
293    }
294
295    pub fn get_algorithm(&self, algorithm_id: &str) -> Option<&OptimizationAlgorithm> {
296        self.optimization_algorithms.get(algorithm_id)
297    }
298
299    pub fn list_algorithms(&self) -> Vec<String> {
300        self.optimization_algorithms.keys().cloned().collect()
301    }
302
303    pub fn add_objective_function(&mut self, function: ObjectiveFunction) {
304        self.objective_functions
305            .insert(function.function_id.clone(), function);
306    }
307
308    pub fn get_objective_function(&self, function_id: &str) -> Option<&ObjectiveFunction> {
309        self.objective_functions.get(function_id)
310    }
311
312    pub fn list_objective_functions(&self) -> Vec<String> {
313        self.objective_functions.keys().cloned().collect()
314    }
315
316    pub fn add_constraint(&mut self, constraint: OptimizationConstraint) {
317        self.constraints.push(constraint);
318    }
319
320    pub fn list_constraints(&self) -> &[OptimizationConstraint] {
321        &self.constraints
322    }
323}
324
325impl RebalancingStrategy {
326    pub fn new() -> Self {
327        Self {
328            strategy_id: "strategy_1".to_string(),
329            strategy_name: "Monthly rebalancing".to_string(),
330            strategy_type: RebalancingStrategyType::TimeBased,
331            parameters: RebalancingParameters::new(),
332            constraints: RebalancingConstraints::new(),
333            target_weights: HashMap::new(),
334        }
335    }
336}
337
338impl RebalancingParameters {
339    pub fn new() -> Self {
340        Self {
341            rebalance_frequency: 30,   // 30 days
342            deviation_threshold: 0.05, // 5%
343            min_trade_size: 1000.0,
344            max_trade_size: 100000.0,
345            transaction_costs: TransactionCosts::new(),
346        }
347    }
348}
349
350impl TransactionCosts {
351    pub fn new() -> Self {
352        Self {
353            commission_rate: 0.001,
354            spread_cost: 0.0005,
355            market_impact: 0.0002,
356            tax_rate: 0.2,
357        }
358    }
359}
360
361impl RebalancingConstraints {
362    pub fn new() -> Self {
363        Self {
364            asset_class_limits: HashMap::new(),
365            sector_limits: HashMap::new(),
366            liquidity_constraints: LiquidityConstraints::new(),
367            regulatory_constraints: RegulatoryConstraints::new(),
368        }
369    }
370}
371
372impl LiquidityConstraints {
373    pub fn new() -> Self {
374        Self {
375            max_daily_volume: 1000000.0,
376            min_liquidity_score: 0.7,
377            liquidity_buffer: 0.1,
378        }
379    }
380}
381
382impl RegulatoryConstraints {
383    pub fn new() -> Self {
384        Self {
385            concentration_limits: HashMap::new(),
386            reporting_requirements: vec!["Daily report".to_string()],
387            compliance_deadlines: vec![86400], // 1 day
388        }
389    }
390}
391
392impl OptimizationAlgorithm {
393    pub fn new() -> Self {
394        Self {
395            algorithm_id: "algo_1".to_string(),
396            algorithm_type: OptimizationAlgorithmType::MeanVariance,
397            parameters: OptimizationParameters::new(),
398        }
399    }
400}
401
402impl OptimizationParameters {
403    pub fn new() -> Self {
404        Self {
405            risk_aversion: 1.0,
406            expected_returns: vec![0.1, 0.08, 0.12],
407            covariance_matrix: vec![
408                vec![0.04, 0.02, 0.01],
409                vec![0.02, 0.09, 0.03],
410                vec![0.01, 0.03, 0.16],
411            ],
412            constraints: vec![],
413        }
414    }
415}
416
417impl ObjectiveFunction {
418    pub fn new() -> Self {
419        Self {
420            function_id: "obj_1".to_string(),
421            function_type: ObjectiveFunctionType::MaximizeSharpe,
422            parameters: HashMap::new(),
423        }
424    }
425}
426
427impl OptimizationConstraint {
428    pub fn new() -> Self {
429        Self {
430            constraint_id: "constraint_1".to_string(),
431            constraint_type: ConstraintType::Equality,
432            bounds: ConstraintBounds::new(),
433        }
434    }
435}
436
437impl ConstraintBounds {
438    pub fn new() -> Self {
439        Self {
440            lower_bound: 0.0,
441            upper_bound: 1.0,
442        }
443    }
444}