OSIRIS Research - Estratégia proprietária com análise profunda de 1 ano.
Timeframe
5m
Direction
Long Only
Stoploss
-2.5%
Trailing Stop
Yes
ROI
0m: 5.0%, 20m: 3.0%, 60m: 2.0%, 120m: 1.0%
Interface Version
N/A
Startup Candles
200
Indicators
19
freqtrade/freqtrade-strategies
Strategy 003 author@: Gerald Lonlas github@: https://github.com/freqtrade/freqtrade-strategies
"""
OSIRIS RESEARCH STRATEGY v1.0 - Deep Proprietary Analysis
============================================================
50+ indicadores | Volume Flow | Microestrutura | Smart Money | Entropia
Sistema de Score por Confluência | Otimização para i9 + 128GB + RTX 4090
INDICADORES PROPRIETÁRIOS:
1. Volume Delta & CVD (Order Flow proxy sem order book)
2. Pressão Compradora/Vendedora por candle (OHLC decomposition)
3. Absorção & Exaustão Institucional (microestrutura)
4. Fair Value Gaps + Liquidity Sweeps (Smart Money Concepts)
5. Kaufman Efficiency Ratio (proxy Hurst Exponent)
6. Volatility Entropy (detecção de regime)
7. Squeeze Momentum (BB inside Keltner = explosão iminente)
8. Volume-Price Divergence (acumulação/distribuição oculta)
9. Composite Momentum (multi-period ROC ponderado)
10. EMA Ribbon Score (alinhamento multi-EMA quantificado)
SISTEMA DE ENTRADA/SAÍDA:
- Score por Confluência: cada indicador contribui pontos
- Threshold otimizável via Hyperopt
- Sem lógica AND rígida — sistema flexível e adaptativo
- Hyperopt encontra os pesos ideais automaticamente
100% proprietário. Desenvolvido exclusivamente para OSIRIS.
"""
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 OsirisResearchStrategy(IStrategy):
"""
OSIRIS Research - Estratégia proprietária com análise profunda de 1 ano.
Combina 50+ indicadores (convencionais + inovadores) num sistema de
Score por Confluência. Cada condição adiciona pontos. Entrada/saída
ocorre quando o score total atinge o threshold otimizado.
Projetada para hardware potente: i9 + 128GB RAM + RTX 4090.
Usa todos os cores via Hyperopt (-j -1).
"""
timeframe = "5m"
# ROI - será otimizado pelo Hyperopt
minimal_roi = {
"0": 0.05,
"20": 0.03,
"60": 0.02,
"120": 0.01,
}
stoploss = -0.025
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 PARAMETERS - BUY (16 parâmetros)
# ===================================================================
# Score threshold — quantos pontos de confluência mínimos para entrar
buy_score_min = IntParameter(4, 17, default=10, space="buy", optimize=True)
# --- Volume Flow ---
buy_pressure_ratio = DecimalParameter(
0.50, 0.70, default=0.55, decimals=2, space="buy", optimize=True
)
buy_rvol_min = DecimalParameter(
0.8, 3.0, default=1.2, decimals=1, space="buy", optimize=True
)
buy_cvd_periods = IntParameter(3, 10, default=5, space="buy", optimize=True)
buy_mfi_min = IntParameter(20, 50, default=30, space="buy", optimize=True)
# --- Momentum ---
buy_rsi_min = IntParameter(25, 45, default=30, space="buy", optimize=True)
buy_rsi_max = IntParameter(55, 75, default=65, space="buy", optimize=True)
buy_stochrsi_max = IntParameter(20, 50, default=30, space="buy", optimize=True)
buy_cci_min = IntParameter(-200, -30, default=-100, space="buy", optimize=True)
# --- Trend ---
buy_adx_min = IntParameter(15, 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.05, 0.40, default=0.15, decimals=2, space="buy", optimize=True
)
buy_volatility_ratio_max = DecimalParameter(
0.8, 2.0, default=1.3, decimals=1, space="buy", optimize=True
)
# --- Multi-TF ---
buy_mtf_rsi_15m_min = IntParameter(30, 55, default=40, space="buy", optimize=True)
buy_mtf_aligned = CategoricalParameter(
[True, False], default=True, space="buy", optimize=True
)
# ===================================================================
# HYPEROPT PARAMETERS - SELL (10 parâmetros)
# ===================================================================
sell_score_min = IntParameter(3, 16, default=8, space="sell", optimize=True)
sell_rsi_max = IntParameter(60, 90, default=75, space="sell", optimize=True)
sell_pressure_ratio_max = DecimalParameter(
0.25, 0.50, default=0.45, decimals=2, space="sell", optimize=True
)
sell_stochrsi_min = IntParameter(55, 95, default=80, space="sell", optimize=True)
sell_cci_max = IntParameter(50, 250, default=100, space="sell", optimize=True)
sell_adx_weakening = CategoricalParameter(
[True, False], default=True, space="sell", optimize=True
)
sell_efficiency_min = DecimalParameter(
0.05, 0.40, default=0.15, decimals=2, space="sell", optimize=True
)
sell_exhaustion_rvol = DecimalParameter(
1.5, 4.0, default=2.0, decimals=1, space="sell", optimize=True
)
sell_cmf_threshold = DecimalParameter(
-0.20, 0.0, default=-0.05, decimals=2, space="sell", optimize=True
)
sell_macd_hist_negative = CategoricalParameter(
[True, False], default=True, space="sell", optimize=True
)
def informative_pairs(self):
pairs = self.dp.current_whitelist()
informative = []
for pair in pairs:
informative.append((pair, "15m"))
informative.append((pair, "1h"))
return informative
# ===================================================================
# PROPRIETARY: VOLUME FLOW ANALYSIS
# ===================================================================
def _calc_volume_flow(self, df: DataFrame) -> DataFrame:
"""
Análise de Fluxo de Volume (Order Flow Proxy).
Decompõe cada candle em pressão compradora/vendedora usando OHLC.
Conceito: Se close > open, o candle é "comprador". A proporção
(close - low) / (high - low) estima quanto do volume foi de compra.
Isso é uma aproximação do order book sem precisar de dados L2.
"""
# Amplitude do candle
hl_range = (df["high"] - df["low"]).replace(0, np.nan)
# Volume Delta Approximation
df["buy_volume"] = df["volume"] * (df["close"] - df["low"]) / hl_range
df["sell_volume"] = df["volume"] * (df["high"] - df["close"]) / hl_range
df["buy_volume"] = df["buy_volume"].fillna(df["volume"] * 0.5)
df["sell_volume"] = df["sell_volume"].fillna(df["volume"] * 0.5)
# Volume Delta (+ = mais compra, - = mais venda)
df["volume_delta"] = df["buy_volume"] - df["sell_volume"]
# Cumulative Volume Delta (CVD) — fluxo acumulado
df["cvd"] = df["volume_delta"].cumsum()
df["cvd_ema"] = ta.EMA(df["cvd"], timeperiod=20)
df["cvd_rising"] = (df["cvd"] > df["cvd"].shift(1)).astype(int)
# Pressure Ratio (0-1, >0.5 = compradores dominam)
total_vol = (df["buy_volume"] + df["sell_volume"]).replace(0, 1)
df["pressure_ratio"] = (df["buy_volume"] / total_vol).fillna(0.5)
df["pressure_ratio_sma"] = df["pressure_ratio"].rolling(10).mean()
# Relative Volume (RVOL) — volume vs média
df["volume_sma_20"] = df["volume"].rolling(20).mean()
df["rvol"] = (df["volume"] / df["volume_sma_20"].replace(0, 1)).fillna(1)
# On-Balance Volume (OBV)
df["obv"] = (np.sign(df["close"].diff()) * df["volume"]).fillna(0).cumsum()
df["obv_ema"] = ta.EMA(df["obv"], timeperiod=20)
# Money Flow Index (MFI) — RSI ponderado por volume
df["mfi"] = ta.MFI(df, timeperiod=14)
# Chaikin Money Flow (CMF)
mf_mult = ((df["close"] - df["low"]) - (df["high"] - df["close"])) / hl_range
mf_mult = mf_mult.fillna(0)
mf_volume = mf_mult * df["volume"]
vol_sum = df["volume"].rolling(20).sum().replace(0, 1)
df["cmf"] = mf_volume.rolling(20).sum() / vol_sum
# Volume Price Trend (VPT) — preço + volume combinados
df["vpt"] = (df["close"].pct_change() * df["volume"]).fillna(0).cumsum()
df["vpt_ema"] = ta.EMA(df["vpt"], timeperiod=20)
# Force Index = variação de preço × volume
df["force_index"] = df["close"].diff() * df["volume"]
df["force_index_ema"] = ta.EMA(df["force_index"], timeperiod=13)
# Rolling VWAP (50 períodos)
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
df["vwap_distance"] = (
(df["close"] - df["vwap"]) / df["vwap"].replace(0, 1) * 100
)
return df
# ===================================================================
# PROPRIETARY: MICROSTRUCTURE ANALYSIS
# ===================================================================
def _calc_microstructure(self, df: DataFrame) -> DataFrame:
"""
Análise de Microestrutura — detecta ações institucionais.
Absorção: Volume alto + corpo pequeno = institucionais absorvendo
Exaustão: Volume extremo + corpo grande = possível esgotamento
Smart Money: FVG, Liquidity Sweeps, Displacement
"""
hl_range = (df["high"] - df["low"]).replace(0, np.nan).fillna(0.0001)
body = (df["close"] - df["open"]).abs()
# Body Ratio (0=doji, 1=marubozu)
df["body_ratio"] = body / hl_range
# Wick Ratios
upper_wick = df["high"] - df[["open", "close"]].max(axis=1)
lower_wick = df[["open", "close"]].min(axis=1) - df["low"]
df["upper_wick_ratio"] = upper_wick / hl_range
df["lower_wick_ratio"] = lower_wick / hl_range
# === PADRÕES INSTITUCIONAIS ===
# Absorção: corpo pequeno + volume alto = absorção silenciosa
df["is_absorption"] = (
(df["body_ratio"] < 0.30) & (df["rvol"] > 1.5)
).astype(int)
# Exaustão: corpo grande + volume extremo = esgotamento
df["is_exhaustion_bull"] = (
(df["body_ratio"] > 0.70)
& (df["rvol"] > 2.0)
& (df["close"] > df["open"])
).astype(int)
df["is_exhaustion_bear"] = (
(df["body_ratio"] > 0.70)
& (df["rvol"] > 2.0)
& (df["close"] < df["open"])
).astype(int)
# Pin Bar Bullish: wick inferior longo, corpo no topo
df["is_pin_bar_bull"] = (
(df["lower_wick_ratio"] > 0.60)
& (df["body_ratio"] < 0.25)
& (df["upper_wick_ratio"] < 0.15)
).astype(int)
# Pin Bar Bearish
df["is_pin_bar_bear"] = (
(df["upper_wick_ratio"] > 0.60)
& (df["body_ratio"] < 0.25)
& (df["lower_wick_ratio"] < 0.15)
).astype(int)
# Displacement Bullish: corpo grande, pouca wick, volume (institucional)
df["is_displacement_bull"] = (
(df["body_ratio"] > 0.80)
& (df["close"] > df["open"])
& (df["rvol"] > 1.2)
).astype(int)
df["is_displacement_bear"] = (
(df["body_ratio"] > 0.80)
& (df["close"] < df["open"])
& (df["rvol"] > 1.2)
).astype(int)
# === SMART MONEY CONCEPTS ===
# Fair Value Gap Bullish: gap entre candle[i].low e candle[i-2].high
df["fvg_bull"] = (df["low"] > df["high"].shift(2)).astype(int)
df["fvg_bear"] = (df["high"] < df["low"].shift(2)).astype(int)
# Liquidity Sweep: preço ultrapassa extremo e fecha de volta
recent_high = df["high"].rolling(20).max().shift(1)
recent_low = df["low"].rolling(20).min().shift(1)
df["liquidity_sweep_high"] = (
(df["high"] > recent_high) & (df["close"] < recent_high)
).astype(int)
df["liquidity_sweep_low"] = (
(df["low"] < recent_low) & (df["close"] > recent_low)
).astype(int)
# Volume Climax com reversão confirmada (sem look-ahead)
df["volume_climax_reversal_bear"] = (
(df["rvol"].shift(1) > 3.0)
& (df["close"].shift(1) > df["open"].shift(1))
& (df["close"] < df["open"])
).astype(int)
df["volume_climax_reversal_bull"] = (
(df["rvol"].shift(1) > 3.0)
& (df["close"].shift(1) < df["open"].shift(1))
& (df["close"] > df["open"])
).astype(int)
return df
# ===================================================================
# PROPRIETARY: INNOVATION INDICATORS
# ===================================================================
def _calc_innovation(self, df: DataFrame) -> DataFrame:
"""
Indicadores inovadores baseados em Teoria da Informação e Física.
- Kaufman Efficiency Ratio: proxy do Hurst Exponent
- Volatility Ratio: proxy de Entropia (ordem vs caos)
- Oscillation Index: mede ruído do preço
- Trend Acceleration: derivada segunda do preço
- Volume-Price Divergence: acumulação/distribuição oculta
- Composite Momentum: ROC multi-período ponderado
- Squeeze Momentum: BB dentro de Keltner = explosão iminente
"""
# === KAUFMAN EFFICIENCY RATIO ===
# ER → 1 = trending (Hurst > 0.5)
# ER → 0 = random walk (Hurst ≤ 0.5)
period = 20
direction = (df["close"] - df["close"].shift(period)).abs()
volatility = df["close"].diff().abs().rolling(period).sum()
df["efficiency_ratio"] = (direction / volatility.replace(0, 1)).fillna(0)
# === VOLATILITY RATIO (Proxy de Entropia) ===
df["atr"] = ta.ATR(df, timeperiod=14)
atr_long = ta.ATR(df, timeperiod=50)
df["volatility_ratio"] = (df["atr"] / atr_long.replace(0, 1)).fillna(1)
# === PRICE OSCILLATION INDEX ===
# Baixo = preço em linha reta (trend), Alto = preço oscilando (range)
net_move_10 = (df["close"] - df["close"].shift(10)).abs()
total_move_10 = df["close"].diff().abs().rolling(10).sum()
df["oscillation_index"] = 1 - (net_move_10 / total_move_10.replace(0, 1))
# === TREND ACCELERATION ===
# Segunda derivada do preço (momentum do momentum)
price_velocity = df["close"].diff(5)
df["trend_acceleration"] = price_velocity.diff(5)
df["trend_accel_norm"] = (
df["trend_acceleration"] / df["atr"].replace(0, 1)
).fillna(0)
# === VOLUME-PRICE DIVERGENCE ===
# Preço sobe + volume cai = divergência bearish (distribuição)
# Preço cai + volume sobe = possível acumulação
price_chg = df["close"].pct_change(10).fillna(0)
vol_chg = df["volume"].pct_change(10).fillna(0)
df["vol_price_divergence"] = price_chg - vol_chg
# === COMPOSITE MOMENTUM (Multi-Period ROC) ===
roc_3 = ta.ROC(df, timeperiod=3).fillna(0)
roc_5 = ta.ROC(df, timeperiod=5).fillna(0)
roc_8 = ta.ROC(df, timeperiod=8).fillna(0)
roc_13 = ta.ROC(df, timeperiod=13).fillna(0)
roc_21 = ta.ROC(df, timeperiod=21).fillna(0)
df["composite_momentum"] = (
roc_3 * 5 + roc_5 * 4 + roc_8 * 3 + roc_13 * 2 + roc_21 * 1
) / 15
# === SQUEEZE MOMENTUM (TTM Squeeze) ===
# Bollinger Bands dentro de Keltner Channels = compressão de volatilidade
# Quando a squeeze libera = movimento explosivo
kelt_ema_20 = ta.EMA(df, timeperiod=20)
kelt_atr_10 = ta.ATR(df, timeperiod=10)
kelt_upper = kelt_ema_20 + kelt_atr_10 * 1.5
kelt_lower = kelt_ema_20 - kelt_atr_10 * 1.5
bb = ta.BBANDS(df, timeperiod=20, nbdevup=2.0, nbdevdn=2.0)
bb_upper = bb["upperband"]
bb_lower = bb["lowerband"]
bb_middle = bb["middleband"]
df["bb_upper"] = bb_upper
df["bb_middle"] = bb_middle
df["bb_lower"] = bb_lower
df["bb_width"] = (bb_upper - bb_lower) / bb_middle.replace(0, 1)
df["bb_percent"] = (
(df["close"] - bb_lower) / (bb_upper - bb_lower).replace(0, 1)
)
df["keltner_upper"] = kelt_upper
df["keltner_lower"] = kelt_lower
# Squeeze ativo = BB está dentro de Keltner
df["squeeze_on"] = (
(bb_lower > kelt_lower) & (bb_upper < kelt_upper)
).astype(int)
# Squeeze release bullish: squeeze acabou + momentum positivo
df["squeeze_release_bull"] = (
(df["squeeze_on"].shift(1) == 1)
& (df["squeeze_on"] == 0)
& (df["close"] > df["close"].shift(1))
).astype(int)
df["squeeze_release_bear"] = (
(df["squeeze_on"].shift(1) == 1)
& (df["squeeze_on"] == 0)
& (df["close"] < df["close"].shift(1))
).astype(int)
return df
# ===================================================================
# INDICATOR COMPUTATION (runs once, cached)
# ===================================================================
def populate_indicators(self, dataframe: DataFrame, metadata: dict) -> DataFrame:
"""
Calcula 50+ indicadores: convencionais + proprietários + inovadores.
Tudo vetorizado para máxima performance no i9.
"""
# ============================================================
# CONVENTIONAL INDICATORS (Enhanced)
# ============================================================
# RSI (dual period)
dataframe["rsi"] = ta.RSI(dataframe, timeperiod=14)
dataframe["rsi_fast"] = ta.RSI(dataframe, timeperiod=7)
# MACD
macd = ta.MACD(dataframe, fastperiod=12, slowperiod=26, signalperiod=9)
dataframe["macd"] = macd["macd"]
dataframe["macd_signal"] = macd["macdsignal"]
dataframe["macd_hist"] = macd["macdhist"]
# EMAs (ribbon)
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)
# EMA Ribbon Score (0-4: quantas EMAs alinhadas bullish)
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)
)
# ADX + Directional Movement
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)
# Stochastic RSI
stoch = ta.STOCHRSI(dataframe, timeperiod=14, fastk_period=3, fastd_period=3)
dataframe["stochrsi_k"] = stoch["fastk"]
dataframe["stochrsi_d"] = stoch["fastd"]
# CCI
dataframe["cci"] = ta.CCI(dataframe, timeperiod=14)
# Williams %R
dataframe["willr"] = ta.WILLR(dataframe, timeperiod=14)
# Rate of Change
dataframe["roc"] = ta.ROC(dataframe, timeperiod=10)
# ATR
dataframe["atr"] = ta.ATR(dataframe, timeperiod=14)
dataframe["atr_pct"] = dataframe["atr"] / dataframe["close"] * 100
# Ichimoku componentes
ichi_tenkan = (
dataframe["high"].rolling(9).max() + dataframe["low"].rolling(9).min()
) / 2
ichi_kijun = (
dataframe["high"].rolling(26).max() + dataframe["low"].rolling(26).min()
) / 2
dataframe["ichi_tenkan"] = ichi_tenkan
dataframe["ichi_kijun"] = ichi_kijun
dataframe["ichi_above"] = (
(dataframe["close"] > ichi_tenkan).astype(int)
+ (dataframe["close"] > ichi_kijun).astype(int)
)
# Parabolic SAR
dataframe["sar"] = ta.SAR(dataframe, acceleration=0.02, maximum=0.2)
dataframe["sar_bullish"] = (dataframe["close"] > dataframe["sar"]).astype(int)
# ============================================================
# PROPRIETARY: VOLUME FLOW
# ============================================================
dataframe = self._calc_volume_flow(dataframe)
# ============================================================
# PROPRIETARY: MICROSTRUCTURE
# ============================================================
dataframe = self._calc_microstructure(dataframe)
# ============================================================
# PROPRIETARY: INNOVATION
# ============================================================
dataframe = self._calc_innovation(dataframe)
# ============================================================
# MULTI-TIMEFRAME (15m + 1h)
# ============================================================
if self.dp:
# --- 15 MINUTES ---
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)
macd_15m = ta.MACD(inf_15m)
inf_15m["macd_hist"] = macd_15m["macdhist"]
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)
# Volume flow 15m
hl_15m = (inf_15m["high"] - inf_15m["low"]).replace(0, np.nan)
bv_15m = inf_15m["volume"] * (inf_15m["close"] - inf_15m["low"]) / hl_15m
sv_15m = inf_15m["volume"] * (inf_15m["high"] - inf_15m["close"]) / hl_15m
bv_15m = bv_15m.fillna(inf_15m["volume"] * 0.5)
sv_15m = sv_15m.fillna(inf_15m["volume"] * 0.5)
inf_15m["pressure"] = (bv_15m / (bv_15m + sv_15m).replace(0, 1)).fillna(
0.5
)
dataframe = merge_informative_pair(
dataframe, inf_15m, self.timeframe, "15m", ffill=True
)
# --- 1 HOUR ---
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 SYSTEM - ENTRY
# ===================================================================
def populate_entry_trend(self, dataframe: DataFrame, metadata: dict) -> DataFrame:
"""
Sistema de Score por Confluência — Entrada.
Cada indicador que confirma a oportunidade adiciona 1 ponto.
Entrada quando score total >= threshold (otimizado por Hyperopt).
Pontuação máxima: 18 pontos
- Volume Flow: 4 pontos
- Momentum: 4 pontos
- Trend: 3 pontos
- Microstructure: 3 pontos
- Innovation: 2 pontos
- Multi-TF: 2 pontos
"""
score = np.zeros(len(dataframe))
# ── VOLUME FLOW (max 4 pontos) ──────────────────────
# 1. Pressão compradora dominante
score += (
dataframe["pressure_ratio"] > self.buy_pressure_ratio.value
).astype(int)
# 2. Volume acima da média (RVOL)
score += (dataframe["rvol"] > self.buy_rvol_min.value).astype(int)
# 3. CVD subindo por N períodos consecutivos
cvd_rising_count = (
dataframe["cvd_rising"].rolling(self.buy_cvd_periods.value).sum()
)
score += (cvd_rising_count >= self.buy_cvd_periods.value).astype(int)
# 4. MFI indicando entrada de dinheiro Smart Money
score += (dataframe["mfi"] > self.buy_mfi_min.value).astype(int)
# ── MOMENTUM (max 4 pontos) ─────────────────────────
# 5. RSI na zona de compra (não sobrevendido nem sobrecomprado)
score += (
(dataframe["rsi"] > self.buy_rsi_min.value)
& (dataframe["rsi"] < self.buy_rsi_max.value)
).astype(int)
# 6. MACD Histogram positivo OU subindo
score += (
(dataframe["macd_hist"] > 0)
| (dataframe["macd_hist"] > dataframe["macd_hist"].shift(1))
).astype(int)
# 7. StochRSI em zona favorável (saindo de sobrevenda)
score += (
dataframe["stochrsi_k"] < self.buy_stochrsi_max.value
).astype(int)
# 8. CCI saindo de oversold (momentum reversal)
score += (
(dataframe["cci"] > self.buy_cci_min.value)
& (dataframe["cci"].shift(1) <= self.buy_cci_min.value)
).astype(int)
# ── TREND (max 3 pontos) ────────────────────────────
# 9. ADX mostrando tendência ativa
score += (dataframe["adx"] > self.buy_adx_min.value).astype(int)
# 10. EMA Ribbon alinhado (N de 4 EMAs em ordem bullish)
score += (
dataframe["ema_ribbon"] >= self.buy_ema_ribbon_min.value
).astype(int)
# 11. Preço acima da VWAP (institucional favorável)
score += (dataframe["close"] > dataframe["vwap"]).astype(int)
# ── MICROSTRUCTURE (max 3 pontos) ───────────────────
# 12. Absorção detectada nas últimas 3 candles
score += (
dataframe["is_absorption"].rolling(3).sum() > 0
).astype(int)
# 13. Pin bar bullish OU liquidity sweep baixo
score += (
(dataframe["is_pin_bar_bull"] == 1)
| (dataframe["liquidity_sweep_low"] == 1)
).astype(int)
# 14. Squeeze release bullish (explosão de volatilidade)
score += (dataframe["squeeze_release_bull"] == 1).astype(int)
# ── INNOVATION (max 2 pontos) ───────────────────────
# 15. Efficiency Ratio indica tendência (não aleatório)
score += (
dataframe["efficiency_ratio"] > self.buy_efficiency_min.value
).astype(int)
# 16. Volatilidade controlada (não excessiva)
score += (
dataframe["volatility_ratio"] < self.buy_volatility_ratio_max.value
).astype(int)
# ── MULTI-TIMEFRAME (max 2 pontos) ──────────────────
# 17. RSI 15m favorável
rsi_15m_col = "rsi_15m"
if rsi_15m_col in dataframe.columns:
score += (
dataframe[rsi_15m_col] > self.buy_mtf_rsi_15m_min.value
).astype(int)
# 18. Tendência 15m + 1h alinhadas (preço acima de EMA50)
if self.buy_mtf_aligned.value:
ema50_15m = "ema_50_15m"
ema50_1h = "ema_50_1h"
if ema50_15m in dataframe.columns and ema50_1h in dataframe.columns:
score += (
(dataframe["close"] > dataframe[ema50_15m])
& (dataframe["close"] > dataframe[ema50_1h])
).astype(int)
# === ENTRY SIGNAL ===
dataframe.loc[
(score >= self.buy_score_min.value) & (dataframe["volume"] > 0),
"enter_long",
] = 1
return dataframe
# ===================================================================
# CONFLUENCE SCORE SYSTEM - EXIT
# ===================================================================
def populate_exit_trend(self, dataframe: DataFrame, metadata: dict) -> DataFrame:
"""
Sistema de Score por Confluência — Saída.
Pontuação máxima: 12 pontos
- Volume Flow: 3 pontos
- Momentum: 3 pontos
- Trend Weakening: 2 pontos
- Microstructure: 2 pontos
- Innovation: 2 pontos
"""
score = np.zeros(len(dataframe))
# ── VOLUME FLOW (max 3 pontos) ──────────────────────
# 1. Pressão vendedora dominante
score += (
dataframe["pressure_ratio"] < self.sell_pressure_ratio_max.value
).astype(int)
# 2. CVD caindo abaixo da EMA (fluxo virando)
score += (dataframe["cvd"] < dataframe["cvd_ema"]).astype(int)
# 3. CMF negativo (dinheiro saindo)
score += (
dataframe["cmf"] < self.sell_cmf_threshold.value
).astype(int)
# ── MOMENTUM (max 3 pontos) ─────────────────────────
# 4. RSI sobrecomprado
score += (dataframe["rsi"] > self.sell_rsi_max.value).astype(int)
# 5. StochRSI sobrecomprado
score += (
dataframe["stochrsi_k"] > self.sell_stochrsi_min.value
).astype(int)
# 6. CCI extremo (overbought)
score += (dataframe["cci"] > self.sell_cci_max.value).astype(int)
# ── TREND WEAKENING (max 2 pontos) ──────────────────
# 7. MACD histogram virando negativo e caindo
if self.sell_macd_hist_negative.value:
score += (
(dataframe["macd_hist"] < 0)
& (dataframe["macd_hist"] < dataframe["macd_hist"].shift(1))
).astype(int)
# 8. ADX caindo (tendência enfraquecendo)
if self.sell_adx_weakening.value:
score += (
dataframe["adx"] < dataframe["adx"].shift(1)
).astype(int)
# ── MICROSTRUCTURE (max 2 pontos) ───────────────────
# 9. Exaustão bullish detectada (topo potencial)
score += (
(dataframe["is_exhaustion_bull"] == 1)
& (dataframe["rvol"] > self.sell_exhaustion_rvol.value)
).astype(int)
# 10. Displacement bearish OU liquidity sweep alto
score += (
(dataframe["is_displacement_bear"] == 1)
| (dataframe["liquidity_sweep_high"] == 1)
).astype(int)
# ── INNOVATION (max 2 pontos) ───────────────────────
# 11. Eficiência baixa (mercado ficando aleatório)
score += (
dataframe["efficiency_ratio"] < self.sell_efficiency_min.value
).astype(int)
# 12. Volume climax bearish (reversão após climax)
score += (
dataframe["volume_climax_reversal_bear"] == 1
).astype(int)
# === EXIT SIGNAL ===
dataframe.loc[
(score >= self.sell_score_min.value) & (dataframe["volume"] > 0),
"exit_long",
] = 1
return dataframe
# ===================================================================
# DYNAMIC STOPLOSS (ATR-based)
# ===================================================================
def custom_stoploss(
self,
pair: str,
trade: Trade,
current_time,
current_rate,
current_profit,
after_fill,
**kwargs,
) -> Optional[float]:
"""Stop loss dinâmico baseado em ATR — adapta à volatilidade atual."""
dataframe, _ = self.dp.get_analyzed_dataframe(pair, self.timeframe)
if dataframe is not None and len(dataframe) > 0:
last = dataframe.iloc[-1]
atr_pct = last.get("atr_pct", 0)
if atr_pct > 0 and trade.open_rate > 0:
# Stop = 2.5x ATR abaixo da entrada
sl_pct = -(atr_pct / 100) * 2.5
return max(sl_pct, -0.05) # Cap at -5%
return None