finance_query/backtesting/refs/
price.rs1use crate::indicators::Indicator;
7
8use super::IndicatorRef;
9use crate::backtesting::strategy::StrategyContext;
10
11#[derive(Debug, Clone, Copy)]
21pub struct ClosePrice;
22
23impl IndicatorRef for ClosePrice {
24 fn key(&self) -> String {
25 "close".to_string()
26 }
27
28 fn required_indicators(&self) -> Vec<(String, Indicator)> {
29 vec![] }
31
32 fn value(&self, ctx: &StrategyContext) -> Option<f64> {
33 Some(ctx.close())
34 }
35
36 fn prev_value(&self, ctx: &StrategyContext) -> Option<f64> {
37 ctx.previous_candle().map(|c| c.close)
38 }
39}
40
41#[derive(Debug, Clone, Copy)]
43pub struct OpenPrice;
44
45impl IndicatorRef for OpenPrice {
46 fn key(&self) -> String {
47 "open".to_string()
48 }
49
50 fn required_indicators(&self) -> Vec<(String, Indicator)> {
51 vec![]
52 }
53
54 fn value(&self, ctx: &StrategyContext) -> Option<f64> {
55 Some(ctx.open())
56 }
57
58 fn prev_value(&self, ctx: &StrategyContext) -> Option<f64> {
59 ctx.previous_candle().map(|c| c.open)
60 }
61}
62
63#[derive(Debug, Clone, Copy)]
65pub struct HighPrice;
66
67impl IndicatorRef for HighPrice {
68 fn key(&self) -> String {
69 "high".to_string()
70 }
71
72 fn required_indicators(&self) -> Vec<(String, Indicator)> {
73 vec![]
74 }
75
76 fn value(&self, ctx: &StrategyContext) -> Option<f64> {
77 Some(ctx.high())
78 }
79
80 fn prev_value(&self, ctx: &StrategyContext) -> Option<f64> {
81 ctx.previous_candle().map(|c| c.high)
82 }
83}
84
85#[derive(Debug, Clone, Copy)]
87pub struct LowPrice;
88
89impl IndicatorRef for LowPrice {
90 fn key(&self) -> String {
91 "low".to_string()
92 }
93
94 fn required_indicators(&self) -> Vec<(String, Indicator)> {
95 vec![]
96 }
97
98 fn value(&self, ctx: &StrategyContext) -> Option<f64> {
99 Some(ctx.low())
100 }
101
102 fn prev_value(&self, ctx: &StrategyContext) -> Option<f64> {
103 ctx.previous_candle().map(|c| c.low)
104 }
105}
106
107#[derive(Debug, Clone, Copy)]
109pub struct VolumeRef;
110
111impl IndicatorRef for VolumeRef {
112 fn key(&self) -> String {
113 "volume".to_string()
114 }
115
116 fn required_indicators(&self) -> Vec<(String, Indicator)> {
117 vec![]
118 }
119
120 fn value(&self, ctx: &StrategyContext) -> Option<f64> {
121 Some(ctx.volume() as f64)
122 }
123
124 fn prev_value(&self, ctx: &StrategyContext) -> Option<f64> {
125 ctx.previous_candle().map(|c| c.volume as f64)
126 }
127}
128
129#[derive(Debug, Clone, Copy)]
131pub struct TypicalPrice;
132
133impl IndicatorRef for TypicalPrice {
134 fn key(&self) -> String {
135 "typical_price".to_string()
136 }
137
138 fn required_indicators(&self) -> Vec<(String, Indicator)> {
139 vec![]
140 }
141
142 fn value(&self, ctx: &StrategyContext) -> Option<f64> {
143 let candle = ctx.current_candle();
144 Some((candle.high + candle.low + candle.close) / 3.0)
145 }
146
147 fn prev_value(&self, ctx: &StrategyContext) -> Option<f64> {
148 ctx.previous_candle()
149 .map(|c| (c.high + c.low + c.close) / 3.0)
150 }
151}
152
153#[derive(Debug, Clone, Copy)]
155pub struct MedianPrice;
156
157impl IndicatorRef for MedianPrice {
158 fn key(&self) -> String {
159 "median_price".to_string()
160 }
161
162 fn required_indicators(&self) -> Vec<(String, Indicator)> {
163 vec![]
164 }
165
166 fn value(&self, ctx: &StrategyContext) -> Option<f64> {
167 let candle = ctx.current_candle();
168 Some((candle.high + candle.low) / 2.0)
169 }
170
171 fn prev_value(&self, ctx: &StrategyContext) -> Option<f64> {
172 ctx.previous_candle().map(|c| (c.high + c.low) / 2.0)
173 }
174}
175
176#[derive(Debug, Clone, Copy)]
195pub struct PriceChangePct;
196
197impl IndicatorRef for PriceChangePct {
198 fn key(&self) -> String {
199 "price_change_pct".to_string()
200 }
201
202 fn required_indicators(&self) -> Vec<(String, Indicator)> {
203 vec![]
204 }
205
206 fn value(&self, ctx: &StrategyContext) -> Option<f64> {
207 let current = ctx.close();
208 ctx.previous_candle().map(|prev| {
209 if prev.close != 0.0 {
210 ((current - prev.close) / prev.close) * 100.0
211 } else {
212 0.0
213 }
214 })
215 }
216
217 fn prev_value(&self, ctx: &StrategyContext) -> Option<f64> {
218 let candles = &ctx.candles;
220 let idx = ctx.index;
221 if idx >= 2 {
222 let prev = &candles[idx - 1];
223 let prev_prev = &candles[idx - 2];
224 if prev_prev.close != 0.0 {
225 Some(((prev.close - prev_prev.close) / prev_prev.close) * 100.0)
226 } else {
227 Some(0.0)
228 }
229 } else {
230 None
231 }
232 }
233}
234
235#[derive(Debug, Clone, Copy)]
250pub struct GapPct;
251
252impl IndicatorRef for GapPct {
253 fn key(&self) -> String {
254 "gap_pct".to_string()
255 }
256
257 fn required_indicators(&self) -> Vec<(String, Indicator)> {
258 vec![]
259 }
260
261 fn value(&self, ctx: &StrategyContext) -> Option<f64> {
262 let current_open = ctx.open();
263 ctx.previous_candle().map(|prev| {
264 if prev.close != 0.0 {
265 ((current_open - prev.close) / prev.close) * 100.0
266 } else {
267 0.0
268 }
269 })
270 }
271
272 fn prev_value(&self, ctx: &StrategyContext) -> Option<f64> {
273 let candles = &ctx.candles;
274 let idx = ctx.index;
275 if idx >= 2 {
276 let prev = &candles[idx - 1];
277 let prev_prev = &candles[idx - 2];
278 if prev_prev.close != 0.0 {
279 Some(((prev.open - prev_prev.close) / prev_prev.close) * 100.0)
280 } else {
281 Some(0.0)
282 }
283 } else {
284 None
285 }
286 }
287}
288
289#[derive(Debug, Clone, Copy)]
300pub struct CandleRange;
301
302impl IndicatorRef for CandleRange {
303 fn key(&self) -> String {
304 "candle_range".to_string()
305 }
306
307 fn required_indicators(&self) -> Vec<(String, Indicator)> {
308 vec![]
309 }
310
311 fn value(&self, ctx: &StrategyContext) -> Option<f64> {
312 let candle = ctx.current_candle();
313 Some(candle.high - candle.low)
314 }
315
316 fn prev_value(&self, ctx: &StrategyContext) -> Option<f64> {
317 ctx.previous_candle().map(|c| c.high - c.low)
318 }
319}
320
321#[derive(Debug, Clone, Copy)]
332pub struct CandleBody;
333
334impl IndicatorRef for CandleBody {
335 fn key(&self) -> String {
336 "candle_body".to_string()
337 }
338
339 fn required_indicators(&self) -> Vec<(String, Indicator)> {
340 vec![]
341 }
342
343 fn value(&self, ctx: &StrategyContext) -> Option<f64> {
344 let candle = ctx.current_candle();
345 Some((candle.close - candle.open).abs())
346 }
347
348 fn prev_value(&self, ctx: &StrategyContext) -> Option<f64> {
349 ctx.previous_candle().map(|c| (c.close - c.open).abs())
350 }
351}
352
353#[derive(Debug, Clone, Copy)]
366pub struct IsBullish;
367
368impl IndicatorRef for IsBullish {
369 fn key(&self) -> String {
370 "is_bullish".to_string()
371 }
372
373 fn required_indicators(&self) -> Vec<(String, Indicator)> {
374 vec![]
375 }
376
377 fn value(&self, ctx: &StrategyContext) -> Option<f64> {
378 let candle = ctx.current_candle();
379 Some(if candle.close > candle.open { 1.0 } else { 0.0 })
380 }
381
382 fn prev_value(&self, ctx: &StrategyContext) -> Option<f64> {
383 ctx.previous_candle()
384 .map(|c| if c.close > c.open { 1.0 } else { 0.0 })
385 }
386}
387
388#[derive(Debug, Clone, Copy)]
392pub struct IsBearish;
393
394impl IndicatorRef for IsBearish {
395 fn key(&self) -> String {
396 "is_bearish".to_string()
397 }
398
399 fn required_indicators(&self) -> Vec<(String, Indicator)> {
400 vec![]
401 }
402
403 fn value(&self, ctx: &StrategyContext) -> Option<f64> {
404 let candle = ctx.current_candle();
405 Some(if candle.close < candle.open { 1.0 } else { 0.0 })
406 }
407
408 fn prev_value(&self, ctx: &StrategyContext) -> Option<f64> {
409 ctx.previous_candle()
410 .map(|c| if c.close < c.open { 1.0 } else { 0.0 })
411 }
412}
413
414#[inline]
427pub fn price() -> ClosePrice {
428 ClosePrice
429}
430
431#[inline]
435pub fn close() -> ClosePrice {
436 ClosePrice
437}
438
439#[inline]
441pub fn open() -> OpenPrice {
442 OpenPrice
443}
444
445#[inline]
447pub fn high() -> HighPrice {
448 HighPrice
449}
450
451#[inline]
453pub fn low() -> LowPrice {
454 LowPrice
455}
456
457#[inline]
459pub fn volume() -> VolumeRef {
460 VolumeRef
461}
462
463#[inline]
465pub fn typical_price() -> TypicalPrice {
466 TypicalPrice
467}
468
469#[inline]
471pub fn median_price() -> MedianPrice {
472 MedianPrice
473}
474
475#[inline]
486pub fn price_change_pct() -> PriceChangePct {
487 PriceChangePct
488}
489
490#[inline]
501pub fn gap_pct() -> GapPct {
502 GapPct
503}
504
505#[inline]
507pub fn candle_range() -> CandleRange {
508 CandleRange
509}
510
511#[inline]
513pub fn candle_body() -> CandleBody {
514 CandleBody
515}
516
517#[inline]
521pub fn is_bullish() -> IsBullish {
522 IsBullish
523}
524
525#[inline]
529pub fn is_bearish() -> IsBearish {
530 IsBearish
531}
532
533#[derive(Debug, Clone, Copy)]
546pub struct RelativeVolume {
547 pub period: usize,
549}
550
551impl IndicatorRef for RelativeVolume {
552 fn key(&self) -> String {
553 format!("relative_volume_{}", self.period)
554 }
555
556 fn required_indicators(&self) -> Vec<(String, Indicator)> {
557 vec![] }
559
560 fn value(&self, ctx: &StrategyContext) -> Option<f64> {
561 let candles = &ctx.candles;
562 let idx = ctx.index;
563
564 if idx < self.period {
566 return None;
567 }
568
569 let avg_volume: f64 = candles[idx.saturating_sub(self.period)..idx]
571 .iter()
572 .map(|c| c.volume as f64)
573 .sum::<f64>()
574 / self.period as f64;
575
576 if avg_volume > 0.0 {
577 let current_volume = ctx.volume() as f64;
578 Some(current_volume / avg_volume)
579 } else {
580 None
581 }
582 }
583
584 fn prev_value(&self, ctx: &StrategyContext) -> Option<f64> {
585 let candles = &ctx.candles;
586 let idx = ctx.index;
587
588 if idx < self.period + 1 {
589 return None;
590 }
591
592 let prev_idx = idx - 1;
593 let avg_volume: f64 = candles[prev_idx.saturating_sub(self.period)..prev_idx]
594 .iter()
595 .map(|c| c.volume as f64)
596 .sum::<f64>()
597 / self.period as f64;
598
599 if avg_volume > 0.0 {
600 Some(candles[prev_idx].volume as f64 / avg_volume)
601 } else {
602 None
603 }
604 }
605}
606
607#[inline]
622pub fn relative_volume(period: usize) -> RelativeVolume {
623 RelativeVolume { period }
624}
625
626#[cfg(test)]
627mod tests {
628 use super::*;
629
630 #[test]
631 fn test_price_keys() {
632 assert_eq!(price().key(), "close");
633 assert_eq!(close().key(), "close");
634 assert_eq!(open().key(), "open");
635 assert_eq!(high().key(), "high");
636 assert_eq!(low().key(), "low");
637 assert_eq!(volume().key(), "volume");
638 assert_eq!(typical_price().key(), "typical_price");
639 assert_eq!(median_price().key(), "median_price");
640 }
641
642 #[test]
643 fn test_price_no_indicators_required() {
644 assert!(price().required_indicators().is_empty());
645 assert!(open().required_indicators().is_empty());
646 assert!(high().required_indicators().is_empty());
647 assert!(low().required_indicators().is_empty());
648 assert!(volume().required_indicators().is_empty());
649 }
650
651 #[test]
652 fn test_derived_price_keys() {
653 assert_eq!(price_change_pct().key(), "price_change_pct");
654 assert_eq!(gap_pct().key(), "gap_pct");
655 assert_eq!(candle_range().key(), "candle_range");
656 assert_eq!(candle_body().key(), "candle_body");
657 assert_eq!(is_bullish().key(), "is_bullish");
658 assert_eq!(is_bearish().key(), "is_bearish");
659 }
660
661 #[test]
662 fn test_derived_price_no_indicators_required() {
663 assert!(price_change_pct().required_indicators().is_empty());
664 assert!(gap_pct().required_indicators().is_empty());
665 assert!(candle_range().required_indicators().is_empty());
666 assert!(candle_body().required_indicators().is_empty());
667 assert!(is_bullish().required_indicators().is_empty());
668 assert!(is_bearish().required_indicators().is_empty());
669 }
670
671 #[test]
672 fn test_relative_volume_key() {
673 assert_eq!(relative_volume(20).key(), "relative_volume_20");
674 assert_eq!(relative_volume(10).key(), "relative_volume_10");
675 }
676}