qualia_core_db/specialized_libs/financial_modeling/
pricing.rs1use super::*;
2
3pub struct PricingEngine {
5 pricing_models: HashMap<String, PricingModel>,
6 market_data: MarketData,
7 valuation_engine: ValuationEngine,
8}
9
10#[derive(Debug, Clone)]
12pub struct PricingModel {
13 pub model_id: String,
14 pub model_type: PricingModelType,
15 pub parameters: PricingModelParameters,
16}
17
18#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
20pub enum PricingModelType {
21 BlackScholes,
22 Binomial,
23 MonteCarlo,
24 FiniteDifference,
25 Analytical,
26}
27
28#[derive(Debug, Clone, Serialize, Deserialize)]
30pub struct PricingModelParameters {
31 pub risk_free_rate: f64,
32 pub volatility: f64,
33 pub dividend_yield: f64,
34 pub time_to_maturity: f64,
35}
36
37pub struct ValuationEngine {
39 valuation_methods: HashMap<String, ValuationMethod>,
40 discount_rates: HashMap<String, f64>,
41 cash_flow_projections: HashMap<String, CashFlowProjection>,
42}
43
44#[derive(Debug, Clone)]
46pub struct ValuationMethod {
47 pub method_id: String,
48 pub method_type: ValuationMethodType,
49 pub parameters: ValuationMethodParameters,
50}
51
52#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
54pub enum ValuationMethodType {
55 DCF,
56 DDM,
57 Multiples,
58 AssetBased,
59 OptionPricing,
60}
61
62#[derive(Debug, Clone, Serialize, Deserialize)]
64pub struct ValuationMethodParameters {
65 pub discount_rate: f64,
66 pub growth_rate: f64,
67 pub terminal_growth: f64,
68 pub multiples: HashMap<String, f64>,
69}
70
71#[derive(Debug, Clone)]
73pub struct CashFlowProjection {
74 pub projection_id: String,
75 pub cash_flows: Vec<CashFlow>,
76 pub assumptions: Vec<Assumption>,
77}
78
79#[derive(Debug, Clone)]
81pub struct CashFlow {
82 pub period: u32,
83 pub amount: f64,
84 pub cash_flow_type: CashFlowType,
85}
86
87#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
89pub enum CashFlowType {
90 Operating,
91 Investing,
92 Financing,
93 Free,
94}
95
96#[derive(Debug, Clone)]
98pub struct Assumption {
99 pub assumption_id: String,
100 pub assumption_name: String,
101 pub assumption_value: f64,
102 pub justification: String,
103}
104
105impl PricingEngine {
106 pub fn new() -> Self {
107 Self {
108 pricing_models: HashMap::new(),
109 market_data: MarketData::new(),
110 valuation_engine: ValuationEngine::new(),
111 }
112 }
113
114 pub fn initialize(&mut self) -> Result<(), FinancialError> {
115 self.valuation_engine.initialize()?;
116 Ok(())
117 }
118
119 pub fn validate_option_parameters(
120 &self,
121 params: &OptionParameters,
122 ) -> Result<(), FinancialError> {
123 if params.underlying_price <= 0.0 {
124 return Err(FinancialError::ValidationError(
125 "Underlying price must be positive".to_string(),
126 ));
127 }
128 if params.strike <= 0.0 {
129 return Err(FinancialError::ValidationError(
130 "Strike price must be positive".to_string(),
131 ));
132 }
133 if params.time_to_maturity < 0.0 {
134 return Err(FinancialError::ValidationError(
135 "Time to maturity must be non-negative".to_string(),
136 ));
137 }
138 if params.volatility < 0.0 {
139 return Err(FinancialError::ValidationError(
140 "Volatility must be non-negative".to_string(),
141 ));
142 }
143 Ok(())
144 }
145
146 pub fn price_option(&self, params: &OptionParameters) -> Result<OptionPrice, FinancialError> {
147 let option_price = self.black_scholes_price(params)?;
149 Ok(option_price)
150 }
151
152 fn black_scholes_price(
153 &self,
154 params: &OptionParameters,
155 ) -> Result<OptionPrice, FinancialError> {
156 let s = params.underlying_price;
157 let k = params.strike;
158 let r = params.risk_free_rate;
159 let sigma = params.volatility;
160 let t = params.time_to_maturity;
161
162 if t <= 0.0 {
165 return Ok(self.intrinsic_price(params));
166 }
167
168 if sigma <= 0.0 {
173 let disc = (-r * t).exp();
174 let fwd = s - k * disc;
175 let (price, delta) = match params.option_type {
176 OptionType::Call => (fwd.max(0.0), if fwd > 0.0 { 1.0 } else { 0.0 }),
177 OptionType::Put => ((-fwd).max(0.0), if fwd < 0.0 { -1.0 } else { 0.0 }),
178 };
179 return Ok(OptionPrice {
180 price,
181 delta,
182 gamma: 0.0,
183 theta: 0.0,
184 vega: 0.0,
185 rho: 0.0,
186 });
187 }
188
189 if s <= 0.0 {
192 let disc = (-r * t).exp();
193 return Ok(match params.option_type {
194 OptionType::Call => OptionPrice {
195 price: 0.0,
196 delta: 0.0,
197 gamma: 0.0,
198 theta: 0.0,
199 vega: 0.0,
200 rho: 0.0,
201 },
202 OptionType::Put => OptionPrice {
203 price: k * disc,
204 delta: -1.0,
205 gamma: 0.0,
206 theta: r * k * disc,
207 vega: 0.0,
208 rho: -t * k * disc,
209 },
210 });
211 }
212
213 let sqrt_t = t.sqrt();
215 let d1 = ((s / k).ln() + (r + 0.5 * sigma * sigma) * t) / (sigma * sqrt_t);
216 let d2 = d1 - sigma * sqrt_t;
217 let disc = (-r * t).exp();
218 let pdf_d1 = self.normal_pdf(d1);
219
220 let (price, delta) = match params.option_type {
221 OptionType::Call => {
222 let p = s * self.normal_cdf(d1) - k * disc * self.normal_cdf(d2);
223 (p, self.normal_cdf(d1))
224 }
225 OptionType::Put => {
226 let p = k * disc * self.normal_cdf(-d2) - s * self.normal_cdf(-d1);
227 (p, self.normal_cdf(d1) - 1.0)
228 }
229 };
230
231 let gamma = pdf_d1 / (s * sigma * sqrt_t);
233 let vega = s * pdf_d1 * sqrt_t;
234
235 let theta = self.calculate_theta(params, d1, d2, pdf_d1);
236 let rho = self.calculate_rho(params, d2, disc);
237
238 Ok(OptionPrice {
239 price,
240 delta,
241 gamma,
242 theta,
243 vega,
244 rho,
245 })
246 }
247
248 fn intrinsic_price(&self, params: &OptionParameters) -> OptionPrice {
251 let intrinsic = match params.option_type {
252 OptionType::Call => (params.underlying_price - params.strike).max(0.0),
253 OptionType::Put => (params.strike - params.underlying_price).max(0.0),
254 };
255 let delta = match params.option_type {
256 OptionType::Call => {
257 if params.underlying_price > params.strike {
258 1.0
259 } else {
260 0.0
261 }
262 }
263 OptionType::Put => {
264 if params.underlying_price < params.strike {
265 -1.0
266 } else {
267 0.0
268 }
269 }
270 };
271 OptionPrice {
272 price: intrinsic,
273 delta,
274 gamma: 0.0,
275 theta: 0.0,
276 vega: 0.0,
277 rho: 0.0,
278 }
279 }
280
281 fn normal_cdf(&self, x: f64) -> f64 {
282 let t = 1.0 / (1.0 + 0.2316419 * x.abs());
284 let d = 0.3989422819 * (-x * x / 2.0).exp();
285 let p = d
286 * t
287 * (0.3193815306
288 + t * (-0.3565637813
289 + t * (1.7814779372 + t * (-1.8212559978 + t * 1.3302744929))));
290 if x >= 0.0 {
291 1.0 - p
292 } else {
293 p
294 }
295 }
296
297 fn normal_pdf(&self, x: f64) -> f64 {
298 (-0.5 * x * x).exp() / (2.0 * std::f64::consts::PI).sqrt()
299 }
300
301 fn calculate_theta(&self, params: &OptionParameters, _d1: f64, d2: f64, pdf_d1: f64) -> f64 {
302 let sqrt_t = params.time_to_maturity.sqrt();
306 let disc = (-params.risk_free_rate * params.time_to_maturity).exp();
307 let annualized = match params.option_type {
308 OptionType::Call => {
309 -(params.underlying_price * pdf_d1 * params.volatility) / (2.0 * sqrt_t)
310 - params.risk_free_rate * params.strike * disc * self.normal_cdf(d2)
311 }
312 OptionType::Put => {
313 -(params.underlying_price * pdf_d1 * params.volatility) / (2.0 * sqrt_t)
314 + params.risk_free_rate * params.strike * disc * self.normal_cdf(-d2)
315 }
316 };
317 annualized / 365.0
318 }
319
320 fn calculate_rho(&self, params: &OptionParameters, d2: f64, disc: f64) -> f64 {
321 match params.option_type {
322 OptionType::Call => {
323 params.strike * params.time_to_maturity * disc * self.normal_cdf(d2)
324 }
325 OptionType::Put => {
326 -params.strike * params.time_to_maturity * disc * self.normal_cdf(-d2)
327 }
328 }
329 }
330
331 pub fn add_pricing_model(&mut self, model: PricingModel) {
332 self.pricing_models.insert(model.model_id.clone(), model);
333 }
334
335 pub fn get_pricing_model(&self, model_id: &str) -> Option<&PricingModel> {
336 self.pricing_models.get(model_id)
337 }
338
339 pub fn list_pricing_models(&self) -> Vec<String> {
340 self.pricing_models.keys().cloned().collect()
341 }
342
343 pub fn market_data(&self) -> &MarketData {
344 &self.market_data
345 }
346
347 pub fn market_data_mut(&mut self) -> &mut MarketData {
348 &mut self.market_data
349 }
350}
351
352impl ValuationEngine {
353 pub fn new() -> Self {
354 Self {
355 valuation_methods: HashMap::new(),
356 discount_rates: HashMap::new(),
357 cash_flow_projections: HashMap::new(),
358 }
359 }
360
361 pub fn initialize(&mut self) -> Result<(), FinancialError> {
362 Ok(())
363 }
364
365 pub fn add_valuation_method(&mut self, method: ValuationMethod) {
366 self.valuation_methods
367 .insert(method.method_id.clone(), method);
368 }
369
370 pub fn get_valuation_method(&self, method_id: &str) -> Option<&ValuationMethod> {
371 self.valuation_methods.get(method_id)
372 }
373
374 pub fn list_valuation_methods(&self) -> Vec<String> {
375 self.valuation_methods.keys().cloned().collect()
376 }
377
378 pub fn set_discount_rate(&mut self, name: &str, rate: f64) {
379 self.discount_rates.insert(name.to_string(), rate);
380 }
381
382 pub fn get_discount_rate(&self, name: &str) -> Option<&f64> {
383 self.discount_rates.get(name)
384 }
385
386 pub fn add_cash_flow_projection(&mut self, projection: CashFlowProjection) {
387 self.cash_flow_projections
388 .insert(projection.projection_id.clone(), projection);
389 }
390
391 pub fn get_cash_flow_projection(&self, projection_id: &str) -> Option<&CashFlowProjection> {
392 self.cash_flow_projections.get(projection_id)
393 }
394}