use crate::studies::*;
use std::collections::HashMap;
type IndicatorConstructor = Box<dyn Fn() -> Box<dyn Indicator> + Send + Sync>;
macro_rules! register_builtins {
($factory:expr, $($ty:ty),+ $(,)?) => {
$(
let key = <$ty as Default>::default().name().to_string();
$factory.register(key, || Box::new(<$ty as Default>::default()));
)+
};
}
pub struct IndicatorFactory {
constructors: HashMap<String, IndicatorConstructor>,
}
impl IndicatorFactory {
pub fn new() -> Self {
let mut factory = Self {
constructors: HashMap::new(),
};
factory.register_builtin();
factory
}
fn register_builtin(&mut self) {
register_builtins!(
self,
SMA,
EMA,
WMA,
DEMA,
TEMA,
HMA,
ALMA,
VWMA,
LSMA,
SWMA,
KAMA,
SmoothedMA,
GuppyMA,
McGinleyDynamic,
VIDYA,
ZLEMA,
HammingMA,
MultipleMA,
MAChannel,
EMACross,
MACross,
RSI,
MACD,
VolumeWeightedMACD,
Stochastic,
StochasticRSI,
WilliamsR,
CCI,
RateOfChange,
Aroon,
AwesomeOscillator,
UltimateOscillator,
ConnorsRSI,
CoppockCurve,
TRIX,
ElderRay,
FisherTransform,
EhlersFisher,
ForceIndex,
MassIndex,
DetrendedPriceOscillator,
KDJ,
PrettyGoodOscillator,
ChandeMomentumOscillator,
TrueStrengthIndex,
StochasticMomentumIndex,
SchaffTrendCycle,
RelativeVigorIndex,
DoubleStochastic,
CenterOfGravity,
KnowSureThing,
RainbowOscillator,
UltimateMomentum,
PriceOscillator,
VolumeOscillator,
BalanceOfPower,
EaseOfMovement,
ADX,
SuperTrend,
IchimokuCloud,
ParabolicSAR,
DirectionalMovement,
VortexIndicator,
SqueezeMomentum,
TrendIntensityIndex,
TrendDetectionIndex,
Qstick,
ChoppinessIndex,
ElderImpulseSystem,
BollingerBands,
BollingerBandsWidth,
ATR,
TrueRange,
KeltnerChannels,
DonchianChannels,
StandardDeviation,
HistoricalVolatility,
ChaikinVolatility,
ChandelierExit,
AccelerationBands,
Envelopes,
PercentB,
CloseToCloseVol,
OhlcVolatility,
VolatilityIndex,
ZeroTrendVol,
CommoditySelectionIndex,
OnBalanceVolume,
VolumeWeightedAvgPrice,
AnchoredVWAP,
MoneyFlowIndex,
AccumulationDistribution,
ChaikinMoneyFlow,
VolumeRateOfChange,
KlingerOscillator,
CVD,
VolProfileVisible,
VolProfileFixed,
NetVolume,
VolumePriceTrend,
PriceVolumeTrendStudy,
NegativeVolumeIndex,
PositiveVolumeIndex,
WilliamsAD,
MarketFacilitationIndex,
IcebergDetector,
StopRunIndicator,
LiquidityTracker,
PivotPoints,
ZigZag,
PriceChannel,
CorrelationCoefficient,
CorrelationLogReturns,
LinearRegression,
LinearRegressionSlope,
StandardError,
StandardErrorBands,
RankCorrelationIndex,
MajorityRule,
AccumulationSwingIndex,
TypicalPrice,
MedianPrice,
WeightedClose,
HL2,
HLC3,
OHLC4,
AdvanceDeclineLine,
Week52HighLow,
SpreadStudy,
RatioStudy,
);
}
pub fn register<F>(&mut self, name: impl Into<String>, constructor: F)
where
F: Fn() -> Box<dyn Indicator> + Send + Sync + 'static,
{
self.constructors.insert(name.into(), Box::new(constructor));
}
pub fn create(&self, name: &str) -> Option<Box<dyn Indicator>> {
self.constructors.get(name).map(|f| f())
}
pub fn list(&self) -> Vec<String> {
self.constructors.keys().cloned().collect()
}
pub fn has(&self, name: &str) -> bool {
self.constructors.contains_key(name)
}
pub fn unregister(&mut self, name: &str) -> bool {
self.constructors.remove(name).is_some()
}
pub fn clear(&mut self) {
self.constructors.clear();
}
pub fn count(&self) -> usize {
self.constructors.len()
}
}
impl Default for IndicatorFactory {
fn default() -> Self {
Self::new()
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_factory() {
let factory = IndicatorFactory::new();
assert!(factory.has("RSI"));
assert!(factory.has("MACD"));
let rsi = factory.create("RSI");
assert!(rsi.is_some());
assert_eq!(rsi.unwrap().name(), "RSI");
let list = factory.list();
assert!(!list.is_empty());
}
#[test]
fn test_custom_indicator() {
let mut factory = IndicatorFactory::new();
factory.register("Custom", || Box::new(SMA::new(10)));
assert!(factory.has("Custom"));
assert!(factory.create("Custom").is_some());
}
#[test]
fn every_registered_name_round_trips() {
let factory = IndicatorFactory::new();
for name in factory.list() {
let indicator = factory
.create(&name)
.unwrap_or_else(|| panic!("registered name {name:?} failed to construct"));
assert_eq!(
indicator.name(),
name,
"indicator constructed for key {name:?} reports a different name()",
);
}
}
#[test]
fn factory_exposes_full_builtin_catalogue() {
let factory = IndicatorFactory::new();
assert_eq!(
factory.count(),
130,
"expected every built-in indicator to be registered exactly once",
);
}
}