use crate::bar_indicators::average::moving_average::{MovingAverageProvider, MovingAverageType};
use crate::bar_indicators::indicator_value::IndicatorValue;
use crate::bar_indicators::ohlcv_field::OhlcvField;
#[derive(Clone)]
pub struct ForceIndex {
smoothing_period: usize,
ma_type: MovingAverageType,
price_source: OhlcvField,
force_values: Vec<f64>,
prev_close: f64,
smoothed_force: MovingAverageProvider,
force_raw: f64,
force_smooth: f64,
bars_count: usize,
is_ready: bool,
}
impl ForceIndex {
pub fn new() -> Self {
Self::with_smoothing(1, MovingAverageType::EMA) }
pub fn with_smoothing_default(smoothing_period: usize) -> Self {
Self::with_smoothing(smoothing_period, MovingAverageType::EMA)
}
pub fn with_smoothing(smoothing_period: usize, ma_type: MovingAverageType) -> Self {
Self::with_source(smoothing_period, ma_type, OhlcvField::Close)
}
pub fn with_source(smoothing_period: usize, ma_type: MovingAverageType, price_source: OhlcvField) -> Self {
assert!(smoothing_period > 0, "Smoothing period must be greater than 0");
Self {
smoothing_period,
ma_type,
price_source,
force_values: Vec::with_capacity(512),
prev_close: 0.0,
smoothed_force: MovingAverageProvider::new(ma_type, smoothing_period),
force_raw: 0.0,
force_smooth: 0.0,
bars_count: 0,
is_ready: false,
}
}
pub fn with_ema(ema_period: usize) -> Self {
Self::with_smoothing(ema_period, MovingAverageType::EMA)
}
pub fn update_bar(&mut self, open: f64, high: f64, low: f64, close: f64, volume: f64) -> f64 {
let price = self.price_source.extract(open, high, low, close, volume);
self.bars_count += 1;
if self.bars_count == 1 {
self.prev_close = price;
return self.force_smooth;
}
let price_change = price - self.prev_close;
self.force_raw = volume * price_change;
if self.force_values.len() >= 512 {
self.force_values.remove(0);
}
self.force_values.push(self.force_raw);
if self.smoothing_period == 1 {
self.force_smooth = self.force_raw;
} else {
self.force_smooth = self.smoothed_force.update_bar(self.force_raw, self.force_raw, self.force_raw, self.force_raw, 1.0);
}
self.prev_close = price;
if self.bars_count >= 2 {
self.is_ready = true;
}
self.force_smooth
}
pub fn value(&self) -> IndicatorValue {
IndicatorValue::Single(self.force_smooth)
}
pub fn raw_value(&self) -> f64 {
self.force_raw
}
pub fn is_ready(&self) -> bool {
self.is_ready
}
pub fn smoothing_period(&self) -> usize {
self.smoothing_period
}
pub fn reset(&mut self) {
self.force_values.clear();
self.prev_close = 0.0;
self.smoothed_force = MovingAverageProvider::new(self.ma_type, self.smoothing_period);
self.force_raw = 0.0;
self.force_smooth = 0.0;
self.bars_count = 0;
self.is_ready = false;
}
pub fn set_ma_type(&mut self, ma_type: MovingAverageType) {
self.ma_type = ma_type;
self.reset();
}
pub fn market_condition(&self) -> &'static str {
match self.force_smooth {
v if v > 1000.0 => "Strong Bull Power", v if v > 0.0 => "Bull Power", v if v < -1000.0 => "Strong Bear Power", v if v < 0.0 => "Bear Power", _ => "Neutral" }
}
pub fn trading_signal(&self) -> i8 {
if !self.is_ready() {
return 0;
}
if self.force_smooth > 0.0 {
1 } else if self.force_smooth < 0.0 {
-1 } else {
0 }
}
pub fn advanced_signal(&self, threshold: f64) -> i8 {
if !self.is_ready() || self.force_values.len() < 3 {
return 0;
}
let len = self.force_values.len();
let current = self.force_smooth;
let prev_1 = if len >= 2 { self.force_values[len - 2] } else { 0.0 };
let prev_2 = if len >= 3 { self.force_values[len - 3] } else { 0.0 };
if prev_2 <= threshold && prev_1 <= threshold && current > threshold {
return 1;
}
if prev_2 >= -threshold && prev_1 >= -threshold && current < -threshold {
return -1;
}
0
}
pub fn pressure_strength(&self) -> f64 {
self.force_smooth.abs()
}
pub fn check_divergence(&self, price_history: &[f64], lookback: usize) -> i8 {
if !self.is_ready() || price_history.len() < lookback + 1 || self.force_values.len() < lookback + 1 {
return 0;
}
let current_price = price_history[price_history.len() - 1];
let past_price = price_history[price_history.len() - lookback - 1];
let current_force = self.force_smooth;
let past_force = self.force_values[self.force_values.len() - lookback - 1];
let price_change = current_price - past_price;
let force_change = current_force - past_force;
if price_change < 0.0 && force_change > 0.0 {
return 1;
}
if price_change > 0.0 && force_change < 0.0 {
return -1;
}
0
}
pub fn trend_direction(&self, lookback: usize) -> i8 {
if !self.is_ready() || self.force_values.len() < lookback + 1 {
return 0;
}
let current = self.force_smooth;
let past = self.force_values[self.force_values.len() - lookback - 1];
if current > past {
1 } else if current < past {
-1 } else {
0 }
}
pub fn normalized_value(&self, normalization_period: usize) -> f64 {
if !self.is_ready() || self.force_values.len() < normalization_period {
return 0.0;
}
let len = self.force_values.len();
let start_idx = len - normalization_period;
let max_force = self.force_values[start_idx..].iter().fold(f64::NEG_INFINITY, |a, &b| a.max(b));
let min_force = self.force_values[start_idx..].iter().fold(f64::INFINITY, |a, &b| a.min(b));
let range = max_force - min_force;
if range.abs() < 1e-12 {
0.0
} else {
(self.force_smooth - min_force) / range * 2.0 - 1.0 }
}
pub fn info(&self) -> String {
format!(
"FI: {:.2}, Raw: {:.2}, Condition: {}, Strength: {:.2}",
self.force_smooth,
self.force_raw,
self.market_condition(),
self.pressure_strength()
)
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_force_index_creation() {
let fi = ForceIndex::new();
assert!(!fi.is_ready());
assert_eq!(fi.value().main(), 0.0);
assert_eq!(fi.smoothing_period(), 1);
}
#[test]
fn test_force_index_with_ema() {
let fi = ForceIndex::with_ema(13);
assert!(!fi.is_ready());
assert_eq!(fi.smoothing_period(), 13);
}
#[test]
fn test_force_index_warmup() {
let mut fi = ForceIndex::new();
for i in 0..10 {
let price = 100.0 + (i as f64 * 0.1).sin() * 5.0;
fi.update_bar(price, price + 1.0, price - 1.0, price, 1000.0);
}
assert!(fi.is_ready());
}
#[test]
fn test_force_index_values_finite() {
let mut fi = ForceIndex::with_ema(13);
for i in 0..30 {
let price = 100.0 + (i as f64 * 0.2).sin() * 10.0;
let value = fi.update_bar(price, price + 1.0, price - 1.0, price, 1000.0);
assert!(value.is_finite());
}
}
#[test]
fn test_force_index_reset() {
let mut fi = ForceIndex::new();
for i in 0..10 {
fi.update_bar(100.0 + i as f64, 105.0, 95.0, 101.0, 1000.0);
}
fi.reset();
assert!(!fi.is_ready());
assert_eq!(fi.value().main(), 0.0);
}
#[test]
fn test_force_index_trading_signal() {
let mut fi = ForceIndex::new();
for i in 0..10 {
let price = 100.0 + i as f64;
fi.update_bar(price, price + 1.0, price - 1.0, price, 1000.0);
}
let signal = fi.trading_signal();
assert!(signal >= -1 && signal <= 1);
}
}