Overall Statistics
Total Orders
0
Average Win
0%
Average Loss
0%
Compounding Annual Return
0%
Drawdown
0%
Expectancy
0
Start Equity
100000.00
End Equity
100000
Net Profit
0%
Sharpe Ratio
0
Sortino Ratio
0
Probabilistic Sharpe Ratio
0%
Loss Rate
0%
Win Rate
0%
Profit-Loss Ratio
0
Alpha
0
Beta
0
Annual Standard Deviation
0
Annual Variance
0
Information Ratio
-1.842
Tracking Error
0.106
Treynor Ratio
0
Total Fees
$0.00
Estimated Strategy Capacity
$0
Lowest Capacity Asset
Portfolio Turnover
0%
Drawdown Recovery
0
from AlgorithmImports import *
import numpy as np

class ZenithDataMinerV3_DualVol(QCAlgorithm):

    def Initialize(self):
        self.SetStartDate(2023, 1, 1)
        self.SetEndDate(2024, 12, 1)
        self.SetCash(100000)
        self.symbol = self.AddCrypto("BTCUSDT", Resolution.Minute).Symbol
        
        # 1. ATR (Benchmark Clássico)
        self.atr = self.ATR(self.symbol, 14, MovingAverageType.Simple, Resolution.Hour)
        
        # 2. Janela para Parkinson (Benchmark Cirúrgico)
        self.vol_window = 240 # 4 horas
        self.history_window = RollingWindow[TradeBar](self.vol_window)
        
        # Janelas de Preço para Breakout (4h)
        self.high_window = RollingWindow[float](240)
        self.low_window = RollingWindow[float](240)
        
        # Controle de High/Low do Dia Anterior (PDH/PDL)
        self.daily_high = 0
        self.daily_low = 0
        self.pdh = 0
        self.pdl = 0
        
        self.virtual_trades = []
        
        self.Schedule.On(self.DateRules.EveryDay(self.symbol), self.TimeRules.At(0, 0), self.ResetDailyStats)
        
        # HEADER CSV ATUALIZADO: Inclui ATR e PARKINSON
        self.Log("TYPE,DATE,HOUR,ATR_PCT,PARKINSON_PCT,SETUP,DIRECTION,STOP_SIZE_PCT,RESULT_R,OUTCOME")
        
        self.SetWarmUp(240)

    def OnData(self, data):
        if self.IsWarmingUp: return
        if not data.Bars.ContainsKey(self.symbol): return
        
        bar = data.Bars[self.symbol]
        
        # Atualiza Janelas
        self.history_window.Add(bar)
        self.high_window.Add(bar.High)
        self.low_window.Add(bar.Low)
        
        # Atualiza PDH/PDL Tracker
        if self.daily_high == 0 or bar.High > self.daily_high: self.daily_high = bar.High
        if self.daily_low == 0 or bar.Low < self.daily_low: self.daily_low = bar.Low
        
        if not self.high_window.IsReady or not self.history_window.IsReady: return
        
        # --- CÁLCULO DE VOLATILIDADE DUPLA ---
        
        # 1. ATR % (Normalizado pelo preço)
        current_atr_pct = (self.atr.Current.Value / bar.Close) * 100
        
        # 2. Parkinson % (Corrigido e Normalizado)
        # Parkinson Volatility = sqrt( 1/(4ln2) * mean( ln(H/L)^2 ) )
        # Isso dá a volatilidade do período da janela.
        current_parkinson_pct = self.CalculateParkinsonVol() * 100
        
        # --- LÓGICA DE TRADES VIRTUAIS ---
        
        for trade in self.virtual_trades[:]:
            self.CheckVirtualTrade(trade, bar)
            
        hour = self.Time.hour
        
        # SETUP A: SWEEP (Reversão) - Londres & NY Open
        if (7 <= hour < 10) or (13 <= hour < 16):
            if self.pdh > 0 and self.pdl > 0:
                # Short Sweep
                if bar.High > self.pdh and bar.Close < self.pdh:
                    sl_price = bar.High
                    if abs(sl_price - bar.Close)/bar.Close < 0.002: sl_price = bar.Close * 1.002
                    self.CreateVirtualTrade("SWEEP", "SHORT", bar.Close, sl_price, current_atr_pct, current_parkinson_pct, bar.Time)
                
                # Long Sweep
                elif bar.Low < self.pdl and bar.Close > self.pdl:
                    sl_price = bar.Low
                    if abs(bar.Close - sl_price)/bar.Close < 0.002: sl_price = bar.Close * 0.998
                    self.CreateVirtualTrade("SWEEP", "LONG", bar.Close, sl_price, current_atr_pct, current_parkinson_pct, bar.Time)

        # SETUP B: BREAKOUT (Tendência) - Late NY
        if 16 <= hour < 19:
            range_high = max(list(self.high_window)[1:])
            range_low = min(list(self.low_window)[1:])
            
            # Breakout Long
            if bar.Close > range_high:
                sl = bar.Close * 0.994 # Stop fixo 0.6%
                self.CreateVirtualTrade("BREAKOUT", "LONG", bar.Close, sl, current_atr_pct, current_parkinson_pct, bar.Time, tp_r=2.0)
            
            # Breakout Short
            elif bar.Close < range_low:
                sl = bar.Close * 1.006 # Stop fixo 0.6%
                self.CreateVirtualTrade("BREAKOUT", "SHORT", bar.Close, sl, current_atr_pct, current_parkinson_pct, bar.Time, tp_r=2.0)

    def CalculateParkinsonVol(self):
        sum_sq_log_hl = 0
        count = 0
        for bar in self.history_window:
            if bar.Low > 0:
                # Logaritmo da razão High/Low
                log_hl = np.log(bar.High / bar.Low)
                sum_sq_log_hl += log_hl ** 2
                count += 1
        
        if count == 0: return 0
        
        # Variância de Parkinson
        variance = (1.0 / (4.0 * np.log(2.0))) * (sum_sq_log_hl / count)
        
        # Volatilidade (Desvio Padrão)
        vol = np.sqrt(variance)
        
        # Escalar para o período da janela (opcional, mas aqui deixamos cru para ser "volatilidade por candle médio na janela")
        return vol

    def CreateVirtualTrade(self, setup_type, direction, entry, sl_price, atr_pct, parkinson_pct, time, tp_r=2.0):
        for t in self.virtual_trades:
            if t['entry_time'] == time and t['setup'] == setup_type: return

        risk_dist = abs(entry - sl_price)
        if risk_dist == 0: return
        
        stop_size_pct = (risk_dist / entry) * 100
        
        if direction == "LONG":
            tp_price = entry + (risk_dist * tp_r)
        else:
            tp_price = entry - (risk_dist * tp_r)

        trade = {
            'setup': setup_type,
            'direction': direction,
            'entry': entry,
            'sl': sl_price,
            'tp': tp_price,
            'atr_pct': atr_pct,
            'parkinson_pct': parkinson_pct,
            'entry_time': time,
            'stop_size_pct': stop_size_pct,
            'active': True
        }
        self.virtual_trades.append(trade)

    def CheckVirtualTrade(self, trade, bar):
        outcome = None
        pnl_r = 0
        
        if trade['direction'] == "LONG":
            if bar.Low <= trade['sl']:
                outcome = "LOSS"
                pnl_r = -1.0
            elif bar.High >= trade['tp']:
                outcome = "WIN"
                pnl_r = 2.0 
        else:
            if bar.High >= trade['sl']:
                outcome = "LOSS"
                pnl_r = -1.0
            elif bar.Low <= trade['tp']:
                outcome = "WIN"
                pnl_r = 2.0
        
        if outcome:
            # Log: DATA, HORA, ATR%, PARKINSON%, SETUP, DIR, STOP%, RESULT(R), OUTCOME
            self.Log(f"DATA,{trade['entry_time']},{trade['entry_time'].hour},{trade['atr_pct']:.4f},{trade['parkinson_pct']:.6f},{trade['setup']},{trade['direction']},{trade['stop_size_pct']:.4f},{pnl_r},{outcome}")
            self.virtual_trades.remove(trade)
        elif (bar.Time - trade['entry_time']).total_seconds() > 3600 * 8:
            self.Log(f"DATA,{trade['entry_time']},{trade['entry_time'].hour},{trade['atr_pct']:.4f},{trade['parkinson_pct']:.6f},{trade['setup']},{trade['direction']},{trade['stop_size_pct']:.4f},-0.1,TIMEOUT")
            self.virtual_trades.remove(trade)

    def ResetDailyStats(self):
        self.pdh = self.daily_high
        self.pdl = self.daily_low
        self.daily_high = 0
        self.daily_low = 0