| 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