src.core.feature_engine

πŸ¦‰ AthenaAI is an AI-powered ensemble machine-learning trading bot for PocketOption, built on BinaryOptionsToolsV2 β†’ Get it on GitLab
πŸ’¬ Need help? Join the Chipa Discord Β· ⚑ Go no-code with ChipaEditor

Source: src/core/feature_engine.py. FeatureEngine turns a list of Candle objects into a fixed-length np.ndarray of numeric features: 40 always-on core features (indices 0–39) plus 17 experimental features (indices 40–56) that the FeatureLab can mask off at runtime.

Class attributes

  • CORE_NAMES: list[str] β€” 40 names for indices 0–39 (see table below).
  • EXPERIMENTAL_NAMES: list[str] β€” 17 names for indices 40–56.
  • NUM_CORE = 40, NUM_EXPERIMENTAL = 17.

Other modules address features by index; the load-bearing ones are:

IndexNameUsed by
6volatility (return std)ExpirySelector.select()
10ma_cross (SMA5 βˆ’ SMA20)alignment gate, fallback predictor
12macd_histalignment gate, fallback predictor
13rsiExpirySelector.select()
14rsi_zonealignment gate and fallback predictor read index 14 as β€œRSI” β€” note this is actually the Β±1/0 overbought/oversold zone flag, while raw RSI is index 13
17atrExpirySelector.select()
24adxExpirySelector.select()

__init__(self)

Creates feature_mask β€” an np.ones(57) array where 1.0 = active, 0.0 = masked β€” and experimental_enabled = True (master switch). The FeatureLab only ever masks indices β‰₯ NUM_CORE; core features stay at 1.0.

compute(self, candles: list[Candle], window: int = 20) -> Optional[np.ndarray]

ParameterTypeDefaultMeaning
candleslist[Candle]β€”Chronological candles, newest last
windowint20Rolling window length for windowed statistics

Returns a float64 array of length 57, or None when len(candles) < max(window, 26) (26 candles are needed for the EMA-26/MACD). Callers must handle None and should also run np.nan_to_num on the result β€” both the bot and the trainer do, since divisions can still produce extreme values (guards use +1e-10 denominators).

Core features (indices 0–39)

In order: current body and range; 1-bar and 5-bar momentum; latest/mean return and return std (volatility); distances to SMA5/SMA10/SMA20; SMA5βˆ’SMA20 cross; MACD line (EMA12βˆ’EMA26), MACD histogram (vs EMA-9 signal); RSI(14) and RSI zone (+1 if >70, βˆ’1 if <30, else 0); Bollinger width and position (20, 2Οƒ); ATR(14) and current-range/ATR; Stochastic %K/%D(14,3) and their difference; CCI(20); Williams %R(14); simplified ADX(14) (actually the DX value, not smoothed); relative volume and volume std (neutral 1.0, 0.0 when all volumes are 0); doji/hammer/engulfing pattern codes; 25th/75th return percentiles, skewness, excess kurtosis; Higuchi fractal dimension of the last window closes; signed up/down close streak (capped at Β±10); and four cyclical time features (sin/cos of UTC hour-of-day and day-of-week from the last candle’s timestamp).

Experimental features (indices 40–56)

Shooting star flag; bearish engulfing flag; distance to SMA50 and above/below SMA50 flag (zeros if < 50 candles); 14/28 MA momentum ratio; four Fibonacci-level distances (23.6/38.2/50/61.8% of the 50-bar high-low range; zeros if < 50 candles); distance to window support and resistance; high/low and open/close ratios; RSI divergence flag (Β±1/0; recomputes RSI over the last 14 bars β€” the most expensive feature); volume spike ratio; wick/body ratio; body vs average body.

If experimental_enabled is False, the 17 slots are filled with zeros so the vector length never changes. Finally the vector is multiplied elementwise by feature_mask (only when lengths match), so masked features become exactly 0.0.

Side effects: none β€” pure computation, no logging.

Static indicator helpers

All private but useful when extending: _ema(data, span) / _ema_array(data, span) (recursive EMA, seeded from the first element; _ema returns the plain mean when len(data) < span), _rsi(closes, period=14) (returns 50.0 with insufficient data, 100.0 when there are no losses), _atr(highs, lows, closes, period=14) (true-range mean; falls back to mean highβˆ’low), _stochastic(...) (returns (50.0, 50.0) with insufficient data), _cci(...) (0.0 fallback), _williams_r(...) (βˆ’50.0 fallback), _adx(...) (25.0 fallback), _doji/_hammer/_engulfing (pattern codes in {βˆ’1, 0, 1}), _skewness/_kurtosis (0.0 when std < 1e-10), _higuchi_fd(series, kmax=5) (Higuchi fractal dimension approximation; returns 1.5 when the series is too short or degenerate).

Adding a feature β€” dimension-change warning

Appending a feature changes the vector length. EnsemblePredictor.partial_fit() detects the mismatch and resets all models from scratch (models.md), and _reload_from_journal() in src/bot.py skips stored trades whose saved vectors have the old length. Add new features to EXPERIMENTAL_NAMES and the experimental block together, and expect to retrain.

Usage example

from src.core.feature_engine import FeatureEngine

engine = FeatureEngine()
feats = engine.compute(candles, window=20)   # candles: list[Candle], len >= 26
if feats is not None:
    print(len(feats))                        # 57
    print(dict(zip(engine.CORE_NAMES, feats[:5])))

See also

πŸ’¬ Unsure how this interacts with the rest of the configuration? Ask in the Chipa Discord β€” or prototype strategies no-code with ChipaEditor.