use crate::indicators::Indicator;
use super::IndicatorRef;
use crate::backtesting::strategy::StrategyContext;
#[derive(Debug, Clone, Copy)]
pub struct ClosePrice;
impl IndicatorRef for ClosePrice {
fn key(&self) -> &str {
"close"
}
fn required_indicators(&self) -> Vec<(String, Indicator)> {
vec![] }
fn value(&self, ctx: &StrategyContext) -> Option<f64> {
Some(ctx.close())
}
fn prev_value(&self, ctx: &StrategyContext) -> Option<f64> {
ctx.previous_candle().map(|c| c.close)
}
}
#[derive(Debug, Clone, Copy)]
pub struct OpenPrice;
impl IndicatorRef for OpenPrice {
fn key(&self) -> &str {
"open"
}
fn required_indicators(&self) -> Vec<(String, Indicator)> {
vec![]
}
fn value(&self, ctx: &StrategyContext) -> Option<f64> {
Some(ctx.open())
}
fn prev_value(&self, ctx: &StrategyContext) -> Option<f64> {
ctx.previous_candle().map(|c| c.open)
}
}
#[derive(Debug, Clone, Copy)]
pub struct HighPrice;
impl IndicatorRef for HighPrice {
fn key(&self) -> &str {
"high"
}
fn required_indicators(&self) -> Vec<(String, Indicator)> {
vec![]
}
fn value(&self, ctx: &StrategyContext) -> Option<f64> {
Some(ctx.high())
}
fn prev_value(&self, ctx: &StrategyContext) -> Option<f64> {
ctx.previous_candle().map(|c| c.high)
}
}
#[derive(Debug, Clone, Copy)]
pub struct LowPrice;
impl IndicatorRef for LowPrice {
fn key(&self) -> &str {
"low"
}
fn required_indicators(&self) -> Vec<(String, Indicator)> {
vec![]
}
fn value(&self, ctx: &StrategyContext) -> Option<f64> {
Some(ctx.low())
}
fn prev_value(&self, ctx: &StrategyContext) -> Option<f64> {
ctx.previous_candle().map(|c| c.low)
}
}
#[derive(Debug, Clone, Copy)]
pub struct VolumeRef;
impl IndicatorRef for VolumeRef {
fn key(&self) -> &str {
"volume"
}
fn required_indicators(&self) -> Vec<(String, Indicator)> {
vec![]
}
fn value(&self, ctx: &StrategyContext) -> Option<f64> {
Some(ctx.volume() as f64)
}
fn prev_value(&self, ctx: &StrategyContext) -> Option<f64> {
ctx.previous_candle().map(|c| c.volume as f64)
}
}
#[derive(Debug, Clone, Copy)]
pub struct TypicalPrice;
impl IndicatorRef for TypicalPrice {
fn key(&self) -> &str {
"typical_price"
}
fn required_indicators(&self) -> Vec<(String, Indicator)> {
vec![]
}
fn value(&self, ctx: &StrategyContext) -> Option<f64> {
let candle = ctx.current_candle();
Some((candle.high + candle.low + candle.close) / 3.0)
}
fn prev_value(&self, ctx: &StrategyContext) -> Option<f64> {
ctx.previous_candle()
.map(|c| (c.high + c.low + c.close) / 3.0)
}
}
#[derive(Debug, Clone, Copy)]
pub struct MedianPrice;
impl IndicatorRef for MedianPrice {
fn key(&self) -> &str {
"median_price"
}
fn required_indicators(&self) -> Vec<(String, Indicator)> {
vec![]
}
fn value(&self, ctx: &StrategyContext) -> Option<f64> {
let candle = ctx.current_candle();
Some((candle.high + candle.low) / 2.0)
}
fn prev_value(&self, ctx: &StrategyContext) -> Option<f64> {
ctx.previous_candle().map(|c| (c.high + c.low) / 2.0)
}
}
#[derive(Debug, Clone, Copy)]
pub struct PriceChangePct;
impl IndicatorRef for PriceChangePct {
fn key(&self) -> &str {
"price_change_pct"
}
fn required_indicators(&self) -> Vec<(String, Indicator)> {
vec![]
}
fn value(&self, ctx: &StrategyContext) -> Option<f64> {
let current = ctx.close();
ctx.previous_candle().map(|prev| {
if prev.close != 0.0 {
((current - prev.close) / prev.close) * 100.0
} else {
0.0
}
})
}
fn prev_value(&self, ctx: &StrategyContext) -> Option<f64> {
let candles = &ctx.candles;
let idx = ctx.index;
if idx >= 2 {
let prev = &candles[idx - 1];
let prev_prev = &candles[idx - 2];
if prev_prev.close != 0.0 {
Some(((prev.close - prev_prev.close) / prev_prev.close) * 100.0)
} else {
Some(0.0)
}
} else {
None
}
}
}
#[derive(Debug, Clone, Copy)]
pub struct GapPct;
impl IndicatorRef for GapPct {
fn key(&self) -> &str {
"gap_pct"
}
fn required_indicators(&self) -> Vec<(String, Indicator)> {
vec![]
}
fn value(&self, ctx: &StrategyContext) -> Option<f64> {
let current_open = ctx.open();
ctx.previous_candle().map(|prev| {
if prev.close != 0.0 {
((current_open - prev.close) / prev.close) * 100.0
} else {
0.0
}
})
}
fn prev_value(&self, ctx: &StrategyContext) -> Option<f64> {
let candles = &ctx.candles;
let idx = ctx.index;
if idx >= 2 {
let prev = &candles[idx - 1];
let prev_prev = &candles[idx - 2];
if prev_prev.close != 0.0 {
Some(((prev.open - prev_prev.close) / prev_prev.close) * 100.0)
} else {
Some(0.0)
}
} else {
None
}
}
}
#[derive(Debug, Clone, Copy)]
pub struct CandleRange;
impl IndicatorRef for CandleRange {
fn key(&self) -> &str {
"candle_range"
}
fn required_indicators(&self) -> Vec<(String, Indicator)> {
vec![]
}
fn value(&self, ctx: &StrategyContext) -> Option<f64> {
let candle = ctx.current_candle();
Some(candle.high - candle.low)
}
fn prev_value(&self, ctx: &StrategyContext) -> Option<f64> {
ctx.previous_candle().map(|c| c.high - c.low)
}
}
#[derive(Debug, Clone, Copy)]
pub struct CandleBody;
impl IndicatorRef for CandleBody {
fn key(&self) -> &str {
"candle_body"
}
fn required_indicators(&self) -> Vec<(String, Indicator)> {
vec![]
}
fn value(&self, ctx: &StrategyContext) -> Option<f64> {
let candle = ctx.current_candle();
Some((candle.close - candle.open).abs())
}
fn prev_value(&self, ctx: &StrategyContext) -> Option<f64> {
ctx.previous_candle().map(|c| (c.close - c.open).abs())
}
}
#[derive(Debug, Clone, Copy)]
pub struct IsBullish;
impl IndicatorRef for IsBullish {
fn key(&self) -> &str {
"is_bullish"
}
fn required_indicators(&self) -> Vec<(String, Indicator)> {
vec![]
}
fn value(&self, ctx: &StrategyContext) -> Option<f64> {
let candle = ctx.current_candle();
Some(if candle.close > candle.open { 1.0 } else { 0.0 })
}
fn prev_value(&self, ctx: &StrategyContext) -> Option<f64> {
ctx.previous_candle()
.map(|c| if c.close > c.open { 1.0 } else { 0.0 })
}
}
#[derive(Debug, Clone, Copy)]
pub struct IsBearish;
impl IndicatorRef for IsBearish {
fn key(&self) -> &str {
"is_bearish"
}
fn required_indicators(&self) -> Vec<(String, Indicator)> {
vec![]
}
fn value(&self, ctx: &StrategyContext) -> Option<f64> {
let candle = ctx.current_candle();
Some(if candle.close < candle.open { 1.0 } else { 0.0 })
}
fn prev_value(&self, ctx: &StrategyContext) -> Option<f64> {
ctx.previous_candle()
.map(|c| if c.close < c.open { 1.0 } else { 0.0 })
}
}
#[inline]
pub fn price() -> ClosePrice {
ClosePrice
}
#[inline]
pub fn close() -> ClosePrice {
ClosePrice
}
#[inline]
pub fn open() -> OpenPrice {
OpenPrice
}
#[inline]
pub fn high() -> HighPrice {
HighPrice
}
#[inline]
pub fn low() -> LowPrice {
LowPrice
}
#[inline]
pub fn volume() -> VolumeRef {
VolumeRef
}
#[inline]
pub fn typical_price() -> TypicalPrice {
TypicalPrice
}
#[inline]
pub fn median_price() -> MedianPrice {
MedianPrice
}
#[inline]
pub fn price_change_pct() -> PriceChangePct {
PriceChangePct
}
#[inline]
pub fn gap_pct() -> GapPct {
GapPct
}
#[inline]
pub fn candle_range() -> CandleRange {
CandleRange
}
#[inline]
pub fn candle_body() -> CandleBody {
CandleBody
}
#[inline]
pub fn is_bullish() -> IsBullish {
IsBullish
}
#[inline]
pub fn is_bearish() -> IsBearish {
IsBearish
}
#[derive(Debug, Clone)]
pub struct RelativeVolume {
pub period: usize,
key: String,
}
impl IndicatorRef for RelativeVolume {
fn key(&self) -> &str {
&self.key
}
fn required_indicators(&self) -> Vec<(String, Indicator)> {
vec![] }
fn value(&self, ctx: &StrategyContext) -> Option<f64> {
let candles = &ctx.candles;
let idx = ctx.index;
if idx < self.period {
return None;
}
let avg_volume: f64 = candles[idx.saturating_sub(self.period)..idx]
.iter()
.map(|c| c.volume as f64)
.sum::<f64>()
/ self.period as f64;
if avg_volume > 0.0 {
let current_volume = ctx.volume() as f64;
Some(current_volume / avg_volume)
} else {
None
}
}
fn prev_value(&self, ctx: &StrategyContext) -> Option<f64> {
let candles = &ctx.candles;
let idx = ctx.index;
if idx < self.period + 1 {
return None;
}
let prev_idx = idx - 1;
let avg_volume: f64 = candles[prev_idx.saturating_sub(self.period)..prev_idx]
.iter()
.map(|c| c.volume as f64)
.sum::<f64>()
/ self.period as f64;
if avg_volume > 0.0 {
Some(candles[prev_idx].volume as f64 / avg_volume)
} else {
None
}
}
}
#[inline]
pub fn relative_volume(period: usize) -> RelativeVolume {
RelativeVolume {
period,
key: format!("relative_volume_{period}"),
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_price_keys() {
assert_eq!(price().key(), "close");
assert_eq!(close().key(), "close");
assert_eq!(open().key(), "open");
assert_eq!(high().key(), "high");
assert_eq!(low().key(), "low");
assert_eq!(volume().key(), "volume");
assert_eq!(typical_price().key(), "typical_price");
assert_eq!(median_price().key(), "median_price");
}
#[test]
fn test_price_no_indicators_required() {
assert!(price().required_indicators().is_empty());
assert!(open().required_indicators().is_empty());
assert!(high().required_indicators().is_empty());
assert!(low().required_indicators().is_empty());
assert!(volume().required_indicators().is_empty());
}
#[test]
fn test_derived_price_keys() {
assert_eq!(price_change_pct().key(), "price_change_pct");
assert_eq!(gap_pct().key(), "gap_pct");
assert_eq!(candle_range().key(), "candle_range");
assert_eq!(candle_body().key(), "candle_body");
assert_eq!(is_bullish().key(), "is_bullish");
assert_eq!(is_bearish().key(), "is_bearish");
}
#[test]
fn test_derived_price_no_indicators_required() {
assert!(price_change_pct().required_indicators().is_empty());
assert!(gap_pct().required_indicators().is_empty());
assert!(candle_range().required_indicators().is_empty());
assert!(candle_body().required_indicators().is_empty());
assert!(is_bullish().required_indicators().is_empty());
assert!(is_bearish().required_indicators().is_empty());
}
#[test]
fn test_relative_volume_key() {
assert_eq!(relative_volume(20).key(), "relative_volume_20");
assert_eq!(relative_volume(10).key(), "relative_volume_10");
}
}