Overall Statistics
Total Orders
393
Average Win
1.45%
Average Loss
-1.01%
Compounding Annual Return
35.503%
Drawdown
15.900%
Expectancy
0.762
Start Equity
100000
End Equity
492605.02
Net Profit
392.605%
Sharpe Ratio
1.21
Sortino Ratio
1.379
Probabilistic Sharpe Ratio
77.463%
Loss Rate
28%
Win Rate
72%
Profit-Loss Ratio
1.44
Alpha
0
Beta
0
Annual Standard Deviation
0.178
Annual Variance
0.032
Information Ratio
1.421
Tracking Error
0.178
Treynor Ratio
0
Total Fees
$821.60
Estimated Strategy Capacity
$260000000.00
Lowest Capacity Asset
COST R735QTJ8XC9X
Portfolio Turnover
4.19%
Drawdown Recovery
494
from AlgorithmImports import *
import numpy as np

class NasdaqRotationV129(QCAlgorithm):
    def initialize(self):
        # 1. Parâmetros de Capital e Margem Nativa
        self.set_start_date(2021, 1, 1)
        self.set_end_date(2026, 6, 1)
        self.set_cash(100000)
        
        self.set_brokerage_model(BrokerageName.InteractiveBrokersBrokerage, AccountType.Margin)
        
        # Universo Core de Alta Performance
        self.tickers = ["AAPL", "MSFT", "NVDA", "AMZN", "META", "GOOGL", "TSLA", "AVGO", "COST", "NFLX"]
        self.symbols = [self.add_equity(t, Resolution.Daily).symbol for t in self.tickers]
        
        # Sensores de Tendência Macro da V127
        self.qqq = self.add_equity("QQQ", Resolution.Daily).symbol
        self.qqq_ema = self.ema(self.qqq, 100, Resolution.Daily)
        self.emas_ind = {symbol: self.ema(symbol, 100, Resolution.Daily) for symbol in self.symbols}
        
        # Memória de Alocação
        self.current_positions = []
        
        # Execução síncrona na terça-feira
        self.schedule.on(self.date_rules.every(DayOfWeek.Tuesday), self.time_rules.after_market_open("QQQ", 30), self.rebalance)
        self.set_warm_up(150, Resolution.Daily)

    def on_data(self, data: Slice):
        pass # Travado para eliminar pânico diário

    def rebalance(self):
        if self.is_warming_up or not self.qqq_ema.is_ready: return

        # Protetor de Queda Livre do Índice
        if self.securities[self.qqq].price < self.qqq_ema.current.value:
            if self.portfolio.invested:
                self.log("Filtro Macro Ativado - Correndo para o Caixa.")
                self.liquidate()
                self.current_positions = []
            return

        risk_adjusted_scores = {}
        for symbol in self.symbols:
            if self.active_securities.contains_key(symbol) and self.emas_ind[symbol].is_ready:
                # O ativo precisa operar acima da sua própria média de 100 dias
                if self.securities[symbol].price > self.emas_ind[symbol].current.value:
                    history = self.history(symbol, 63, Resolution.Daily) # Volta para os 3 meses da V127
                    if not history.empty and len(history) >= 63:
                        closes = history['close'].values
                        raw_return = closes[-1] / closes[0]
                        
                        daily_returns = np.diff(closes) / closes[:-1]
                        std_dev = np.std(daily_returns)
                        
                        if std_dev > 0:
                            score = (raw_return - 1.0) / std_dev
                            
                            # TRAVA DE INÉRCIA: Dá bônus para quem já está na carteira para evitar taxas
                            if symbol in self.current_positions:
                                score *= 1.08
                                
                            risk_adjusted_scores[symbol] = score

        if not risk_adjusted_scores:
            if self.portfolio.invested:
                self.liquidate()
                self.current_positions = []
            return

        # Seleção das 3 forças consistentes
        sorted_tickers = sorted(risk_adjusted_scores.items(), key=lambda item: item[1], reverse=True)
        top_targets = [item[0] for item in sorted_tickers[:3]]

        # Só gera ordens se a estrutura do Top 3 quebrar de verdade
        if set(top_targets) != set(self.current_positions):
            self.log("Mudança estrutural na carteira. Rebalanceando.")
            
            # Remove quem perdeu espaço de vez
            for symbol in list(self.portfolio.keys()):
                if symbol not in top_targets and self.portfolio[symbol].invested:
                    self.liquidate(symbol)
            
            # Distribui o lote cheio (31% por ativo)
            for symbol in top_targets:
                self.set_holdings(symbol, 0.31)
                
            self.current_positions = top_targets