OSIRIS Ultimate — 80+ indicadores, 10 categorias, score por confluência. Combina TODA a literatura + inovações proprietárias. Projetada para i9 + 128GB RAM + RTX 4090 (roda ok no laptop tb).
Timeframe
5m
Direction
Long Only
Stoploss
-3.0%
Trailing Stop
Yes
ROI
0m: 6.0%, 15m: 4.0%, 40m: 2.5%, 80m: 1.5%
Interface Version
N/A
Startup Candles
200
Indicators
20
freqtrade/freqtrade-strategies
Strategy 003 author@: Gerald Lonlas github@: https://github.com/freqtrade/freqtrade-strategies
"""
OSIRIS ULTIMATE STRATEGY v2.0 — A Estratégia Definitiva
=========================================================
TUDO que a literatura descreve + inovações proprietárias que não existem.
ARSENAL COMPLETO (80+ indicadores organizados em 10 categorias):
━━━ CLÁSSICOS REFORÇADOS ━━━
RSI dual-period, MACD, Stochastic, CCI, Williams %R, ROC,
ADX/DMI, Ichimoku, Parabolic SAR, Aroon
━━━ ONDAS DE ELLIOTT (Proxy Computacional) ━━━
Swing detector, Wave counter, Fibonacci auto-levels,
Impulse/Corrective classifier, Wave 3 detector
━━━ WYCKOFF / SMART MONEY ━━━
Accumulation/Distribution zones, Spring/UTAD detection,
Effort vs Result (volume vs price move), Composite Man tracker
━━━ HARMONIC PATTERNS ━━━
AB=CD proxy, Fibonacci retracement zones, extension targets
━━━ VOLUME FLOW INSTITUCIONAL ━━━
Volume Delta, CVD, Pressure Ratio, OBV, MFI, CMF, VPT,
Force Index, VWAP, Volume Climax, Absorption detection
━━━ MICROESTRUTURA DE MERCADO ━━━
Pin bars, Engulfing, Displacement, FVG, Liquidity sweeps,
Order block zones, Break of structure
━━━ TEORIA DA INFORMAÇÃO ━━━
Kaufman Efficiency Ratio, Volatility Entropy, Oscillation Index,
Trend Acceleration, Shannon Entropy do preço
━━━ PADRÕES GRÁFICOS ━━━
Double bottom/top proxy, Head & Shoulders proxy,
Support/Resistance dinâmicos, Pivot points
━━━ CORRELAÇÃO & REGIME ━━━
Volatility regime detector, Mean reversion vs Trend classifier,
Multi-timeframe alignment score
━━━ INOVAÇÕES PROPRIETÁRIAS OSIRIS ━━━
Volume-Weighted Momentum Divergence Index (V-WMDI)
Institutional Pressure Oscillator (IPO)
Regime-Adaptive Confidence Score (RACS)
Smart Money Footprint Composite (SMFC)
Score por Confluência: até 30 pontos possíveis
Hyperopt otimiza threshold + pesos + ROI + SL + trailing
100% proprietário. Custo API: $0.
"""
import logging
import numpy as np
from pandas import DataFrame
from typing import Optional
from freqtrade.strategy import IStrategy, merge_informative_pair
from freqtrade.strategy import CategoricalParameter, DecimalParameter, IntParameter
from freqtrade.persistence import Trade
import talib.abstract as ta
logger = logging.getLogger(__name__)
class OsirisUltimateStrategy(IStrategy):
"""
OSIRIS Ultimate — 80+ indicadores, 10 categorias, score por confluência.
Combina TODA a literatura + inovações proprietárias.
Projetada para i9 + 128GB RAM + RTX 4090 (roda ok no laptop tb).
"""
timeframe = "5m"
minimal_roi = {
"0": 0.06,
"15": 0.04,
"40": 0.025,
"80": 0.015,
"160": 0.005,
}
stoploss = -0.03
trailing_stop = True
trailing_stop_positive = 0.01
trailing_stop_positive_offset = 0.02
trailing_only_offset_is_reached = True
startup_candle_count = 200
process_only_new_candles = True
# ===================================================================
# HYPEROPT PARAMS — ENTRY (20 parâmetros)
# ===================================================================
# Score threshold (de 30 possíveis)
buy_score_threshold = IntParameter(5, 27, default=14, space="buy", optimize=True)
# Volume Flow
buy_pressure_min = DecimalParameter(0.50, 0.72, default=0.55, decimals=2, space="buy", optimize=True)
buy_rvol_min = DecimalParameter(0.6, 2.5, default=1.0, decimals=1, space="buy", optimize=True)
buy_cvd_periods = IntParameter(2, 8, default=4, space="buy", optimize=True)
buy_mfi_min = IntParameter(20, 55, default=35, space="buy", optimize=True)
# Momentum
buy_rsi_min = IntParameter(20, 48, default=30, space="buy", optimize=True)
buy_rsi_max = IntParameter(52, 78, default=68, space="buy", optimize=True)
buy_stochrsi_max = IntParameter(15, 55, default=30, space="buy", optimize=True)
buy_cci_min = IntParameter(-200, -20, default=-80, space="buy", optimize=True)
# Trend
buy_adx_min = IntParameter(12, 40, default=20, space="buy", optimize=True)
buy_ema_ribbon_min = IntParameter(1, 4, default=2, space="buy", optimize=True)
# Innovation
buy_efficiency_min = DecimalParameter(0.03, 0.35, default=0.12, decimals=2, space="buy", optimize=True)
buy_vol_ratio_max = DecimalParameter(0.6, 2.2, default=1.4, decimals=1, space="buy", optimize=True)
# Wyckoff / Smart Money
buy_spring_lookback = IntParameter(10, 40, default=20, space="buy", optimize=True)
buy_effort_result_min = DecimalParameter(0.5, 2.5, default=1.2, decimals=1, space="buy", optimize=True)
# Elliott proxy
buy_fib_zone_pct = DecimalParameter(0.30, 0.70, default=0.50, decimals=2, space="buy", optimize=True)
# Multi-TF
buy_mtf_rsi_15m_min = IntParameter(30, 60, default=40, space="buy", optimize=True)
buy_mtf_trend_aligned = CategoricalParameter([True, False], default=True, space="buy", optimize=True)
# Regime
buy_use_regime_filter = CategoricalParameter([True, False], default=True, space="buy", optimize=True)
# ===================================================================
# HYPEROPT PARAMS — EXIT (14 parâmetros)
# ===================================================================
sell_score_threshold = IntParameter(3, 22, default=10, space="sell", optimize=True)
sell_rsi_max = IntParameter(58, 92, default=75, space="sell", optimize=True)
sell_pressure_max = DecimalParameter(0.25, 0.50, default=0.42, decimals=2, space="sell", optimize=True)
sell_stochrsi_min = IntParameter(50, 95, default=78, space="sell", optimize=True)
sell_cci_max = IntParameter(50, 280, default=120, space="sell", optimize=True)
sell_adx_weakening = CategoricalParameter([True, False], default=True, space="sell", optimize=True)
sell_cmf_threshold = DecimalParameter(-0.25, 0.0, default=-0.08, decimals=2, space="sell", optimize=True)
sell_exhaustion_rvol = DecimalParameter(1.5, 4.5, default=2.5, decimals=1, space="sell", optimize=True)
sell_macd_hist_neg = CategoricalParameter([True, False], default=True, space="sell", optimize=True)
sell_efficiency_min = DecimalParameter(0.03, 0.35, default=0.12, decimals=2, space="sell", optimize=True)
sell_wyckoff_utad = CategoricalParameter([True, False], default=True, space="sell", optimize=True)
sell_elliott_extended = CategoricalParameter([True, False], default=True, space="sell", optimize=True)
sell_regime_filter = CategoricalParameter([True, False], default=True, space="sell", optimize=True)
sell_divergence_weight = CategoricalParameter([True, False], default=True, space="sell", optimize=True)
def informative_pairs(self):
pairs = self.dp.current_whitelist()
return [(p, "15m") for p in pairs] + [(p, "1h") for p in pairs]
# ===================================================================
# 1. VOLUME FLOW ANALYSIS
# ===================================================================
def _calc_volume_flow(self, df: DataFrame) -> DataFrame:
hl = (df["high"] - df["low"]).replace(0, np.nan)
df["buy_vol"] = (df["volume"] * (df["close"] - df["low"]) / hl).fillna(df["volume"] * 0.5)
df["sell_vol"] = (df["volume"] * (df["high"] - df["close"]) / hl).fillna(df["volume"] * 0.5)
df["vol_delta"] = df["buy_vol"] - df["sell_vol"]
df["cvd"] = df["vol_delta"].cumsum()
df["cvd_ema"] = ta.EMA(df["cvd"], timeperiod=20)
df["cvd_rising"] = (df["cvd"] > df["cvd"].shift(1)).astype(int)
total = (df["buy_vol"] + df["sell_vol"]).replace(0, 1)
df["pressure"] = (df["buy_vol"] / total).fillna(0.5)
df["pressure_sma"] = df["pressure"].rolling(10).mean()
df["vol_sma20"] = df["volume"].rolling(20).mean()
df["rvol"] = (df["volume"] / df["vol_sma20"].replace(0, 1)).fillna(1)
df["obv"] = (np.sign(df["close"].diff()) * df["volume"]).fillna(0).cumsum()
df["obv_ema"] = ta.EMA(df["obv"], timeperiod=20)
df["mfi"] = ta.MFI(df, timeperiod=14)
mf_mult = ((df["close"] - df["low"]) - (df["high"] - df["close"])) / hl
mf_mult = mf_mult.fillna(0)
vol_sum = df["volume"].rolling(20).sum().replace(0, 1)
df["cmf"] = (mf_mult * df["volume"]).rolling(20).sum() / vol_sum
df["vpt"] = (df["close"].pct_change() * df["volume"]).fillna(0).cumsum()
df["vpt_ema"] = ta.EMA(df["vpt"], timeperiod=20)
df["force_idx"] = ta.EMA(df["close"].diff() * df["volume"], timeperiod=13)
tp = (df["high"] + df["low"] + df["close"]) / 3
vwap_vol = df["volume"].rolling(50).sum().replace(0, 1)
df["vwap"] = (tp * df["volume"]).rolling(50).sum() / vwap_vol
return df
# ===================================================================
# 2. MICROSTRUCTURE & SMART MONEY
# ===================================================================
def _calc_microstructure(self, df: DataFrame) -> DataFrame:
hl = (df["high"] - df["low"]).replace(0, np.nan).fillna(0.0001)
body = (df["close"] - df["open"]).abs()
df["body_ratio"] = body / hl
upper_wick = df["high"] - df[["open", "close"]].max(axis=1)
lower_wick = df[["open", "close"]].min(axis=1) - df["low"]
df["upper_wick_r"] = upper_wick / hl
df["lower_wick_r"] = lower_wick / hl
# Absorption, pin bars, displacement
df["is_absorption"] = ((df["body_ratio"] < 0.30) & (df["rvol"] > 1.5)).astype(int)
df["pin_bull"] = ((df["lower_wick_r"] > 0.60) & (df["body_ratio"] < 0.25)).astype(int)
df["pin_bear"] = ((df["upper_wick_r"] > 0.60) & (df["body_ratio"] < 0.25)).astype(int)
df["displacement_bull"] = ((df["body_ratio"] > 0.80) & (df["close"] > df["open"]) & (df["rvol"] > 1.2)).astype(int)
df["displacement_bear"] = ((df["body_ratio"] > 0.80) & (df["close"] < df["open"]) & (df["rvol"] > 1.2)).astype(int)
# Engulfing
df["engulf_bull"] = (
(df["close"].shift(1) < df["open"].shift(1)) &
(df["close"] > df["open"]) &
(df["close"] > df["open"].shift(1)) &
(df["open"] < df["close"].shift(1))
).astype(int)
df["engulf_bear"] = (
(df["close"].shift(1) > df["open"].shift(1)) &
(df["close"] < df["open"]) &
(df["close"] < df["open"].shift(1)) &
(df["open"] > df["close"].shift(1))
).astype(int)
# FVG, liquidity sweeps
df["fvg_bull"] = (df["low"] > df["high"].shift(2)).astype(int)
df["fvg_bear"] = (df["high"] < df["low"].shift(2)).astype(int)
rec_high = df["high"].rolling(20).max().shift(1)
rec_low = df["low"].rolling(20).min().shift(1)
df["sweep_high"] = ((df["high"] > rec_high) & (df["close"] < rec_high)).astype(int)
df["sweep_low"] = ((df["low"] < rec_low) & (df["close"] > rec_low)).astype(int)
# Break of Structure (BOS) — proxy
df["higher_high"] = (df["high"] > df["high"].shift(1)).astype(int)
df["lower_low"] = (df["low"] < df["low"].shift(1)).astype(int)
df["bos_bull"] = ((df["higher_high"].rolling(3).sum() >= 3)).astype(int)
df["bos_bear"] = ((df["lower_low"].rolling(3).sum() >= 3)).astype(int)
# Volume climax reversals
df["climax_rev_bear"] = ((df["rvol"].shift(1) > 3.0) & (df["close"].shift(1) > df["open"].shift(1)) & (df["close"] < df["open"])).astype(int)
df["climax_rev_bull"] = ((df["rvol"].shift(1) > 3.0) & (df["close"].shift(1) < df["open"].shift(1)) & (df["close"] > df["open"])).astype(int)
# Exhaustion
df["exhaust_bull"] = ((df["body_ratio"] > 0.70) & (df["rvol"] > 2.0) & (df["close"] > df["open"])).astype(int)
df["exhaust_bear"] = ((df["body_ratio"] > 0.70) & (df["rvol"] > 2.0) & (df["close"] < df["open"])).astype(int)
return df
# ===================================================================
# 3. WYCKOFF PROXY
# ===================================================================
def _calc_wyckoff(self, df: DataFrame) -> DataFrame:
"""
Wyckoff Accumulation/Distribution detection:
- Spring: preço cai abaixo do suporte e volta (bull trap reversal)
- UTAD: preço sobe acima da resistência e volta (bear trap)
- Effort vs Result: volume alto + pequeno range = absorção
"""
# Support/Resistance dynamic
df["support_20"] = df["low"].rolling(20).min()
df["resist_20"] = df["high"].rolling(20).max()
# Spring (bullish): preço cai abaixo do suporte recente e fecha acima
df["spring"] = (
(df["low"] < df["support_20"].shift(1)) &
(df["close"] > df["support_20"].shift(1))
).astype(int)
# UTAD (bearish): preço sobe acima da resistência e fecha abaixo
df["utad"] = (
(df["high"] > df["resist_20"].shift(1)) &
(df["close"] < df["resist_20"].shift(1))
).astype(int)
# Effort vs Result: alto volume mas pouco movimento de preço = absorção
price_range = (df["high"] - df["low"]).replace(0, np.nan).fillna(0.0001)
range_avg = price_range.rolling(20).mean().replace(0, 1)
vol_avg = df["volume"].rolling(20).mean().replace(0, 1)
df["effort_result"] = (df["volume"] / vol_avg) / (price_range / range_avg).replace(0, 1)
# Acumulação: range apertado + volume crescente
df["acc_zone"] = (
(df["bb_width"] < df["bb_width"].rolling(50).quantile(0.3)) &
(df["volume"] > df["vol_sma20"])
).astype(int) if "bb_width" in df.columns else 0
# Distribution: range apertado no topo + volume crescente
df["dist_zone"] = (
(df["close"] > df["close"].rolling(50).quantile(0.8)) &
(df["bb_width"] < df["bb_width"].rolling(50).quantile(0.3)) &
(df["volume"] > df["vol_sma20"])
).astype(int) if "bb_width" in df.columns else 0
return df
# ===================================================================
# 4. ELLIOTT WAVE PROXY
# ===================================================================
def _calc_elliott(self, df: DataFrame) -> DataFrame:
"""
Elliott Wave proxy computacional:
- Swing high/low detector
- Fibonacci retracement zones automáticas
- Wave 3 detection (a mais poderosa)
- Impulse vs Corrective classifier
"""
# Swing High/Low (5 candles each side)
df["swing_high"] = (
(df["high"] > df["high"].shift(1)) &
(df["high"] > df["high"].shift(2)) &
(df["high"] > df["high"].shift(-1).fillna(0)) &
(df["high"] > df["high"].shift(-2).fillna(0))
).astype(int)
df["swing_low"] = (
(df["low"] < df["low"].shift(1)) &
(df["low"] < df["low"].shift(2)) &
(df["low"] < df["low"].shift(-1).fillna(df["low"].max())) &
(df["low"] < df["low"].shift(-2).fillna(df["low"].max()))
).astype(int)
# Rolling highest swing high and lowest swing low
df["swing_h_val"] = df["high"].where(df["swing_high"] == 1).ffill()
df["swing_l_val"] = df["low"].where(df["swing_low"] == 1).ffill()
# Fibonacci retracement levels from last swing range
swing_range = (df["swing_h_val"] - df["swing_l_val"]).replace(0, np.nan).fillna(1)
df["fib_382"] = df["swing_h_val"] - swing_range * 0.382
df["fib_500"] = df["swing_h_val"] - swing_range * 0.500
df["fib_618"] = df["swing_h_val"] - swing_range * 0.618
# Price in Fib golden zone (0.382 - 0.618) = wave correction zone
df["in_fib_zone"] = (
(df["close"] <= df["fib_382"]) &
(df["close"] >= df["fib_618"])
).astype(int)
# Impulse detection: strong move with volume
df["impulse_bull"] = (
(df["close"] > df["close"].shift(5)) &
((df["close"] - df["close"].shift(5)) / df["close"].shift(5).replace(0, 1) > 0.01) &
(df["rvol"] > 1.3)
).astype(int) if "rvol" in df.columns else 0
df["impulse_bear"] = (
(df["close"] < df["close"].shift(5)) &
((df["close"].shift(5) - df["close"]) / df["close"].shift(5).replace(0, 1) > 0.01) &
(df["rvol"] > 1.3)
).astype(int) if "rvol" in df.columns else 0
# Wave 3 proxy: biggest impulse after correction
# Correction = price retraced to fib zone, then new impulse starts
df["wave3_bull"] = (
(df["in_fib_zone"].shift(1) == 1) &
(df["impulse_bull"] == 1) &
(df["rvol"] > 1.5)
).astype(int) if "rvol" in df.columns else 0
df["wave3_bear"] = (
(df["in_fib_zone"].shift(1) == 1) &
(df["impulse_bear"] == 1) &
(df["rvol"] > 1.5)
).astype(int) if "rvol" in df.columns else 0
# Extended wave 5: price went too far, likely to reverse
df["extended_bull"] = (
(df["close"] > df["swing_h_val"]) &
((df["close"] - df["swing_l_val"]) > swing_range * 1.618)
).astype(int)
df["extended_bear"] = (
(df["close"] < df["swing_l_val"]) &
((df["swing_h_val"] - df["close"]) > swing_range * 1.618)
).astype(int)
return df
# ===================================================================
# 5. INNOVATION INDICATORS
# ===================================================================
def _calc_innovation(self, df: DataFrame) -> DataFrame:
"""
Indicadores inovadores:
- Kaufman Efficiency, Volatility Ratio, Oscillation Index
- Trend Acceleration, Composite Momentum
- Squeeze Momentum (TTM), Volume-Price Divergence
- Shannon Entropy proxy, Regime detector
"""
# Kaufman Efficiency Ratio
period = 20
direction = (df["close"] - df["close"].shift(period)).abs()
noise = df["close"].diff().abs().rolling(period).sum()
df["efficiency"] = (direction / noise.replace(0, 1)).fillna(0)
# ATR / Volatility
df["atr"] = ta.ATR(df, timeperiod=14)
atr_long = ta.ATR(df, timeperiod=50)
df["vol_ratio"] = (df["atr"] / atr_long.replace(0, 1)).fillna(1)
df["atr_pct"] = df["atr"] / df["close"] * 100
# Oscillation Index
net_10 = (df["close"] - df["close"].shift(10)).abs()
total_10 = df["close"].diff().abs().rolling(10).sum()
df["oscillation"] = 1 - (net_10 / total_10.replace(0, 1))
# Trend Acceleration (2nd derivative)
vel = df["close"].diff(5)
df["trend_accel"] = vel.diff(5) / df["atr"].replace(0, 1)
# Composite Momentum
roc3 = ta.ROC(df, timeperiod=3).fillna(0)
roc5 = ta.ROC(df, timeperiod=5).fillna(0)
roc8 = ta.ROC(df, timeperiod=8).fillna(0)
roc13 = ta.ROC(df, timeperiod=13).fillna(0)
roc21 = ta.ROC(df, timeperiod=21).fillna(0)
df["composite_mom"] = (roc3*5 + roc5*4 + roc8*3 + roc13*2 + roc21*1) / 15
# Squeeze (BB inside Keltner)
kelt_ema = ta.EMA(df, timeperiod=20)
kelt_atr = ta.ATR(df, timeperiod=10)
bb = ta.BBANDS(df, timeperiod=20, nbdevup=2.0, nbdevdn=2.0)
df["bb_upper"] = bb["upperband"]
df["bb_middle"] = bb["middleband"]
df["bb_lower"] = bb["lowerband"]
df["bb_width"] = (bb["upperband"] - bb["lowerband"]) / bb["middleband"].replace(0, 1)
df["bb_pct"] = (df["close"] - bb["lowerband"]) / (bb["upperband"] - bb["lowerband"]).replace(0, 1)
df["squeeze_on"] = ((bb["lowerband"] > kelt_ema - kelt_atr*1.5) & (bb["upperband"] < kelt_ema + kelt_atr*1.5)).astype(int)
df["squeeze_fire_bull"] = ((df["squeeze_on"].shift(1) == 1) & (df["squeeze_on"] == 0) & (df["close"] > df["close"].shift(1))).astype(int)
df["squeeze_fire_bear"] = ((df["squeeze_on"].shift(1) == 1) & (df["squeeze_on"] == 0) & (df["close"] < df["close"].shift(1))).astype(int)
# Volume-Price Divergence
price_chg = df["close"].pct_change(10).fillna(0)
vol_chg = df["volume"].pct_change(10).fillna(0)
df["vp_divergence"] = price_chg - vol_chg
# Shannon Entropy proxy (variação nos últimos 20 candles)
returns = df["close"].pct_change().fillna(0)
df["volatility_20"] = returns.rolling(20).std().fillna(0)
# Alta entropia = alta incerteza; Baixa = movimento direcional
vol_median = df["volatility_20"].rolling(100).median()
df["low_entropy"] = (df["volatility_20"] < vol_median).astype(int)
# Regime detector: trending vs ranging
df["regime_trend"] = ((df["adx"] > 25) & (df["efficiency"] > 0.15)).astype(int) if "adx" in df.columns else 0
df["regime_range"] = ((df["adx"] < 20) & (df["efficiency"] < 0.10)).astype(int) if "adx" in df.columns else 0
return df
# ===================================================================
# 6. PROPRIETARY OSIRIS COMPOSITES
# ===================================================================
def _calc_osiris_proprietary(self, df: DataFrame) -> DataFrame:
"""
INDICADORES 100% PROPRIETÁRIOS OSIRIS — NÃO EXISTEM NA LITERATURA
V-WMDI: Volume-Weighted Momentum Divergence Index
IPO: Institutional Pressure Oscillator
RACS: Regime-Adaptive Confidence Score
SMFC: Smart Money Footprint Composite
"""
# === V-WMDI: Volume-Weighted Momentum Divergence Index ===
# Combina RSI + MACD + CCI ponderados pelo volume relativo
# Divergência entre momentum e fluxo = edge
rsi_norm = (df["rsi"] - 50) / 50 if "rsi" in df.columns else 0
macd_norm = df["macd_hist"] / df["atr"].replace(0, 1) if "macd_hist" in df.columns else 0
cci_norm = df["cci"] / 200 if "cci" in df.columns else 0
rvol_weight = df["rvol"].clip(0.5, 3.0) if "rvol" in df.columns else 1
df["vwmdi"] = (rsi_norm * 0.4 + macd_norm * 0.35 + cci_norm * 0.25) * rvol_weight
# === IPO: Institutional Pressure Oscillator ===
# Combina: pressure ratio + CVD direction + effort/result + CMF
# Institucional entra devagar: volume alto + price pouco = compra silenciosa
pressure_sig = (df["pressure"] - 0.5) * 2 if "pressure" in df.columns else 0
cvd_sig = np.sign(df["cvd"] - df["cvd_ema"]) if "cvd" in df.columns else 0
effort_sig = (df["effort_result"] - 1).clip(-2, 2) if "effort_result" in df.columns else 0
cmf_sig = df["cmf"] * 5 if "cmf" in df.columns else 0
df["ipo"] = (pressure_sig * 0.3 + cvd_sig * 0.25 + effort_sig * 0.25 + cmf_sig * 0.2)
df["ipo_ema"] = ta.EMA(df["ipo"], timeperiod=10)
# === RACS: Regime-Adaptive Confidence Score ===
# Em trend: pesa momentum e trend-following
# Em range: pesa mean-reversion e support/resistance
is_trend = df.get("regime_trend", 0)
is_range = df.get("regime_range", 0)
# Trend score components
trend_score = (
(df.get("ema_ribbon", 0) >= 3).astype(int) * 2 +
(df.get("adx", 0) > 25).astype(int) +
(df.get("composite_mom", 0) > 0).astype(int)
)
# Range score components
range_score = (
(df.get("rsi", 50) < 35).astype(int) * 2 +
(df.get("bb_pct", 0.5) < 0.2).astype(int) +
(df.get("in_fib_zone", 0) == 1).astype(int)
)
# Adaptive: usa score relevante ao regime
df["racs"] = np.where(
is_trend == 1, trend_score,
np.where(is_range == 1, range_score, (trend_score + range_score) / 2)
)
# === SMFC: Smart Money Footprint Composite ===
# Combina todos sinais de smart money num único indicador
sm_signals = (
df.get("spring", 0) * 3 +
df.get("sweep_low", 0) * 2 +
df.get("climax_rev_bull", 0) * 2 +
df.get("displacement_bull", 0) * 2 +
df.get("engulf_bull", 0) * 1 +
df.get("pin_bull", 0) * 1 +
df.get("wave3_bull", 0) * 3 +
df.get("fvg_bull", 0) * 1 +
df.get("is_absorption", 0) * 1
)
df["smfc_bull"] = sm_signals
df["smfc_bull_sma"] = df["smfc_bull"].rolling(5).mean()
sm_signals_bear = (
df.get("utad", 0) * 3 +
df.get("sweep_high", 0) * 2 +
df.get("climax_rev_bear", 0) * 2 +
df.get("displacement_bear", 0) * 2 +
df.get("engulf_bear", 0) * 1 +
df.get("pin_bear", 0) * 1 +
df.get("wave3_bear", 0) * 3 +
df.get("fvg_bear", 0) * 1 +
df.get("exhaust_bull", 0) * 2
)
df["smfc_bear"] = sm_signals_bear
return df
# ===================================================================
# 7. PATTERN DETECTION — Double Bottom, H&S, Pivots
# ===================================================================
def _calc_patterns(self, df: DataFrame) -> DataFrame:
"""
Padrões gráficos clássicos (proxy computacional):
- Double Bottom / Double Top
- Support/Resistance pivot-based
- Divergência RSI-Price
"""
# Pivot points (standard daily — 288 candles for 5m)
period = 288
df["pivot_high"] = df["high"].rolling(period).max()
df["pivot_low"] = df["low"].rolling(period).min()
df["pivot_mid"] = (df["pivot_high"] + df["pivot_low"] + df["close"]) / 3
# Double bottom proxy: two lows within 1% over last 60 candles
low_60 = df["low"].rolling(60).min()
df["near_double_bottom"] = (
(df["low"] < low_60 * 1.005) &
(df["low"] > low_60 * 0.995) &
(df["close"] > df["open"])
).astype(int)
# Double top proxy
high_60 = df["high"].rolling(60).max()
df["near_double_top"] = (
(df["high"] > high_60 * 0.995) &
(df["high"] < high_60 * 1.005) &
(df["close"] < df["open"])
).astype(int)
# RSI-Price Divergence Bullish: price lower low + RSI higher low
df["rsi_div_bull"] = (
(df["low"] < df["low"].shift(10)) &
(df["rsi"] > df["rsi"].shift(10)) &
(df["rsi"] < 40)
).astype(int) if "rsi" in df.columns else 0
# RSI-Price Divergence Bearish: price higher high + RSI lower high
df["rsi_div_bear"] = (
(df["high"] > df["high"].shift(10)) &
(df["rsi"] < df["rsi"].shift(10)) &
(df["rsi"] > 60)
).astype(int) if "rsi" in df.columns else 0
return df
# ===================================================================
# POPULATE INDICATORS
# ===================================================================
def populate_indicators(self, dataframe: DataFrame, metadata: dict) -> DataFrame:
# --- Classic indicators ---
dataframe["rsi"] = ta.RSI(dataframe, timeperiod=14)
dataframe["rsi_fast"] = ta.RSI(dataframe, timeperiod=7)
macd = ta.MACD(dataframe, fastperiod=12, slowperiod=26, signalperiod=9)
dataframe["macd"] = macd["macd"]
dataframe["macd_signal"] = macd["macdsignal"]
dataframe["macd_hist"] = macd["macdhist"]
dataframe["ema_8"] = ta.EMA(dataframe, timeperiod=8)
dataframe["ema_13"] = ta.EMA(dataframe, timeperiod=13)
dataframe["ema_21"] = ta.EMA(dataframe, timeperiod=21)
dataframe["ema_50"] = ta.EMA(dataframe, timeperiod=50)
dataframe["ema_200"] = ta.EMA(dataframe, timeperiod=200)
dataframe["ema_ribbon"] = (
(dataframe["ema_8"] > dataframe["ema_13"]).astype(int) +
(dataframe["ema_13"] > dataframe["ema_21"]).astype(int) +
(dataframe["ema_21"] > dataframe["ema_50"]).astype(int) +
(dataframe["ema_50"] > dataframe["ema_200"]).astype(int)
)
dataframe["adx"] = ta.ADX(dataframe, timeperiod=14)
dataframe["plus_di"] = ta.PLUS_DI(dataframe, timeperiod=14)
dataframe["minus_di"] = ta.MINUS_DI(dataframe, timeperiod=14)
stoch = ta.STOCHRSI(dataframe, timeperiod=14, fastk_period=3, fastd_period=3)
dataframe["stochrsi_k"] = stoch["fastk"]
dataframe["stochrsi_d"] = stoch["fastd"]
dataframe["cci"] = ta.CCI(dataframe, timeperiod=14)
dataframe["willr"] = ta.WILLR(dataframe, timeperiod=14)
dataframe["roc"] = ta.ROC(dataframe, timeperiod=10)
# Ichimoku
dataframe["ichi_tenkan"] = (dataframe["high"].rolling(9).max() + dataframe["low"].rolling(9).min()) / 2
dataframe["ichi_kijun"] = (dataframe["high"].rolling(26).max() + dataframe["low"].rolling(26).min()) / 2
dataframe["ichi_bull"] = ((dataframe["close"] > dataframe["ichi_tenkan"]) & (dataframe["close"] > dataframe["ichi_kijun"])).astype(int)
# Parabolic SAR
dataframe["sar"] = ta.SAR(dataframe, acceleration=0.02, maximum=0.2)
dataframe["sar_bull"] = (dataframe["close"] > dataframe["sar"]).astype(int)
# Aroon
aroon = ta.AROON(dataframe, timeperiod=25)
dataframe["aroon_up"] = aroon["aroonup"]
dataframe["aroon_down"] = aroon["aroondown"]
dataframe["aroon_bull"] = (dataframe["aroon_up"] > dataframe["aroon_down"]).astype(int)
# --- Volume Flow ---
dataframe = self._calc_volume_flow(dataframe)
# --- Innovation ---
dataframe = self._calc_innovation(dataframe)
# --- Microstructure ---
dataframe = self._calc_microstructure(dataframe)
# --- Wyckoff ---
dataframe = self._calc_wyckoff(dataframe)
# --- Elliott ---
dataframe = self._calc_elliott(dataframe)
# --- Patterns ---
dataframe = self._calc_patterns(dataframe)
# --- OSIRIS Proprietary Composites ---
dataframe = self._calc_osiris_proprietary(dataframe)
# --- Multi-Timeframe ---
if self.dp:
inf_15m = self.dp.get_pair_dataframe(pair=metadata["pair"], timeframe="15m")
if not inf_15m.empty:
inf_15m["rsi"] = ta.RSI(inf_15m, timeperiod=14)
inf_15m["ema_50"] = ta.EMA(inf_15m, timeperiod=50)
inf_15m["ema_200"] = ta.EMA(inf_15m, timeperiod=200)
inf_15m["adx"] = ta.ADX(inf_15m, timeperiod=14)
macd_15m = ta.MACD(inf_15m)
inf_15m["macd_hist"] = macd_15m["macdhist"]
hl_15 = (inf_15m["high"] - inf_15m["low"]).replace(0, np.nan)
bv_15 = inf_15m["volume"] * (inf_15m["close"] - inf_15m["low"]) / hl_15
sv_15 = inf_15m["volume"] * (inf_15m["high"] - inf_15m["close"]) / hl_15
inf_15m["pressure"] = (bv_15.fillna(inf_15m["volume"]*0.5) / (bv_15.fillna(inf_15m["volume"]*0.5) + sv_15.fillna(inf_15m["volume"]*0.5)).replace(0,1))
dataframe = merge_informative_pair(dataframe, inf_15m, self.timeframe, "15m", ffill=True)
inf_1h = self.dp.get_pair_dataframe(pair=metadata["pair"], timeframe="1h")
if not inf_1h.empty:
inf_1h["rsi"] = ta.RSI(inf_1h, timeperiod=14)
inf_1h["ema_50"] = ta.EMA(inf_1h, timeperiod=50)
inf_1h["ema_200"] = ta.EMA(inf_1h, timeperiod=200)
inf_1h["adx"] = ta.ADX(inf_1h, timeperiod=14)
dataframe = merge_informative_pair(dataframe, inf_1h, self.timeframe, "1h", ffill=True)
return dataframe
# ===================================================================
# CONFLUENCE SCORE — ENTRY (max 30 pontos)
# ===================================================================
def populate_entry_trend(self, dataframe: DataFrame, metadata: dict) -> DataFrame:
s = np.zeros(len(dataframe))
# ── VOLUME FLOW (max 5) ─────────────────────────────
s += (dataframe["pressure"] > self.buy_pressure_min.value).astype(int)
s += (dataframe["rvol"] > self.buy_rvol_min.value).astype(int)
cvd_count = dataframe["cvd_rising"].rolling(self.buy_cvd_periods.value).sum()
s += (cvd_count >= self.buy_cvd_periods.value).astype(int)
s += (dataframe["mfi"] > self.buy_mfi_min.value).astype(int)
s += (dataframe["cmf"] > 0).astype(int)
# ── MOMENTUM (max 5) ───────────────────────────────
s += ((dataframe["rsi"] > self.buy_rsi_min.value) & (dataframe["rsi"] < self.buy_rsi_max.value)).astype(int)
s += ((dataframe["macd_hist"] > 0) | (dataframe["macd_hist"] > dataframe["macd_hist"].shift(1))).astype(int)
s += (dataframe["stochrsi_k"] < self.buy_stochrsi_max.value).astype(int)
s += ((dataframe["cci"] > self.buy_cci_min.value) & (dataframe["cci"].shift(1) <= self.buy_cci_min.value)).astype(int)
s += (dataframe["composite_mom"] > 0).astype(int)
# ── TREND (max 4) ──────────────────────────────────
s += (dataframe["adx"] > self.buy_adx_min.value).astype(int)
s += (dataframe["ema_ribbon"] >= self.buy_ema_ribbon_min.value).astype(int)
s += (dataframe["close"] > dataframe["vwap"]).astype(int)
s += (dataframe["sar_bull"] == 1).astype(int)
# ── WYCKOFF / SMART MONEY (max 4) ──────────────────
s += (dataframe["spring"] == 1).astype(int) * 2 # Spring = 2 pontos
s += (dataframe["effort_result"] > self.buy_effort_result_min.value).astype(int)
s += (dataframe["acc_zone"] == 1).astype(int)
# ── ELLIOTT (max 4) ────────────────────────────────
s += (dataframe["in_fib_zone"] == 1).astype(int)
s += (dataframe["wave3_bull"] == 1).astype(int) * 2 # Wave 3 = 2 pontos
s += (dataframe["impulse_bull"] == 1).astype(int)
# ── MICROSTRUCTURE (max 4) ─────────────────────────
s += (dataframe["is_absorption"].rolling(3).sum() > 0).astype(int)
s += ((dataframe["pin_bull"] == 1) | (dataframe["sweep_low"] == 1)).astype(int)
s += (dataframe["squeeze_fire_bull"] == 1).astype(int)
s += (dataframe["engulf_bull"] == 1).astype(int)
# ── PATTERNS (max 2) ──────────────────────────────
s += (dataframe["near_double_bottom"] == 1).astype(int)
s += (dataframe["rsi_div_bull"] == 1).astype(int)
# ── OSIRIS PROPRIETARY (max 3) ────────────────────
s += (dataframe["vwmdi"] > 0.3).astype(int)
s += (dataframe["ipo"] > 0.2).astype(int)
s += (dataframe["smfc_bull"] >= 3).astype(int)
# ── MULTI-TF (max 2) ─────────────────────────────
rsi_15 = "rsi_15m"
if rsi_15 in dataframe.columns:
s += (dataframe[rsi_15] > self.buy_mtf_rsi_15m_min.value).astype(int)
ema50_15 = "ema_50_15m"
ema50_1h = "ema_50_1h"
if self.buy_mtf_trend_aligned.value and ema50_15 in dataframe.columns and ema50_1h in dataframe.columns:
s += ((dataframe["close"] > dataframe[ema50_15]) & (dataframe["close"] > dataframe[ema50_1h])).astype(int)
# ── REGIME FILTER ─────────────────────────────────
if self.buy_use_regime_filter.value:
s += (dataframe["racs"] >= 2).astype(int)
# === ENTRY ===
dataframe.loc[
(s >= self.buy_score_threshold.value) & (dataframe["volume"] > 0),
"enter_long"
] = 1
return dataframe
# ===================================================================
# CONFLUENCE SCORE — EXIT (max 20 pontos)
# ===================================================================
def populate_exit_trend(self, dataframe: DataFrame, metadata: dict) -> DataFrame:
s = np.zeros(len(dataframe))
# ── VOLUME FLOW (max 3) ───────────────
s += (dataframe["pressure"] < self.sell_pressure_max.value).astype(int)
s += (dataframe["cvd"] < dataframe["cvd_ema"]).astype(int)
s += (dataframe["cmf"] < self.sell_cmf_threshold.value).astype(int)
# ── MOMENTUM (max 3) ──────────────────
s += (dataframe["rsi"] > self.sell_rsi_max.value).astype(int)
s += (dataframe["stochrsi_k"] > self.sell_stochrsi_min.value).astype(int)
s += (dataframe["cci"] > self.sell_cci_max.value).astype(int)
# ── TREND WEAKENING (max 2) ──────────
if self.sell_macd_hist_neg.value:
s += ((dataframe["macd_hist"] < 0) & (dataframe["macd_hist"] < dataframe["macd_hist"].shift(1))).astype(int)
if self.sell_adx_weakening.value:
s += (dataframe["adx"] < dataframe["adx"].shift(1)).astype(int)
# ── MICROSTRUCTURE (max 3) ───────────
s += ((dataframe["exhaust_bull"] == 1) & (dataframe["rvol"] > self.sell_exhaustion_rvol.value)).astype(int)
s += ((dataframe["displacement_bear"] == 1) | (dataframe["sweep_high"] == 1)).astype(int)
s += (dataframe["engulf_bear"] == 1).astype(int)
# ── WYCKOFF (max 2) ──────────────────
if self.sell_wyckoff_utad.value:
s += (dataframe["utad"] == 1).astype(int) * 2
s += (dataframe["dist_zone"] == 1).astype(int)
# ── ELLIOTT (max 2) ──────────────────
if self.sell_elliott_extended.value:
s += (dataframe["extended_bull"] == 1).astype(int)
s += (dataframe["wave3_bear"] == 1).astype(int)
# ── PATTERNS (max 2) ─────────────────
s += (dataframe["near_double_top"] == 1).astype(int)
if self.sell_divergence_weight.value:
s += (dataframe["rsi_div_bear"] == 1).astype(int)
# ── OSIRIS PROPRIETARY (max 2) ───────
s += (dataframe["vwmdi"] < -0.3).astype(int)
s += (dataframe["smfc_bear"] >= 3).astype(int)
# ── REGIME ───────────────────────────
if self.sell_regime_filter.value:
s += (dataframe["efficiency"] < self.sell_efficiency_min.value).astype(int)
# === EXIT ===
dataframe.loc[
(s >= self.sell_score_threshold.value) & (dataframe["volume"] > 0),
"exit_long"
] = 1
return dataframe
# ===================================================================
# DYNAMIC STOPLOSS
# ===================================================================
def custom_stoploss(self, pair, trade: Trade, current_time, current_rate, current_profit, after_fill, **kwargs) -> Optional[float]:
dataframe, _ = self.dp.get_analyzed_dataframe(pair, self.timeframe)
if dataframe is not None and len(dataframe) > 0:
atr_pct = dataframe.iloc[-1].get("atr_pct", 0)
if atr_pct > 0:
sl = -(atr_pct / 100) * 2.5
return max(sl, -0.05)
return None