from AlgorithmImports import *
import pandas as pd
from sklearn.tree import DecisionTreeClassifier
class WeeklyTreeBTC(QCAlgorithm):
def Initialize(self):
self.SetStartDate(2024, 1, 1)
self.SetEndDate(2025, 1, 1)
self.SetCash(100000)
# Subscribe to BTC and DOGE minute data
self.btc = self.AddCrypto("BTCUSDT", Resolution.Minute, Market.Binance, True).Symbol
self.doge = self.AddCrypto("DOGEUSDT", Resolution.Minute, Market.Binance, True).Symbol
self.eth = self.AddCrypto("ETHUSDT", Resolution.Minute, Market.Binance, True).Symbol
# Warm-up
self.SetWarmUp(24*60, Resolution.Minute)
# Top features
self.top_features = [
'BTCUSDT_hour_asia', 'BTCUSDT_hour_europe',
'ETHUSDT_rel_vol', 'BTCUSDT_rel_vol_ytd',
'ETHUSDT_rel_vol_ytd', 'ETHUSDT_vma5',
'DOGEUSDT_rel_vol_ytd', 'DOGEUSDT_vol_rank_15m'
]
# Model placeholder
self.model = None
# Schedule weekly retraining
self.Schedule.On(self.DateRules.Every(DayOfWeek.Monday),
self.TimeRules.At(0,0),
self.TrainWeeklyTree)
def ComputeFeatures(self, hist, time):
"""Compute feature vector for top_features at given time"""
# Reset index for safety
hist_reset = hist.reset_index()
features = {}
# BTC hour features
features['BTCUSDT_hour_asia'] = time.hour
features['BTCUSDT_hour_europe'] = (time + pd.Timedelta(hours=6)).hour
# ETH relative volume
eth_hist = hist_reset[hist_reset['symbol'] == str(self.eth)]
if not eth_hist.empty:
eth_vol = eth_hist['volume'].mean()
eth_bar = eth_hist[eth_hist['time'] == time]
features['ETHUSDT_rel_vol'] = eth_bar['volume'].values[0] / eth_vol if eth_vol>0 else 0
features['ETHUSDT_rel_vol_ytd'] = eth_bar['volume'].values[0] / eth_vol if eth_vol>0 else 0
features['ETHUSDT_vma5'] = eth_hist.loc[eth_hist['time'] <= time].tail(5)['close'].mean()
else:
features['ETHUSDT_rel_vol'] = 0
features['ETHUSDT_rel_vol_ytd'] = 0
features['ETHUSDT_vma5'] = 0
# BTC relative volume YTD
btc_hist = hist_reset[hist_reset['symbol'] == str(self.btc)]
if not btc_hist.empty:
btc_vol = btc_hist['volume'].mean()
btc_bar = btc_hist[btc_hist['time'] == time]
features['BTCUSDT_rel_vol_ytd'] = btc_bar['volume'].values[0] / btc_vol if btc_vol>0 else 0
# DOGE features
doge_hist = hist_reset[hist_reset['symbol'] == str(self.doge)]
if not doge_hist.empty:
doge_vol = doge_hist['volume'].mean()
doge_bar = doge_hist[doge_hist['time'] == time]
features['DOGEUSDT_rel_vol_ytd'] = doge_bar['volume'].values[0] / doge_vol if doge_vol>0 else 0
# vol_rank_15m = relative rank over last 15 minutes
last_15 = doge_hist.loc[(doge_hist['time'] <= time)].tail(15)
features['DOGEUSDT_vol_rank_15m'] = (doge_bar['volume'].values[0] / (last_15['volume'].max() if not last_15.empty else 1))
return pd.DataFrame([features])
def TrainWeeklyTree(self):
# Pull 1 week of minute history
hist = self.History([self.btc, self.doge, self.eth], 7*24*60, Resolution.Minute, dataNormalizationMode=DataNormalizationMode.Raw)
if hist.empty:
return
hist = hist.reset_index()
X_list = []
y_list = []
for time in hist['time'].unique()[:-1]: # skip last bar (no next)
features = self.ComputeFeatures(hist, time)
# label = 1 if next BTC close > current BTC close
btc_hist = hist[hist['symbol'] == str(self.btc)]
btc_bar_idx = btc_hist[btc_hist['time'] == time].index
if len(btc_bar_idx)==0 or btc_bar_idx[0] >= len(btc_hist)-1:
continue
current_close = btc_hist.loc[btc_bar_idx[0], 'close']
next_close = btc_hist.loc[btc_bar_idx[0]+1, 'close']
label = 1 if next_close > current_close else 0
X_list.append(features.values[0])
y_list.append(label)
if len(X_list) == 0:
return
X = pd.DataFrame(X_list, columns=self.top_features)
y = pd.Series(y_list)
# Train decision tree
self.model = DecisionTreeClassifier(max_depth=3)
self.model.fit(X, y)
self.Debug(f"Weekly tree trained at {self.Time}")
def OnData(self, data):
if self.model is None:
return
# Pull latest history for features
hist = self.History([self.btc, self.doge, self.eth], 60, Resolution.Minute)
if hist.empty:
return
features = self.ComputeFeatures(hist, hist.index.get_level_values('time')[-1])
pred = self.model.predict(features)[0]
# Execute BTC trade based on prediction
if pred == 1:
self.SetHoldings(self.btc, 1.0) # full allocation
else:
self.Liquidate(self.btc)