Overall Statistics |
Total Trades 7313 Average Win 0.10% Average Loss -0.08% Compounding Annual Return 13.876% Drawdown 12.600% Expectancy 0.178 Net Profit 66.496% Sharpe Ratio 0.923 Probabilistic Sharpe Ratio 38.868% Loss Rate 48% Win Rate 52% Profit-Loss Ratio 1.25 Alpha 0.054 Beta 0.361 Annual Standard Deviation 0.109 Annual Variance 0.012 Information Ratio -0.196 Tracking Error 0.142 Treynor Ratio 0.278 Total Fees $8274.08 Estimated Strategy Capacity $28000000.00 Lowest Capacity Asset DOV R735QTJ8XC9X |
""" Crypto trading bot using maching learning Multiple crypto portfolio @version: 0.4 """ import clr clr.AddReference("System") clr.AddReference("QuantConnect.Algorithm") clr.AddReference("QuantConnect.Common") from System import * from QuantConnect import * from QuantConnect.Algorithm import * from QuantConnect.Algorithm.Framework.Execution import * import pandas as pd pd.set_option('mode.use_inf_as_na', True) from sklearn.pipeline import Pipeline from sklearn.decomposition import PCA from sklearn.metrics import get_scorer from sklearn.neural_network import MLPClassifier from sklearn.metrics import classification_report from sklearn.model_selection import RandomizedSearchCV from timeseriessplitgroups import TimeSeriesSplitGroups STEPS = [("pca", PCA()), ("mlp", MLPClassifier(n_iter_no_change=1, max_iter=100, solver="adam", early_stopping=True, warm_start=True, validation_fraction=0.2))] PARAMS = {"pca__n_components": [None, 0.9], "mlp__activation": ["logistic", "relu"], "mlp__alpha": [0.1, 0.01, 0.001, 0.0001, 0], "mlp__hidden_layer_sizes": [[96, ], [48, 48], [32, 32, 32]]} class MLCryptoAlgo(QCAlgorithm): def Initialize(self): self.SetStartDate(2018, 1, 1) self.SetCash(100000) self.SetBrokerageModel(BrokerageName.Bitfinex, AccountType.Cash) self.Settings.FreePortfolioValuePercentage = 0.05 self.lookbacks = [1, 7, 15, 30, 90] self.datapoints = 365 * 5 self.model = None self.threshold = 0.01 self.resolution = Resolution.Daily self.SetBenchmark(SecurityType.Crypto, "BTCUSD") tickers = ["BTCUSD", "ETHUSD", "LTCUSD", "EOSUSD", "XMRUSD", "XRPUSD"] # NO BCH, ADA, DOT, ATOM [self.AddCrypto(t, self.resolution, Market.Bitfinex) for t in tickers] self.pos_size = 1.0 / len(tickers) self.Train(self.DateRules.WeekStart(), self.TimeRules.At(0, 0), self.train_model) self.Schedule.On(self.DateRules.EveryDay(), self.TimeRules.At(0, 0), self.trade) def train_model(self): """ Train model with new data, model is created if missing """ if self.model is None: cv = TimeSeriesSplitGroups(n_splits=10) self.model = RandomizedSearchCV(Pipeline(steps=STEPS), PARAMS, scoring="balanced_accuracy", cv=cv, n_iter=10, n_jobs=1) x, y = self.get_data(self.datapoints, include_y=True) if len(x) > 0 and len(y) > 0: groups = x.index.get_level_values("time") self.model.fit(x, y, groups=groups) self.Debug(classification_report(y, self.model.predict(x))) self.Plot("Model", "Bal. Accuracy", float(self.model.best_score_)) def trade(self): x = self.get_data(max(self.lookbacks) + 1, include_y=False) if len(x) > 0 and self.model is not None: symbols = x.index.get_level_values("symbol") pred = pd.Series(self.model.predict(x), index=symbols).sort_values() for symbol in pred.index: if pred[symbol]==1: self.SetHoldings(symbol, self.pos_size) elif pred[symbol]==-1: self.SetHoldings(symbol, 0) def get_data(self, datapoints=1, include_y=True): tickers = list(self.ActiveSecurities.Keys) data = self.History(tickers, datapoints, self.resolution) data["volatility"] = data["high"] - data["low"] data["spread"] = data["askclose"] - data["bidclose"] data = data[["close", "volatility", "volume", "spread"]] groups = data.groupby("symbol") features = [groups.pct_change(p) for p in self.lookbacks] # Momentum features += [data / groups.apply(lambda x: x.rolling(p).mean()) for p in self.lookbacks] # Feats normalized by their average features = pd.concat(features, join="inner", axis="columns").dropna() if include_y: target = groups["close"].pct_change(1).shift(-1) target = target.reindex_like(features).dropna() target = target.apply(lambda x: +1 if x>self.threshold else (-1 if x<-self.threshold else 0)) return features.loc[target.index], target else: return features
""" Crypto trading bot using machine learning Complete version with limit and stop order @version: 0.13 """ import clr clr.AddReference("System") clr.AddReference("QuantConnect.Algorithm") clr.AddReference("QuantConnect.Common") from System import * from QuantConnect import * from QuantConnect.Algorithm import * from QuantConnect.Algorithm.Framework.Execution import * import pandas as pd pd.set_option('mode.use_inf_as_na', True) from sklearn.decomposition import PCA from sklearn.pipeline import Pipeline from sklearn.metrics import precision_score from sklearn.preprocessing import StandardScaler from sklearn.neural_network import MLPClassifier from sklearn.model_selection import GridSearchCV from sklearn.linear_model import LogisticRegression from timeseriescv import PurgedTimeSeriesSplitGroups from sklearn.ensemble import GradientBoostingClassifier from sklearn.model_selection import train_test_split as ttsplit import features as ft STEPS = [("scaler", StandardScaler()), ("pca", PCA()), ("model", LogisticRegression())] PARAMS = {"pca__n_components": ["mle", None], "model": [MLPClassifier(n_iter_no_change=1, early_stopping=True, hidden_layer_sizes=(64, 64)), GradientBoostingClassifier(n_iter_no_change=1, max_depth=5), LogisticRegression()]} class MLCryptoAlgo(QCAlgorithm): def Initialize(self): self.SetStartDate(2018, 1, 1) self.SetCash(100000) self.SetBrokerageModel(BrokerageName.GDAX, AccountType.Cash) self.Settings.FreePortfolioValuePercentage = 0.05 self.resolution = Resolution.Hour self.bar_size = 24 self.tickers = ["BTCUSD", "ETHUSD", "LTCUSD", "EOSUSD", "XRPUSD", "ETCUSD", "BCHUSD", "ATOMUSD"] # NO ADA, DOT, SOL, XMR self.SetBenchmark(self.CustomBenchmark) [self.AddCrypto(t, self.resolution, Market.GDAX) for t in self.tickers] self.periods = [1, 7, 30] self.lookback = max(self.periods) self.datapoints = 365 * 1 self.possize = 0.0 self.commissions = 0.002 self.model = None self.Train(self.DateRules.WeekStart(), #self.DateRules.EveryDay(), self.TimeRules.At(0, 15), self.train_model) self.Schedule.On(self.DateRules.EveryDay(), self.TimeRules.At(0, 30), #self.TimeRules.Every(TimeSpan.FromHours(1)), self.trade) def CustomBenchmark(self, time): bmk = [self.Securities[ticker].Price for ticker in self.tickers] return sum(bmk)/len(self.tickers) def train_model(self): """ Train model with new data, model is created if missing """ x, y = self.get_data(self.datapoints, augmentation=2, include_y=True) if len(x) > 0 and len(y) > 0: dates = x.index.get_level_values("time").sort_values() train_dates, test_dates = ttsplit(dates.unique(), shuffle=False) x_train = x[dates.isin(train_dates)] y_train = y[dates.isin(train_dates)] if self.model is None: cv = PurgedTimeSeriesSplitGroups(n_splits=5, purge_groups=self.lookback) self.model = GridSearchCV(Pipeline(steps=STEPS), PARAMS, scoring="precision", cv=cv) groups = x_train.index.get_level_values("time") self.model.fit(x_train, (y_train > 0), groups=groups) self.Log(pd.DataFrame(self.model.cv_results_).to_string()) x_test = x[dates.isin(test_dates)] y_test = y[dates.isin(test_dates)] self.calc_kelly(x_test, (y_test > 0), y_test) def calc_kelly(self, x, y, returns): """ Calculate info needed for Kelly position sizing """ winrate = precision_score(y, self.model.predict(x)) avgwin = max(returns[returns > 0].mean() - 2 * self.commissions, 0) avgloss = max(-returns[returns < 0].mean() + 2 * self.commissions, 0) self.possize = winrate / avgloss - (1 - winrate) / avgwin self.possize = min(max(self.possize, 0), 1) self.Debug(f"PT:{len(x)} WR:{winrate:.3f} PS:{self.possize:.3f} " f"AW:{avgwin:.4f} AL:{avgloss:.4f}") self.Plot("Model", "Win Rate", winrate) self.Plot("Model", "Win Loss Ratio", avgwin / avgloss) self.Plot("Model", "Kelly Position", self.possize) def trade(self): x = self.get_data(self.lookback*2+1, include_y=False) self.Transactions.CancelOpenOrders() if len(x) > 0 and self.model is not None: x = x.groupby("symbol").last() # TODO: Check why there are more datapoints tickers = x.index.get_level_values("symbol") pred = pd.Series(self.model.predict(x), index=tickers) self.Log(f"Predictions\n{pred.to_string()}") self.Debug(f"Prediction symbols {len(tickers)}") for ticker in pred.index: target = self.possize / len(tickers) if pred[ticker]==1 else 0 qty = self.CalculateOrderQuantity(ticker, target) self.LimitOrder(ticker, qty, self.Securities[ticker].Price) def get_data(self, points=1, augmentation=1, include_y=True): tickers = list(self.ActiveSecurities.Keys) history = self.History(tickers, points * self.bar_size, self.resolution) # define data history["volatility"] = history["high"] - history["low"] history["spread"] = history["askclose"] - history["bidclose"] history["vwap"] = ft.vwap(history, self.lookback) history = history[["close", "volatility", "volume", "spread", "vwap"]] features_aug, target_aug = pd.DataFrame(), pd.Series() step_size = int(self.bar_size / augmentation) for step in range(0, self.bar_size, step_size): # consolidate data and shift it if there is augmentation groupers = [pd.Grouper(level="symbol"), pd.Grouper(level="time", base=step, freq=f"{self.bar_size}H")] consolidated = history.groupby(groupers) data = pd.concat([consolidated["close"].last(), consolidated["volatility"].mean(), consolidated["volume"].sum(), consolidated["vwap"].mean(), consolidated["spread"].mean()], join="inner", axis="columns").dropna() # calculate features indicators = [ft.momentum(data, self.periods), ft.strength(data, self.periods), ft.macd(data, zip(self.periods[:-1], self.periods[1:])), ft.minmax(data, self.periods[1:])] new_indicators = [ft.diff(i, self.periods[1:]) for i in indicators] new_indicators += [ft.std(i, p) for i in indicators for p in self.periods[1:]] features = ft.join_indicators(indicators+new_indicators+[ft.time(data)]) features_aug = features_aug.append(features) target = data["close"].groupby("symbol").pct_change(1).shift(-1) target_aug = target_aug.append(target) if include_y: target_aug = target_aug.reindex_like(features_aug).dropna() return features_aug.loc[target_aug.index], target_aug else: return features_aug
""" Crypto trading bot using maching learning Triple barrier target @version: 0.3 """ import clr clr.AddReference("System") clr.AddReference("QuantConnect.Algorithm") clr.AddReference("QuantConnect.Common") from System import * from QuantConnect import * from QuantConnect.Algorithm import * from QuantConnect.Algorithm.Framework.Execution import * import pandas as pd pd.set_option('mode.use_inf_as_na', True) from sklearn.pipeline import Pipeline from sklearn.decomposition import PCA from sklearn.metrics import get_scorer from sklearn.metrics import confusion_matrix from sklearn.neural_network import MLPClassifier from sklearn.metrics import classification_report from sklearn.model_selection import RandomizedSearchCV from timeseriessplitgroups import TimeSeriesSplitGroups STEPS = [("pca", PCA()), ("mlp", MLPClassifier(n_iter_no_change=1, max_iter=100, solver="adam", early_stopping=True, warm_start=True, validation_fraction=0.2))] PARAMS = {"pca__n_components": [None, 0.9], "mlp__activation": ["logistic", "relu"], "mlp__alpha": [0.1, 0.01, 0.001, 0.0001, 0], "mlp__hidden_layer_sizes": [[96, ], [48, 48], [32, 32, 32]]} # TODO: Add Trailing Stop https://www.quantconnect.com/docs/algorithm-reference/trading-and-orders#Trading-and-Orders-Updating-Orders class MLCryptoAlgo(QCAlgorithm): def Initialize(self): self.SetStartDate(2018, 1, 1) self.SetCash(100000) self.SetBrokerageModel(BrokerageName.GDAX, AccountType.Cash) self.Settings.FreePortfolioValuePercentage = 0.05 self.lookbacks = [1, 7, 15, 30, 91, 182, 365] self.datapoints = 365 * 5 self.model = None self.limit_margin = 0.0 self.stop_margin = 0.01 self.take_profit = 0.01 self.resolution = Resolution.Daily self.SetBenchmark(SecurityType.Crypto, "BTCUSD") tickers = ["BTCUSD"] [self.AddCrypto(t, self.resolution, Market.GDAX) for t in tickers] self.pos_size = 1.0 / (len(tickers) * (1+self.limit_margin)) # Accounting for available cash self.Train(self.DateRules.MonthStart(), self.TimeRules.At(0, 0), self.train_model) self.Schedule.On(self.DateRules.EveryDay(), #self.TimeRules.Every(timedelta(minutes=60)), self.TimeRules.At(0, 0), self.trade) def train_model(self): """ Train model with new data, model is created if missing """ if self.model is None: cv = TimeSeriesSplitGroups(n_splits=10) self.model = RandomizedSearchCV(Pipeline(steps=STEPS), PARAMS, scoring="balanced_accuracy", cv=cv, n_iter=10, n_jobs=1) x, y = self.get_data(self.datapoints, include_y=True) if len(x) > 0 and len(y) > 0: groups = x.index.get_level_values("time") self.model.fit(x, y, groups=groups) self.Debug(confusion_matrix(y, self.model.predict(x))) self.Debug(classification_report(y, self.model.predict(x))) self.Plot("Model", "Bal. Accuracy", float(self.model.best_score_)) def trade(self): x = self.get_data(max(self.lookbacks) + 1, include_y=False) if len(x) > 0 and self.model is not None: y = pd.Series(self.model.predict(x), index=x.index) for symbol in self.ActiveSecurities.Keys: signal = y[str(symbol.ID)][0] if signal == 1: qty_order = self.CalculateOrderQuantity(symbol, self.pos_size) if qty_order > 0: price = self.Securities[symbol].Price limit = round(price * (1+self.limit_margin), 2) stop = round(price * (1-self.stop_margin), 2) self.StopLimitOrder(symbol, qty_order, stop, limit) elif signal == -1: self.SetHoldings(symbol, 0) def get_data(self, datapoints=1, include_y=True): tickers = list(self.ActiveSecurities.Keys) data = self.History(tickers, datapoints, self.resolution) data["volatility"] = data["high"] - data["low"] data["spread"] = data["askclose"] - data["bidclose"] data = data[["close", "volatility", "volume", "spread"]] groups = data.groupby("symbol") features = [groups.pct_change(p) for p in self.lookbacks] # Momentum features += [data / groups.apply(lambda x: x.rolling(p).mean()) for p in self.lookbacks] # Feats normalized by their average features = pd.concat(features, join="inner", axis="columns").dropna() if include_y: target = groups["close"].pct_change(1).shift(-1) target = target.reindex_like(features).dropna() target = target.apply(lambda x: +1 if x>self.take_profit else (-1 if x<-self.stop_margin else 0)) return features.loc[target.index], target else: return features
""" Trading bot using machine learning Testing different bars types @version: 0.17 """ import datetime import clr clr.AddReference("System") clr.AddReference("QuantConnect.Algorithm") clr.AddReference("QuantConnect.Common") from System import * from QuantConnect import * from QuantConnect.Algorithm import * from QuantConnect.Algorithm.Framework.Execution import * import random import pandas as pd pd.set_option('mode.use_inf_as_na', True) from sklearn.pipeline import Pipeline from sklearn.metrics import precision_score from sklearn.preprocessing import StandardScaler from sklearn.neural_network import MLPClassifier import features as ft from qcutils import SP500 STEPS = [("scaler", StandardScaler()), ("model", MLPClassifier(warm_start=True, max_iter=1, hidden_layer_sizes=(64, 64)))] class MLCryptoAlgo(QCAlgorithm): def Initialize(self): self.SetStartDate(2018, 1, 1) self.SetCash(100000) self.Settings.FreePortfolioValuePercentage = 0.05 self.resolution = Resolution.Daily self.bar_size = 24 random.seed(42) self.tickers = random.sample(SP500, 10) [self.AddEquity(t, self.resolution) for t in self.tickers] #self.AddEquity("SPY", self.resolution) self.periods = [1, 5, 21] self.train_days = timedelta(252 * 2) self.test_days = timedelta(252 * 1) self.test_start = None self.pos_size = 0.0 self.commissions = 0.0 self.model = Pipeline(steps=STEPS) self.Train(self.DateRules.MonthStart(), #self.DateRules.EveryDay(), self.TimeRules.At(0, 15), self.train_model) self.Schedule.On(self.DateRules.EveryDay(), self.TimeRules.At(10, 0), #self.TimeRules.Every(TimeSpan.FromHours(1)), self.trade) def train_model(self): """ Train model with new data, model is created if missing """ self.test_start = self.Time-self.test_days train_start = self.test_start-self.train_days x_train, y_train = self.get_data(train_start, self.test_start) self.model.fit(x_train, (y_train > 0)) x_test, y_test = self.get_data(self.test_start, self.Time) self.Log(f"Model score {self.model.score(x_test, (y_test > 0))}") self.calc_kelly(x_test, (y_test > 0), y_test) def calc_kelly(self, x, y, returns): """ Calculate info needed for Kelly position sizing """ winrate = precision_score(y, self.model.predict(x)) avgwin = max(returns[returns > 0].mean() - 2 * self.commissions, 0) avgloss = max(-returns[returns < 0].mean() + 2 * self.commissions, 0) self.pos_size = winrate / avgloss - (1 - winrate) / avgwin self.pos_size = min(max(self.pos_size, 0), 1) self.Debug(f"PT:{len(x)} WR:{winrate:.3f} PS:{self.pos_size:.3f} " f"AW:{avgwin:.4f} AL:{avgloss:.4f}") self.Plot("Model", "Win Rate", winrate) self.Plot("Model", "Win Loss Ratio", avgwin / avgloss) self.Plot("Model", "Kelly Position", self.pos_size) def trade(self): x_pred = self.get_data(self.test_start, self.Time, include_y=False) self.Transactions.CancelOpenOrders() if len(x_pred) > 0 and self.model is not None: x_pred = x_pred.sort_index().groupby("symbol").last() tickers = x_pred.index.get_level_values("symbol") pred = pd.Series(self.model.predict(x_pred), index=tickers) self.Log(f"Predictions\n{pred.to_string()}") self.Debug(f"{self.Time} - Predictions symbols {len(pred)} - {pred.mean():.2f}") for ticker in pred.index: target = self.pos_size / sum(pred) if pred[ticker] else 0 self.SetHoldings(ticker, target) def get_data(self, start, end, include_y=True): tickers = list(self.ActiveSecurities.Keys) history = self.History(tickers, start, end, self.resolution) #data = ft.volume_bars(history, bar_size=self.bar_size)[["close"]] data = history[["close"]] # calculate features indicators = [ft.momentum(data, self.periods), ft.strength(data, self.periods)] features = ft.join_indicators(indicators) target = data["close"].groupby("symbol").pct_change(1).shift(-1) if include_y: target = target.reindex_like(features).dropna() return features.loc[target.index], target else: return features
""" Crypto trading bot using machine learning Index with weights based on Machine Learning probabilities @version: 0.14 """ import clr clr.AddReference("System") clr.AddReference("QuantConnect.Algorithm") clr.AddReference("QuantConnect.Common") from System import * from QuantConnect import * from QuantConnect.Algorithm import * from QuantConnect.Algorithm.Framework.Execution import * import pandas as pd pd.set_option('mode.use_inf_as_na', True) from sklearn.decomposition import PCA from sklearn.pipeline import Pipeline from sklearn.preprocessing import StandardScaler from sklearn.neural_network import MLPClassifier from sklearn.model_selection import GridSearchCV from sklearn.linear_model import LogisticRegression from timeseriescv import PurgedTimeSeriesSplitGroups from sklearn.ensemble import GradientBoostingClassifier from sklearn.model_selection import train_test_split as ttsplit import features as ft STEPS = [("scaler", StandardScaler()), ("pca", PCA(n_components="mle")), ("model", LogisticRegression())] PARAMS = {"model": [MLPClassifier(n_iter_no_change=1, early_stopping=True, hidden_layer_sizes=(64, 64)), GradientBoostingClassifier(n_iter_no_change=1, max_depth=5), LogisticRegression()]} class MLCryptoAlgo(QCAlgorithm): def Initialize(self): self.SetStartDate(2018, 1, 1) self.SetCash(100000) self.SetBrokerageModel(BrokerageName.GDAX, AccountType.Cash) self.Settings.FreePortfolioValuePercentage = 0.05 self.resolution = Resolution.Hour self.bar_size = 24 self.tickers = ["BTCUSD", "ETHUSD", "LTCUSD", "EOSUSD", "XRPUSD", "ETCUSD", "BCHUSD", "ATOMUSD"] # NO ADA, DOT, SOL, XMR self.SetBenchmark(self.CustomBenchmark) [self.AddCrypto(t, self.resolution, Market.GDAX) for t in self.tickers] self.periods = [1, 7, 30] self.lookback = max(self.periods) self.datapoints = 365 * 1 self.possize = 0.0 self.commissions = 0.002 self.model = None self.Train(self.DateRules.MonthStart(), #self.DateRules.EveryDay(), self.TimeRules.At(0, 15), self.train_model) self.Schedule.On(self.DateRules.EveryDay(), self.TimeRules.At(0, 30), #self.TimeRules.Every(TimeSpan.FromHours(1)), self.trade) def CustomBenchmark(self, time): bmk = [self.Securities[ticker].Price for ticker in self.tickers] return sum(bmk)/len(self.tickers) def train_model(self): """ Train model with new data, model is created if missing """ x, y = self.get_data(self.datapoints, augmentation=2, include_y=True) if len(x) > 0 and len(y) > 0: if self.model is None: cv = PurgedTimeSeriesSplitGroups(n_splits=5, purge_groups=self.lookback) self.model = GridSearchCV(Pipeline(steps=STEPS), PARAMS, scoring="precision", cv=cv) groups = x.index.get_level_values("time") self.model.fit(x, (y > 0), groups=groups) self.Log(pd.DataFrame(self.model.cv_results_).to_string()) def trade(self): x = self.get_data(self.lookback*2+1, include_y=False) self.Transactions.CancelOpenOrders() if len(x) > 0 and self.model is not None: x = x.groupby("symbol").last() tickers = x.index.get_level_values("symbol") pred = pd.Series(self.model.predict_proba(x)[:,1], index=tickers) sizes = pred/pred.sum() self.Debug(f"Sizes {sizes}") self.Log(f"Predictions\n{pred.to_string()}") self.Debug(f"Prediction symbols {len(tickers)}") for ticker in pred.index: qty = self.CalculateOrderQuantity(ticker, sizes[ticker]) self.LimitOrder(ticker, qty, self.Securities[ticker].Price) def get_data(self, points=1, augmentation=1, include_y=True): tickers = list(self.ActiveSecurities.Keys) history = self.History(tickers, points * self.bar_size, self.resolution) # define data history["volatility"] = history["high"] - history["low"] history["spread"] = history["askclose"] - history["bidclose"] history["vwap"] = ft.vwap(history, self.lookback) history = history[["close", "volatility", "volume", "spread", "vwap"]] features_aug, target_aug = pd.DataFrame(), pd.Series() step_size = int(self.bar_size / augmentation) for step in range(0, self.bar_size, step_size): # consolidate data and shift it if there is augmentation groupers = [pd.Grouper(level="symbol"), pd.Grouper(level="time", base=step, freq=f"{self.bar_size}H")] consolidated = history.groupby(groupers) data = pd.concat([consolidated["close"].last(), consolidated["volatility"].mean(), consolidated["volume"].sum(), consolidated["vwap"].mean(), consolidated["spread"].mean()], join="inner", axis="columns").dropna() # calculate features indicators = [ft.momentum(data, self.periods), ft.strength(data, self.periods), ft.macd(data, zip(self.periods[:-1], self.periods[1:])), ft.minmax(data, self.periods[1:])] #new_indicators = [ft.diff(i, self.periods[1:]) for i in indicators] #new_indicators += [ft.std(i, p) for i in indicators for p in self.periods[1:]] #features = ft.join_indicators(indicators+new_indicators+[ft.time(data)]) features = ft.join_indicators(indicators+[ft.time(data)]) features_aug = features_aug.append(features) target = data["close"].groupby("symbol").pct_change(1).shift(-1) target_aug = target_aug.append(target) if include_y: target_aug = target_aug.reindex_like(features_aug).dropna() return features_aug.loc[target_aug.index], target_aug else: return features_aug
""" Crypto trading bot using maching learning Incorporating results from feature experiments @version: 0.8 """ import clr clr.AddReference("System") clr.AddReference("QuantConnect.Algorithm") clr.AddReference("QuantConnect.Common") from System import * from QuantConnect import * from QuantConnect.Algorithm import * from QuantConnect.Algorithm.Framework.Execution import * import pickle import pandas as pd pd.set_option('mode.use_inf_as_na', True) from sklearn.pipeline import Pipeline from sklearn.decomposition import PCA from sklearn.preprocessing import MinMaxScaler from sklearn.neural_network import MLPClassifier from sklearn.model_selection import GridSearchCV from sklearn.metrics import average_precision_score from timeseriescv import PurgedTimeSeriesSplitGroups from sklearn.linear_model import LogisticRegression from sklearn.ensemble import GradientBoostingClassifier from sklearn.model_selection import train_test_split as ttsplit STEPS = [("scaler", MinMaxScaler()), ("pca", PCA()), ("model", LogisticRegression())] PARAMS = {"pca__n_components": [1, 0.99], "model": [MLPClassifier(n_iter_no_change=1, early_stopping=True), GradientBoostingClassifier(n_iter_no_change=1), LogisticRegression()]} class MLCryptoAlgo(QCAlgorithm): def Initialize(self): self.SetStartDate(2018, 1, 1) self.SetCash(100000) self.SetBrokerageModel(BrokerageName.Bitfinex, AccountType.Cash) self.resolution = Resolution.Daily self.SetBenchmark(SecurityType.Crypto, "BTCUSD") self.tickers = ["BTCUSD", "ETHUSD", "LTCUSD", "EOSUSD", "XMRUSD", "XRPUSD"][:1] # NO BCH, ADA, DOT, ATOM [self.AddCrypto(t, self.resolution, Market.Bitfinex) for t in self.tickers] self.lookbacks = [1, 7, 15, 31, 63, 126, 252] self.datapoints = 365 * 5 self.pos_size = 0.0 self.commissions = 0.002 self.model = None self.model_key = "crypto_multi_daily" self.ObjectStore.Delete(self.model_key) if self.ObjectStore.ContainsKey(self.model_key): model_buffer = self.ObjectStore.ReadBytes(self.model_key) self.Log(f"Loading model {self.model_key}") self.model = pickle.loads(bytes(model_buffer)) self.Train(self.DateRules.WeekStart(), #self.DateRules.EveryDay(), self.TimeRules.At(0, 30), self.train_model) self.Schedule.On(self.DateRules.EveryDay(), self.TimeRules.At(0, 0), #self.TimeRules.Every(TimeSpan.FromHours(1)), self.trade) def train_model(self): """ Train model with new data, model is created if missing """ if self.model is None: cv = PurgedTimeSeriesSplitGroups(n_splits=10, purge_groups=max(self.lookbacks)) self.model = GridSearchCV(Pipeline(steps=STEPS), PARAMS, scoring="average_precision", cv=cv) x, y = self.get_data(self.datapoints, include_y=True) if len(x) > 0 and len(y) > 0: dates = x.index.get_level_values("time").sort_values() train_dates, test_dates = ttsplit(dates.unique(), shuffle=False) x_train = x[dates.isin(train_dates)] y_train = y[dates.isin(train_dates)] groups = x_train.index.get_level_values("time") self.model.fit(x_train, (y_train > 0), groups=groups) self.Log(pd.DataFrame(self.model.cv_results_)) self.ObjectStore.SaveBytes(self.model_key, pickle.dumps(self.model)) x_test = x[dates.isin(test_dates)] y_test = y[dates.isin(test_dates)] self.calc_kelly(x_test, (y_test > 0), y_test) def calc_kelly(self, x, y, returns): """ Calculate info needed for Kelly position sizing """ win_rate = average_precision_score(y, self.model.predict(x)) avg_win = max(returns[returns>0].mean()-2*self.commissions, 0) avg_loss = -returns[returns<0].mean()+2*self.commissions self.pos_size = min(max(win_rate/avg_loss-(1-win_rate)/avg_win, 0), 1) self.Plot("Model", "Win Rate", win_rate) self.Plot("Model", "Win Loss Ratio", avg_win/avg_loss) self.Plot("Model", "Kelly Position", self.pos_size) self.Debug(f"WR:{win_rate:.3f} PS:{self.pos_size:.3f} " f"AW:{avg_win:.4f} AL:{avg_loss:.4f}") def trade(self): x = self.get_data(max(self.lookbacks) + 1, include_y=False) if len(x) > 0 and self.model is not None: symbols = x.index.get_level_values("symbol") pred = pd.Series(self.model.predict(x), index=symbols) self.Log(f"Predictions\n{pred.to_string()}") for symbol in pred.index: if pred[symbol] == 1: self.SetHoldings(symbol, self.pos_size/len(self.tickers)) else: self.SetHoldings(symbol, 0) def get_data(self, datapoints=1, include_y=True): tickers = list(self.ActiveSecurities.Keys) data = self.History(tickers, datapoints, self.resolution) data["volatility"] = data["high"] - data["low"] data["spread"] = data["askclose"] - data["bidclose"] data = data[["close", "volatility", "volume", "spread"]] groups = data.groupby("symbol") features = [groups.pct_change(p) for p in self.lookbacks] # Momentum features += [data / groups.transform(lambda x: x.rolling(p).mean()) for p in self.lookbacks[1:]] # Strength features += [(data - groups.transform(lambda x: x.rolling(p).min()))/ (groups.transform(lambda x: x.rolling(p).max())- groups.transform(lambda x: x.rolling(p).min())) for p in self.lookbacks[1:]] # Min/max strength features = pd.concat(features, join="inner", axis="columns").dropna() self.Debug(f"Get Data {features.shape}") if include_y: target = groups["close"].pct_change(1).shift(-1) target = target.reindex_like(features).dropna() return features.loc[target.index], target else: return features
""" Crypto trading bot using machine learning Using online learning with Neural Network @version: 0.16 """ import datetime import clr clr.AddReference("System") clr.AddReference("QuantConnect.Algorithm") clr.AddReference("QuantConnect.Common") from System import * from QuantConnect import * from QuantConnect.Algorithm import * from QuantConnect.Algorithm.Framework.Execution import * import pandas as pd pd.set_option('mode.use_inf_as_na', True) from sklearn.pipeline import Pipeline from sklearn.metrics import precision_score from sklearn.preprocessing import StandardScaler from sklearn.neural_network import MLPClassifier import features as ft STEPS = [("scaler", StandardScaler()), ("model", MLPClassifier(warm_start=True, max_iter=10, hidden_layer_sizes=(64, 64)))] class MLCryptoAlgo(QCAlgorithm): def Initialize(self): self.SetStartDate(2018, 1, 1) self.SetCash(100000) self.SetBrokerageModel(BrokerageName.GDAX, AccountType.Cash) self.Settings.FreePortfolioValuePercentage = 0.05 self.resolution = Resolution.Daily self.tickers = ["BTCUSD", "ETHUSD", "LTCUSD", "XRPUSD", "BCHUSD"] self.SetBenchmark(self.CustomBenchmark) [self.AddCrypto(t, self.resolution, Market.GDAX) for t in self.tickers] self.periods = [1, 7, 30] self.train_days = timedelta(365 * 2) self.test_days = timedelta(365 * 1) self.test_start = None self.pos_size = 0.01 self.commissions = 0.002 self.limit_order = None self.model = Pipeline(steps=STEPS) self.Train(self.DateRules.MonthStart(), #self.DateRules.EveryDay(), self.TimeRules.At(0, 15), self.train_model) self.Schedule.On(self.DateRules.EveryDay(), self.TimeRules.At(0, 30), #self.TimeRules.Every(TimeSpan.FromHours(1)), self.trade) def CustomBenchmark(self, time): bmk = [self.Securities[ticker].Price for ticker in self.tickers] return sum(bmk)/len(self.tickers) def train_model(self): """ Train model with new data, model is created if missing """ self.test_start = self.Time-self.test_days train_start = self.test_start-self.train_days x_train, y_train = self.get_data(train_start, self.test_start) self.model.fit(x_train, (y_train > 0)) x_test, y_test = self.get_data(self.test_start, self.Time) self.Log(f"Model score {self.model.score(x_test, (y_test > 0))}") self.calc_kelly(x_test, (y_test > 0), y_test) def calc_kelly(self, x, y, returns): """ Calculate info needed for Kelly position sizing """ winrate = precision_score(y, self.model.predict(x)) avgwin = max(returns[returns > 0].mean() - 2 * self.commissions, 0) avgloss = max(-returns[returns < 0].mean() + 2 * self.commissions, 0) self.pos_size = winrate / avgloss - (1 - winrate) / avgwin self.pos_size = min(max(self.pos_size, 0), 1) self.Debug(f"PT:{len(x)} WR:{winrate:.3f} PS:{self.pos_size:.3f} " f"AW:{avgwin:.4f} AL:{avgloss:.4f}") self.Plot("Model", "Win Rate", winrate) self.Plot("Model", "Win Loss Ratio", avgwin / avgloss) self.Plot("Model", "Kelly Position", self.pos_size) def trade(self): x_pred = self.get_data(self.test_start, self.Time, include_y=False) self.Transactions.CancelOpenOrders() if len(x_pred) > 0 and self.model is not None: x_pred = x_pred.sort_index().groupby("symbol").last() tickers = x_pred.index.get_level_values("symbol") pred = pd.Series(self.model.predict(x_pred), index=tickers) self.Log(f"Predictions\n{pred.to_string()}") self.Debug(f"{self.Time} - Predictions symbols {len(pred)}") for ticker in pred.index: target = self.pos_size / sum(pred) if pred[ticker] else 0 qty = self.CalculateOrderQuantity(ticker, target) last_price = self.Securities[ticker].Price if qty>0 and self.limit_order is not None: limit_price = last_price * (1 + self.limit_order) self.LimitOrder(ticker, qty, limit_price) else: self.MarketOrder(ticker, qty) def get_data(self, start, end, include_y=True): tickers = list(self.ActiveSecurities.Keys) history = self.History(tickers, start, end, self.resolution) data = history[["close", "volume"]] # calculate features indicators = [ft.momentum(data, self.periods), ft.strength(data, self.periods)] features = ft.join_indicators(indicators) target = data["close"].groupby("symbol").pct_change(1).shift(-1) if include_y: target = target.reindex_like(features).dropna() return features.loc[target.index], target else: return features
""" Crypto trading bot using maching learning Minimalist version @version: 0.10 """ import clr clr.AddReference("System") clr.AddReference("QuantConnect.Algorithm") clr.AddReference("QuantConnect.Common") from System import * from QuantConnect import * from QuantConnect.Algorithm import * from QuantConnect.Algorithm.Framework.Execution import * import pandas as pd pd.set_option('mode.use_inf_as_na', True) import features as ft from sklearn.pipeline import Pipeline from sklearn.decomposition import PCA from sklearn.metrics import precision_score from sklearn.preprocessing import MinMaxScaler from sklearn.neural_network import MLPClassifier from sklearn.model_selection import GridSearchCV from sklearn.linear_model import LogisticRegression from timeseriescv import PurgedTimeSeriesSplitGroups from sklearn.ensemble import GradientBoostingClassifier from sklearn.model_selection import train_test_split as ttsplit STEPS = [("scaler", MinMaxScaler()), ("model", LogisticRegression())] PARAMS = {"model": [MLPClassifier(n_iter_no_change=5, early_stopping=True, hidden_layer_sizes=(64, 64)), GradientBoostingClassifier(n_iter_no_change=5, max_depth=5), LogisticRegression()]} class MLCryptoAlgo(QCAlgorithm): def Initialize(self): self.SetStartDate(2018, 1, 1) self.SetCash(100000) self.SetBrokerageModel(BrokerageName.GDAX, AccountType.Cash) self.resolution = Resolution.Daily self.tickers = ["BTCUSD", "ETHUSD", "LTCUSD", "EOSUSD", "XRPUSD", "ETCUSD", "BCHUSD", "ATOMUSD"] # NO ADA, DOT, SOL, XMR self.SetBenchmark(self.CustomBenchmark) [self.AddCrypto(t, self.resolution, Market.GDAX) for t in self.tickers] self.periods = [1, 7, 14, 21, 28] self.lookback = max(self.periods) self.datapoints = 365 * 5 self.pos_size = 0.0 self.commissions = 0.002 self.model = None self.Train(self.DateRules.WeekStart(), #self.DateRules.EveryDay(), self.TimeRules.At(0, 30), self.train_model) self.Schedule.On(self.DateRules.EveryDay(), self.TimeRules.At(1, 0), #self.TimeRules.Every(TimeSpan.FromHours(1)), self.trade) def CustomBenchmark(self, time): bmk = [self.Securities[ticker].Price for ticker in self.tickers] return sum(bmk)/len(self.tickers) def train_model(self): """ Train model with new data, model is created if missing """ x, y = self.get_data(self.datapoints, include_y=True) if len(x) > 0 and len(y) > 0: dates = x.index.get_level_values("time").sort_values() train_dates, test_dates = ttsplit(dates.unique(), shuffle=False) x_train = x[dates.isin(train_dates)] y_train = y[dates.isin(train_dates)] if self.model is None: cv = PurgedTimeSeriesSplitGroups(n_splits=10, purge_groups=self.lookback) self.model = GridSearchCV(Pipeline(steps=STEPS), PARAMS, scoring="precision", cv=cv) groups = x_train.index.get_level_values("time") self.model.fit(x_train, (y_train > 0), groups=groups) self.Log(pd.DataFrame(self.model.cv_results_).to_string()) x_test = x[dates.isin(test_dates)] y_test = y[dates.isin(test_dates)] self.calc_kelly(x_test, (y_test > 0), y_test) def calc_kelly(self, x, y, returns): """ Calculate info needed for Kelly position sizing """ win_rate = precision_score(y, self.model.predict(x)) avg_win = max(returns[returns>0].mean()-2*self.commissions, 0) avg_loss = max(-returns[returns<0].mean()+2*self.commissions, 0) self.pos_size = min(max(win_rate/avg_loss-(1-win_rate)/avg_win, 0), 1) self.Plot("Model", "Win Rate", win_rate) self.Plot("Model", "Win Loss Ratio", avg_win/avg_loss) self.Plot("Model", "Kelly Position", self.pos_size) self.Debug(f"PT:{len(x)} WR:{win_rate:.3f} PS:{self.pos_size:.3f} " f"AW:{avg_win:.4f} AL:{avg_loss:.4f}") def trade(self): x = self.get_data(self.lookback + 1, include_y=False) if len(x) > 0 and self.model is not None: symbols = x.index.get_level_values("symbol") pred = pd.Series(self.model.predict(x), index=symbols) self.Log(f"Predictions\n{pred.to_string()}") self.Debug(f"Prediction symbols {len(symbols)}") for symbol in pred.index: self.SetHoldings(symbol, self.pos_size/len(symbols)) \ if pred[symbol] == 1 else self.SetHoldings(symbol, 0) def get_data(self, datapoints=1, include_y=True): tickers = list(self.ActiveSecurities.Keys) data = self.History(tickers, datapoints, self.resolution) # define data data["volatility"] = data["high"] - data["low"] data["spread"] = data["askclose"] - data["bidclose"] price_mean = data[["open", "high", "low", "close"]].mean(axis=1) price_volume = price_mean*data["volume"] price_volume_sum = price_volume.rolling(self.lookback).sum() volume_sum = data["volume"].rolling(self.lookback).sum() data["vwap"] = price_volume_sum/volume_sum data = data[["close", "volatility", "volume", "spread", "vwap"]] # calculate features features = ft.momentum(data, self.periods) features += ft.strength(data, self.periods) features += ft.macd(data, zip(self.periods[:-1], self.periods[1:])) features += ft.minmax(data, self.periods[1:]) norm_feats = ["volatility", "spread", "vwap"] data[norm_feats] = data[norm_feats].divide(data["close"], axis=0) data["close"] = data["close"].groupby("symbol").pct_change(1) data["volume"] = data["volume"].groupby("symbol").pct_change(1) features += ft.sequence(data, self.lookback) features = pd.concat(features, join="inner", axis="columns").dropna() times = features.index.get_level_values("time") # TODO: Add to indicators features["month"] = times.month features["day"] = times.day / times.days_in_month features["weekday"] = times.weekday if include_y: target = data["close"].groupby("symbol").shift(-1) target = target.reindex_like(features).dropna() return features.loc[target.index], target else: return features
""" Crypto trading bot using machine learning Using online learning with Neural Network @version: 0.16 """ import datetime import clr clr.AddReference("System") clr.AddReference("QuantConnect.Algorithm") clr.AddReference("QuantConnect.Common") from System import * from QuantConnect import * from QuantConnect.Algorithm import * from QuantConnect.Algorithm.Framework.Execution import * import random import pandas as pd pd.set_option('mode.use_inf_as_na', True) from sklearn.pipeline import Pipeline from sklearn.metrics import precision_score from sklearn.preprocessing import StandardScaler from sklearn.neural_network import MLPClassifier import features as ft from qcutils import SP500 STEPS = [("scaler", StandardScaler()), ("model", MLPClassifier(warm_start=True, max_iter=1, hidden_layer_sizes=(64, 64)))] class MLCryptoAlgo(QCAlgorithm): def Initialize(self): self.SetStartDate(2018, 1, 1) self.SetCash(100000) self.Settings.FreePortfolioValuePercentage = 0.05 self.resolution = Resolution.Daily random.seed(42) self.tickers = random.sample(SP500, 10) [self.AddEquity(t, self.resolution) for t in self.tickers] #self.AddEquity("SPY", self.resolution) self.periods = [1, 5, 21] self.train_days = timedelta(252 * 2) self.test_days = timedelta(252 * 1) self.test_start = None self.pos_size = 0.0 self.commissions = 0.0 self.limit_order = None self.model = Pipeline(steps=STEPS) self.Train(self.DateRules.MonthStart(), #self.DateRules.EveryDay(), self.TimeRules.At(0, 15), self.train_model) self.Schedule.On(self.DateRules.EveryDay(), self.TimeRules.At(10, 0), #self.TimeRules.Every(TimeSpan.FromHours(1)), self.trade) def train_model(self): """ Train model with new data, model is created if missing """ self.test_start = self.Time-self.test_days train_start = self.test_start-self.train_days x_train, y_train = self.get_data(train_start, self.test_start) self.model.fit(x_train, (y_train > 0)) x_test, y_test = self.get_data(self.test_start, self.Time) self.Log(f"Model score {self.model.score(x_test, (y_test > 0))}") self.calc_kelly(x_test, (y_test > 0), y_test) def calc_kelly(self, x, y, returns): """ Calculate info needed for Kelly position sizing """ winrate = precision_score(y, self.model.predict(x)) avgwin = max(returns[returns > 0].mean() - 2 * self.commissions, 0) avgloss = max(-returns[returns < 0].mean() + 2 * self.commissions, 0) self.pos_size = winrate / avgloss - (1 - winrate) / avgwin self.pos_size = min(max(self.pos_size, 0), 1) self.Debug(f"PT:{len(x)} WR:{winrate:.3f} PS:{self.pos_size:.3f} " f"AW:{avgwin:.4f} AL:{avgloss:.4f}") self.Plot("Model", "Win Rate", winrate) self.Plot("Model", "Win Loss Ratio", avgwin / avgloss) self.Plot("Model", "Kelly Position", self.pos_size) def trade(self): x_pred = self.get_data(self.test_start, self.Time, include_y=False) self.Transactions.CancelOpenOrders() if len(x_pred) > 0 and self.model is not None: x_pred = x_pred.sort_index().groupby("symbol").last() tickers = x_pred.index.get_level_values("symbol") pred = pd.Series(self.model.predict(x_pred), index=tickers) self.Log(f"Predictions\n{pred.to_string()}") self.Debug(f"{self.Time} - Predictions symbols {len(pred)} - {pred.mean():.2f}") for ticker in pred.index: target = self.pos_size / sum(pred) if pred[ticker] else 0 qty = self.CalculateOrderQuantity(ticker, target) last_price = self.Securities[ticker].Price if qty>0 and self.limit_order is not None: limit_price = last_price * (1 + self.limit_order) self.LimitOrder(ticker, qty, limit_price) else: self.MarketOrder(ticker, qty) def get_data(self, start, end, include_y=True): tickers = list(self.ActiveSecurities.Keys) history = self.History(tickers, start, end, self.resolution) data = history[["close", "volume"]] # calculate features indicators = [ft.momentum(data, self.periods), ft.strength(data, self.periods)] features = ft.join_indicators(indicators) target = data["close"].groupby("symbol").pct_change(1).shift(-1) if include_y: target = target.reindex_like(features).dropna() return features.loc[target.index], target else: return features
""" Trading bot using machine learning Testing different bars types @version: 0.17 """ import datetime import clr clr.AddReference("System") clr.AddReference("QuantConnect.Algorithm") clr.AddReference("QuantConnect.Common") from System import * from QuantConnect import * from QuantConnect.Algorithm import * from QuantConnect.Algorithm.Framework.Execution import * import random import pandas as pd pd.set_option('mode.use_inf_as_na', True) from sklearn.pipeline import Pipeline from sklearn.metrics import precision_score from sklearn.preprocessing import StandardScaler from sklearn.neural_network import MLPClassifier import features as ft from qcutils import SP500 STEPS = [("scaler", StandardScaler()), ("model", MLPClassifier(warm_start=True, max_iter=1, hidden_layer_sizes=(64, 64)))] class MLCryptoAlgo(QCAlgorithm): def Initialize(self): self.SetStartDate(2018, 1, 1) self.SetCash(100000) self.Settings.FreePortfolioValuePercentage = 0.05 self.resolution = Resolution.Hour self.bar_size = 24 random.seed(42) self.tickers = random.sample(SP500, 10) [self.AddEquity(t, self.resolution) for t in self.tickers] #self.AddEquity("SPY", self.resolution) self.periods = [1, 5, 21] self.train_days = timedelta(252 * 2) self.test_days = timedelta(252 * 1) self.test_start = None self.pos_size = 0.0 self.commissions = 0.0 self.limit_order = None self.model = Pipeline(steps=STEPS) self.Train(self.DateRules.MonthStart(), #self.DateRules.EveryDay(), self.TimeRules.At(0, 15), self.train_model) self.Schedule.On(self.DateRules.EveryDay(), self.TimeRules.At(10, 0), #self.TimeRules.Every(TimeSpan.FromHours(1)), self.trade) def train_model(self): """ Train model with new data, model is created if missing """ self.test_start = self.Time-self.test_days train_start = self.test_start-self.train_days x_train, y_train = self.get_data(train_start, self.test_start) self.model.fit(x_train, (y_train > 0)) x_test, y_test = self.get_data(self.test_start, self.Time) self.Log(f"Model score {self.model.score(x_test, (y_test > 0))}") self.calc_kelly(x_test, (y_test > 0), y_test) def calc_kelly(self, x, y, returns): """ Calculate info needed for Kelly position sizing """ winrate = precision_score(y, self.model.predict(x)) avgwin = max(returns[returns > 0].mean() - 2 * self.commissions, 0) avgloss = max(-returns[returns < 0].mean() + 2 * self.commissions, 0) self.pos_size = winrate / avgloss - (1 - winrate) / avgwin self.pos_size = min(max(self.pos_size, 0), 1) self.Debug(f"PT:{len(x)} WR:{winrate:.3f} PS:{self.pos_size:.3f} " f"AW:{avgwin:.4f} AL:{avgloss:.4f}") self.Plot("Model", "Win Rate", winrate) self.Plot("Model", "Win Loss Ratio", avgwin / avgloss) self.Plot("Model", "Kelly Position", self.pos_size) def trade(self): x_pred = self.get_data(self.test_start, self.Time, include_y=False) self.Transactions.CancelOpenOrders() if len(x_pred) > 0 and self.model is not None: x_pred = x_pred.sort_index().groupby("symbol").last() tickers = x_pred.index.get_level_values("symbol") pred = pd.Series(self.model.predict(x_pred), index=tickers) self.Log(f"Predictions\n{pred.to_string()}") self.Debug(f"{self.Time} - Predictions symbols {len(pred)} - {pred.mean():.2f}") for ticker in pred.index: target = self.pos_size / sum(pred) if pred[ticker] else 0 qty = self.CalculateOrderQuantity(ticker, target) last_price = self.Securities[ticker].Price if qty>0 and self.limit_order is not None: limit_price = last_price * (1 + self.limit_order) self.LimitOrder(ticker, qty, limit_price) else: self.MarketOrder(ticker, qty) def get_data(self, start, end, include_y=True): tickers = list(self.ActiveSecurities.Keys) history = self.History(tickers, start, end, self.resolution) data = ft.volume_bars(history, bar_size=self.bar_size) data = data[["close", "volume"]] # calculate features indicators = [ft.momentum(data, self.periods), ft.strength(data, self.periods)] features = ft.join_indicators(indicators) target = data["close"].groupby("symbol").pct_change(1).shift(-1) if include_y: target = target.reindex_like(features).dropna() return features.loc[target.index], target else: return features
""" Crypto trading bot using maching learning Implementing Kelly criterion @version: 0.5 """ import clr clr.AddReference("System") clr.AddReference("QuantConnect.Algorithm") clr.AddReference("QuantConnect.Common") from System import * from QuantConnect import * from QuantConnect.Algorithm import * from QuantConnect.Algorithm.Framework.Execution import * import pickle import pandas as pd pd.set_option('mode.use_inf_as_na', True) from sklearn.pipeline import Pipeline from sklearn.decomposition import PCA from sklearn.neural_network import MLPClassifier from sklearn.metrics import classification_report from sklearn.model_selection import train_test_split from sklearn.model_selection import RandomizedSearchCV from timeseriessplitgroups import TimeSeriesSplitGroups from sklearn.metrics import get_scorer, average_precision_score STEPS = [("pca", PCA()), ("mlp", MLPClassifier(n_iter_no_change=1, max_iter=100, solver="adam", early_stopping=True, warm_start=True, validation_fraction=0.2))] PARAMS = {"pca__n_components": [None, 0.9], "mlp__activation": ["logistic", "relu"], "mlp__alpha": [0.1, 0.01, 0.001, 0.0001, 0], "mlp__hidden_layer_sizes": [[96, ], [48, 48], [32, 32, 32]]} class MLCryptoAlgo(QCAlgorithm): def Initialize(self): self.SetStartDate(2019, 1, 1) self.SetCash(100000) self.SetBrokerageModel(BrokerageName.Bitfinex, AccountType.Cash) self.Settings.FreePortfolioValuePercentage = 0.05 self.resolution = Resolution.Daily self.SetBenchmark(SecurityType.Crypto, "BTCUSD") #tickers = ["BTCUSD", "ETHUSD", "LTCUSD", # "EOSUSD", "XMRUSD", "XRPUSD"] # NO BCH, ADA, DOT, ATOM tickers = ["BTCUSD"] [self.AddCrypto(t, self.resolution, Market.Bitfinex) for t in tickers] self.lookbacks = [1, 7, 15, 30, 90] self.datapoints = 365 * 5 self.commissions = 0.005 self.model = None self.model_key = "crypto_btc" if self.ObjectStore.ContainsKey(self.model_key): model_buffer = self.ObjectStore.ReadBytes(self.model_key) self.Log(f"Loading model {self.model_key}") self.model = pickle.loads(bytes(model_buffer)) self.pos_size = 1.0/len(tickers) self.Train(self.DateRules.WeekStart(), self.TimeRules.At(0, 0), self.train_model) self.Schedule.On(self.DateRules.EveryDay(), self.TimeRules.At(0, 0), self.trade) def train_model(self): """ Train model with new data, model is created if missing """ if self.model is None: cv = TimeSeriesSplitGroups(n_splits=10) self.model = RandomizedSearchCV(Pipeline(steps=STEPS), PARAMS, scoring="average_precision", cv=cv, n_iter=10, n_jobs=1) x, y = self.get_data(self.datapoints, include_y=True) if len(x) > 0 and len(y) > 0: x_train, x_test, y_train, y_test = train_test_split(x, y, shuffle=False) groups = x_train.index.get_level_values("time") self.model.fit(x_train, (y_train>0), groups=groups) self.ObjectStore.SaveBytes(self.model_key, pickle.dumps(self.model)) self.calc_kelly(x_test, (y_test>0), y_test) self.Log(classification_report((y_test>0), self.model.predict(x_test))) self.Plot("Model", "Precision", float(self.model.best_score_)) def calc_kelly(self, x, y, returns): """ Calculate info needed for Kelly position sizing """ y_pred = self.model.predict(x) win_rate = average_precision_score(y_pred, y) #self.Plot("Model", "Win Rate", float(win_rate)) avg_gain = returns[returns>0].mean()-self.commissions avg_loss = -(returns[returns<0].mean()-self.commissions) win_loss_ratio = avg_gain/avg_loss self.pos_size = max(win_rate-(1-win_rate)/win_loss_ratio, 0) symbols_nr = len(y.index.get_level_values("symbol").unique()) self.pos_size = min(self.pos_size, 1.0/symbols_nr) self.Log(f"Win Rate {win_rate} - Pos Size {self.pos_size}") #self.Plot("Model", "Kelly Size", float(self.pos_size)) def trade(self): x = self.get_data(max(self.lookbacks) + 1, include_y=False) if len(x) > 0 and self.model is not None: symbols = x.index.get_level_values("symbol") pred = pd.Series(self.model.predict(x), index=symbols).sort_values() for symbol in pred.index: self.Log(f"Signal for {symbol}: {pred[symbol]}") if pred[symbol]==1: self.SetHoldings(symbol, self.pos_size) elif pred[symbol]==-1: self.SetHoldings(symbol, 0) def get_data(self, datapoints=1, include_y=True): tickers = list(self.ActiveSecurities.Keys) data = self.History(tickers, datapoints, self.resolution) data["volatility"] = data["high"] - data["low"] data["spread"] = data["askclose"] - data["bidclose"] data = data[["close", "volatility", "volume", "spread"]] groups = data.groupby("symbol") features = [groups.pct_change(p) for p in self.lookbacks] # Momentum features += [data / groups.apply(lambda x: x.rolling(p).mean()) for p in self.lookbacks] # Feats normalized by their average features = pd.concat(features, join="inner", axis="columns").dropna() if include_y: target = groups["close"].pct_change(1).shift(-1) target = target.reindex_like(features).dropna() return features.loc[target.index], target else: return features
""" Crypto trading bot using maching learning Limit and Stop loss order @version: 0.2 """ import clr clr.AddReference("System") clr.AddReference("QuantConnect.Algorithm") clr.AddReference("QuantConnect.Common") from System import * from QuantConnect import * from QuantConnect.Algorithm import * from QuantConnect.Algorithm.Framework.Execution import * import pandas as pd pd.set_option('mode.use_inf_as_na', True) from sklearn.pipeline import Pipeline from sklearn.decomposition import PCA from sklearn.neural_network import MLPClassifier from sklearn.model_selection import RandomizedSearchCV from timeseriessplitgroups import TimeSeriesSplitGroups STEPS = [("pca", PCA()), ("mlp", MLPClassifier(n_iter_no_change=1, max_iter=100, solver="adam", early_stopping=True, warm_start=True, validation_fraction=0.2))] PARAMS = {"pca__n_components": [None, 0.9], "mlp__activation": ["logistic", "relu"], "mlp__alpha": [0.1, 0.01, 0.001, 0.0001, 0], "mlp__hidden_layer_sizes": [[96, ], [48, 48], [32, 32, 32]]} # TODO: Add Trailing Stop https://www.quantconnect.com/docs/algorithm-reference/trading-and-orders#Trading-and-Orders-Updating-Orders class MLCryptoAlgo(QCAlgorithm): def Initialize(self): self.SetStartDate(2018, 1, 1) self.SetCash(100000) self.SetBrokerageModel(BrokerageName.GDAX, AccountType.Cash) self.lookbacks = [1, 8, 24, 24*7, 24*30] self.datapoints = 365 * 24 self.model = None self.limit_margin = 0.01 self.stop_margin = 0.01 self.resolution = Resolution.Hour self.SetBenchmark(SecurityType.Crypto, "BTCUSD") tickers = ["BTCUSD"] [self.AddCrypto(t, self.resolution, Market.GDAX) for t in tickers] self.pos_size = 1.0 / (len(tickers) * (1+self.limit_margin)) # Accounting for available cash self.Train(self.DateRules.MonthStart(), self.TimeRules.At(0, 0), self.train_model) self.Schedule.On(self.DateRules.EveryDay(), self.TimeRules.Every(timedelta(minutes=60)), #self.TimeRules.At(1, 0), self.trade) def train_model(self): """ Train model with new data, model is created if missing """ if self.model is None: cv = TimeSeriesSplitGroups(n_splits=10) self.model = RandomizedSearchCV(Pipeline(steps=STEPS), PARAMS, scoring="accuracy", cv=cv, n_iter=10, n_jobs=1) x, y = self.get_data(self.datapoints, include_y=True) if len(x) > 0 and len(y) > 0: groups = x.index.get_level_values("time") self.model.fit(x, y, groups=groups) self.Plot("Model", "Accuracy", float(self.model.best_score_)) def trade(self): self.Transactions.CancelOpenOrders() x = self.get_data(max(self.lookbacks) + 1, include_y=False) if len(x) > 0 and self.model is not None: y = pd.Series(self.model.predict_proba(x)[:, 1], index=x.index) to_buy = y[y >= 0.5].index.get_level_values("symbol") for symbol in self.ActiveSecurities.Keys: if str(symbol.ID) in to_buy: pos_size, side = self.pos_size, +1 else: pos_size, side = 0, -1 qty_order = self.CalculateOrderQuantity(symbol, pos_size) if qty_order != 0: price = self.Securities[symbol].Price limit = round(price * (1+side*self.limit_margin), 2) stop = round(price * (1-side*self.stop_margin), 2) self.StopLimitOrder(symbol, qty_order, stop, limit) def get_data(self, datapoints=1, include_y=True): tickers = list(self.ActiveSecurities.Keys) data = self.History(tickers, datapoints, self.resolution) data["volatility"] = data["high"] - data["low"] data["spread"] = data["askclose"] - data["bidclose"] data = data[["close", "volatility", "volume", "spread"]] groups = data.groupby("symbol") features = [groups.pct_change(p) for p in self.lookbacks] # Momentum features += [data / groups.apply(lambda x: x.rolling(p).mean()) for p in self.lookbacks] # Feats normalized by their average features = pd.concat(features, join="inner", axis="columns").dropna() if include_y: target = groups["close"].pct_change(1).shift(-1) target = target.reindex_like(features).dropna() return features.loc[target.index], (target > 0).astype("float") else: return features
""" Crypto trading bot using maching learning @version: 0.1 """ import clr clr.AddReference("System") clr.AddReference("QuantConnect.Algorithm") clr.AddReference("QuantConnect.Common") from System import * from QuantConnect import * from QuantConnect.Algorithm import * import pandas as pd pd.set_option('mode.use_inf_as_na', True) from sklearn.pipeline import Pipeline from sklearn.decomposition import PCA from sklearn.neural_network import MLPClassifier from sklearn.model_selection import RandomizedSearchCV from timeseriessplitgroups import TimeSeriesSplitGroups STEPS = [("pca", PCA()), ("mlp", MLPClassifier(n_iter_no_change=1, max_iter=100, solver="adam", early_stopping=True, warm_start=True, validation_fraction=0.2))] PARAMS = {"pca__n_components": [None, 0.9], "mlp__activation": ["logistic", "relu"], "mlp__alpha": [0.1, 0.01, 0.001, 0.0001, 0], "mlp__hidden_layer_sizes": [[96, ], [48, 48], [32, 32, 32]]} class MLCryptoAlgo(QCAlgorithm): def Initialize(self): self.SetStartDate(2018, 1, 1) self.SetCash(100000) self.SetBrokerageModel(BrokerageName.GDAX, AccountType.Cash) self.lookbacks = [1, 2, 4, 8, 24, 24*7, 24*15, 24*30] self.datapoints = 24 * 365 self.model = None self.resolution = Resolution.Daily self.SetBenchmark(SecurityType.Crypto, "BTCUSD") tickers = ["BTCUSD"] [self.AddCrypto(ticker, self.resolution, Market.GDAX) for ticker in tickers] self.position_size = 1.0/len(tickers) self.Train(self.DateRules.MonthStart(), self.TimeRules.At(0, 0), self.train_model) self.Schedule.On(self.DateRules.EveryDay(), self.TimeRules.Every(timedelta(minutes=60)), #self.TimeRules.At(10,0,0), self.trade) def train_model(self): """ Train model with new data, model is created if missing """ if self.model is None: cv = TimeSeriesSplitGroups(n_splits=10) self.model = RandomizedSearchCV(Pipeline(steps=STEPS), PARAMS, scoring="accuracy", cv=cv, n_iter=10, n_jobs=1) x, y = self.get_data(self.datapoints, include_y=True) if len(x)>0 and len(y)>0: groups = x.index.get_level_values("time") self.model.fit(x, y, groups=groups) self.Plot("Model", "Accuracy", float(self.model.best_score_)) self.Debug(f"{self.Time} Model {self.model.best_score_:.1%}") def trade(self): x = self.get_data(max(self.lookbacks) + 1, include_y=False) if len(x) > 0: y = pd.Series(self.model.predict_proba(x)[:, 1], index=x.index, name="Signal") to_buy = y[y >= 0.5].index.get_level_values("symbol") for symbol in self.ActiveSecurities.Keys: self.SetHoldings(symbol, self.position_size) if str(symbol.ID) in to_buy else self.Liquidate(symbol) def get_data(self, datapoints=1, include_y=True): tickers = list(self.ActiveSecurities.Keys) data = self.History(tickers, datapoints, self.resolution) data["volatility"] = data["high"] - data["low"] data["spread"] = data["askclose"] - data["bidclose"] data = data[["close", "volatility", "volume", "spread"]] groups = data.groupby("symbol") features = [groups.pct_change(p) for p in self.lookbacks] # Momentum features += [data/groups.apply(lambda x: x.rolling(p).mean()) for p in self.lookbacks] # Feats normalized by their average features = pd.concat(features, join="inner", axis="columns").dropna() if include_y: target = groups["close"].pct_change(1).shift(-1) target = target.reindex_like(features).dropna() return features.loc[target.index], (target > 0).astype("float") else: return features
""" Crypto trading bot using maching learning Minimalist version @version: 0.9 """ import clr clr.AddReference("System") clr.AddReference("QuantConnect.Algorithm") clr.AddReference("QuantConnect.Common") from System import * from QuantConnect import * from QuantConnect.Algorithm import * from QuantConnect.Algorithm.Framework.Execution import * import pandas as pd pd.set_option('mode.use_inf_as_na', True) from sklearn.pipeline import Pipeline from sklearn.decomposition import PCA from sklearn.metrics import precision_score from sklearn.preprocessing import MinMaxScaler from sklearn.neural_network import MLPClassifier from sklearn.model_selection import GridSearchCV from sklearn.linear_model import LogisticRegression from timeseriescv import PurgedTimeSeriesSplitGroups from sklearn.ensemble import GradientBoostingClassifier from sklearn.model_selection import train_test_split as ttsplit STEPS = [("scaler", MinMaxScaler()), ("pca", PCA()), ("model", LogisticRegression())] PARAMS = {"pca__n_components": [1, 0.99], "model": [MLPClassifier(n_iter_no_change=5, early_stopping=True, hidden_layer_sizes=(100,100)), GradientBoostingClassifier(n_iter_no_change=5, max_depth=5), LogisticRegression()]} class MLCryptoAlgo(QCAlgorithm): def Initialize(self): self.SetStartDate(2018, 1, 1) self.SetCash(100000) self.SetBrokerageModel(BrokerageName.Bitfinex, AccountType.Cash) self.resolution = Resolution.Daily self.tickers = ["BTCUSD", "ETHUSD", "LTCUSD", "EOSUSD", "XMRUSD", "XRPUSD", "XLMUSD", "TRXUSD", "ETCUSD", "DAIUSD"] # NO BCH, ADA, DOT, ATOM self.SetBenchmark(self.CustomBenchmark) [self.AddCrypto(t, self.resolution, Market.Bitfinex) for t in self.tickers] self.lookbacks = [1, 3, 5, 7, 15, 30] self.datapoints = 365 * 1 self.pos_size = 0.0 self.commissions = 0.002 self.model = None self.Train(self.DateRules.WeekStart(), #self.DateRules.EveryDay(), self.TimeRules.At(0, 30), self.train_model) self.Schedule.On(self.DateRules.EveryDay(), self.TimeRules.At(1, 0), #self.TimeRules.Every(TimeSpan.FromHours(1)), self.trade) def CustomBenchmark(self, time): bmk = [self.Securities[ticker].Price for ticker in self.tickers] return sum(bmk)/len(self.tickers) def train_model(self): """ Train model with new data, model is created if missing """ x, y = self.get_data(self.datapoints, include_y=True) if len(x) > 0 and len(y) > 0: dates = x.index.get_level_values("time").sort_values() train_dates, test_dates = ttsplit(dates.unique(), shuffle=False) x_train = x[dates.isin(train_dates)] y_train = y[dates.isin(train_dates)] if self.model is None: cv = PurgedTimeSeriesSplitGroups(n_splits=10, purge_groups=max(self.lookbacks)) self.model = GridSearchCV(Pipeline(steps=STEPS), PARAMS, scoring="precision", cv=cv) groups = x_train.index.get_level_values("time") self.model.fit(x_train, (y_train > 0), groups=groups) self.Log(pd.DataFrame(self.model.cv_results_).to_string()) x_test = x[dates.isin(test_dates)] y_test = y[dates.isin(test_dates)] #self.calc_kelly(x_test, (y_test > 0), y_test) def calc_kelly(self, x, y, returns): """ Calculate info needed for Kelly position sizing """ win_rate = precision_score(y, self.model.predict(x)) avg_win = max(returns[returns>0].mean()-2*self.commissions, 0) avg_loss = -returns[returns<0].mean()+2*self.commissions self.pos_size = min(max(win_rate/avg_loss-(1-win_rate)/avg_win, 0), 1) self.Plot("Model", "Win Rate", win_rate) self.Plot("Model", "Win Loss Ratio", avg_win/avg_loss) self.Plot("Model", "Kelly Position", self.pos_size) self.Debug(f"PT:{len(x)} WR:{win_rate:.3f} PS:{self.pos_size:.3f} " f"AW:{avg_win:.4f} AL:{avg_loss:.4f}") def trade(self): x = self.get_data(max(self.lookbacks) + 1, include_y=False) if len(x) > 0 and self.model is not None: symbols = x.index.get_level_values("symbol") pred = pd.Series(self.model.predict_proba(x)[:,1], index=symbols) self.Log(f"Predictions\n{pred.to_string()}") [self.SetHoldings(symbol, round(pred[symbol],3)/len(symbols)) for symbol in pred.index] def trade_OLD(self): x = self.get_data(max(self.lookbacks) + 1, include_y=False) if len(x) > 0 and self.model is not None: symbols = x.index.get_level_values("symbol") pred = pd.Series(self.model.predict(x), index=symbols) self.Log(f"Predictions\n{pred.to_string()}") [self.SetHoldings(symbol, self.pos_size/len(symbols)) if pred[symbol] == 1 \ else self.SetHoldings(symbol, 0) for symbol in pred.index] def get_data(self, datapoints=1, include_y=True): tickers = list(self.ActiveSecurities.Keys) data = self.History(tickers, datapoints, self.resolution) data["volatility"] = (data["high"] - data["low"])/data["close"] data["spread"] = (data["askclose"] - data["bidclose"])/data["close"] price_volume = data[["open", "high","low","close"]].mean(axis=1)*data["volume"] data["vwap"] = price_volume.rolling(max(self.lookbacks)).sum() / \ data["volume"].rolling(max(self.lookbacks)).sum() data["vwap"] /= data["close"] data["close"] = data["close"].groupby("symbol").pct_change(1) data["volume"] = data["volume"].groupby("symbol").pct_change(1) data = data[["close", "volatility", "volume", "spread", "vwap"]] groups = data.groupby("symbol") features = [groups.shift(s) for s in range(max(self.lookbacks))] features = pd.concat(features, join="inner", axis="columns").dropna() times = features.index.get_level_values("time") features["month"] = times.month features["day"] = times.day / times.days_in_month features["weekday"] = times.weekday if include_y: target = groups["close"].shift(-1) target = target.reindex_like(features).dropna() return features.loc[target.index], target else: return features
""" Crypto trading bot using maching learning Using PurgedTimeSeriesSplitGroups @version: 0.6 """ import clr clr.AddReference("System") clr.AddReference("QuantConnect.Algorithm") clr.AddReference("QuantConnect.Common") from System import * from QuantConnect import * from QuantConnect.Algorithm import * from QuantConnect.Algorithm.Framework.Execution import * import pickle import pandas as pd pd.set_option('mode.use_inf_as_na', True) from sklearn.pipeline import Pipeline from sklearn.decomposition import PCA from sklearn.neural_network import MLPClassifier from sklearn.metrics import classification_report from sklearn.metrics import average_precision_score from sklearn.model_selection import train_test_split from timeseriescv import PurgedTimeSeriesSplitGroups from sklearn.model_selection import RandomizedSearchCV STEPS = [("pca", PCA()), ("mlp", MLPClassifier(n_iter_no_change=1, max_iter=1000, solver="adam", early_stopping=True, warm_start=True, validation_fraction=0.2))] PARAMS = {"pca__n_components": [None, 0.9], "mlp__alpha": [0.01, 0.001, 0], "mlp__hidden_layer_sizes": [[96, ], [48, 48], [32, 32, 32]]} class MLCryptoAlgo(QCAlgorithm): def Initialize(self): self.SetStartDate(2018, 1, 1) self.SetCash(100000) self.SetBrokerageModel(BrokerageName.Bitfinex, AccountType.Cash) self.Settings.FreePortfolioValuePercentage = 0.05 self.resolution = Resolution.Hour self.SetBenchmark(SecurityType.Crypto, "BTCUSD") tickers = ["BTCUSD", "ETHUSD", "LTCUSD", "EOSUSD", "XMRUSD", "XRPUSD"] # NO BCH, ADA, DOT, ATOM [self.AddCrypto(t, self.resolution, Market.Bitfinex) for t in tickers] self.lookbacks = [1, 4, 8, 24, 24*7] self.datapoints = 365 * 24 #self.commissions = 0.005 self.pos_size = 1.0 / len(tickers) self.model = None self.model_key = "crypto_multi_hour" if self.ObjectStore.ContainsKey(self.model_key): model_buffer = self.ObjectStore.ReadBytes(self.model_key) self.Log(f"Loading model {self.model_key}") self.model = pickle.loads(bytes(model_buffer)) self.Train(self.DateRules.WeekStart(), self.TimeRules.At(0, 30), self.train_model) self.Schedule.On(self.DateRules.EveryDay(), #self.TimeRules.At(0, 0), self.TimeRules.Every(TimeSpan.FromHours(1)), self.trade) def train_model(self): """ Train model with new data, model is created if missing """ if self.model is None: cv = PurgedTimeSeriesSplitGroups(n_splits=10, purge_groups=max(self.lookbacks)) self.model = RandomizedSearchCV(Pipeline(steps=STEPS), PARAMS, scoring="accuracy", cv=cv, n_iter=10, n_jobs=-1) x, y = self.get_data(self.datapoints, include_y=True) if len(x) > 0 and len(y) > 0: x_train, x_test, y_train, y_test = train_test_split(x, y, shuffle=False) groups = x_train.index.get_level_values("time") self.model.fit(x_train, (y_train>0), groups=groups) self.ObjectStore.SaveBytes(self.model_key, pickle.dumps(self.model)) self.calc_kelly(x_test, (y_test > 0), y_test) self.Log(classification_report((y_test > 0), self.model.predict(x_test))) def calc_kelly(self, x, y, returns): """ Calculate info needed for Kelly position sizing """ win_rate = self.model.best_score_ avg_gain = returns[returns>0].mean() avg_loss = -returns[returns<0].mean() win_loss_ratio = avg_gain/avg_loss kelly_pos = win_rate-(1-win_rate)/win_loss_ratio symbols_nr = len(y.index.get_level_values("symbol").unique()) self.pos_size = max(kelly_pos, 0)/symbols_nr self.Plot("Model", "Win Rate", float(win_rate)) self.Plot("Model", "Win Loss Ratio", float(win_loss_ratio)) self.Plot("Model", "Kelly Position", float(kelly_pos)) self.Debug(f"WR:{win_rate:.3f} WLR:{win_loss_ratio:.3f} PS:{self.pos_size:.3f}") def trade(self): x = self.get_data(max(self.lookbacks) + 1, include_y=False) if len(x) > 0 and self.model is not None: symbols = x.index.get_level_values("symbol") pred = pd.Series(self.model.predict(x), index=symbols).sort_values() for symbol in pred.index: self.Log(f"Signal for {symbol}: {pred[symbol]}") if pred[symbol] == 1: self.SetHoldings(symbol, self.pos_size) elif pred[symbol] == -1: self.SetHoldings(symbol, 0) def get_data(self, datapoints=1, include_y=True): tickers = list(self.ActiveSecurities.Keys) data = self.History(tickers, datapoints, self.resolution) data["volatility"] = data["high"] - data["low"] data["spread"] = data["askclose"] - data["bidclose"] data = data[["close", "volatility", "volume", "spread"]] groups = data.groupby("symbol") features = [groups.pct_change(p) for p in self.lookbacks] # Momentum features += [data / groups.apply(lambda x: x.rolling(p).mean()) for p in self.lookbacks] # Feats normalized by their average features = pd.concat(features, join="inner", axis="columns").dropna() if include_y: target = groups["close"].pct_change(1).shift(-1) target = target.reindex_like(features).dropna() return features.loc[target.index], target else: return features
""" Crypto trading bot using machine learning Using online learning with Neural Network @version: 0.15 """ import datetime import clr clr.AddReference("System") clr.AddReference("QuantConnect.Algorithm") clr.AddReference("QuantConnect.Common") from System import * from QuantConnect import * from QuantConnect.Algorithm import * from QuantConnect.Algorithm.Framework.Execution import * import pandas as pd pd.set_option('mode.use_inf_as_na', True) from sklearn.pipeline import Pipeline from sklearn.metrics import precision_score from sklearn.preprocessing import StandardScaler from sklearn.neural_network import MLPClassifier import features as ft STEPS = [("scaler", StandardScaler()), ("model", MLPClassifier(warm_start=True, max_iter=10, hidden_layer_sizes=(64, 64)))] class MLCryptoAlgo(QCAlgorithm): def Initialize(self): self.SetStartDate(2018, 1, 1) self.SetCash(100000) self.SetBrokerageModel(BrokerageName.GDAX, AccountType.Cash) self.Settings.FreePortfolioValuePercentage = 0.05 self.resolution = Resolution.Daily self.tickers = ["BTCUSD", "ETHUSD", "LTCUSD", "XRPUSD", "BCHUSD"] self.SetBenchmark(self.CustomBenchmark) [self.AddCrypto(t, self.resolution, Market.GDAX) for t in self.tickers] self.periods = [1, 7, 30] self.train_days = timedelta(365 * 2) self.test_days = timedelta(365 * 1) self.test_start = None self.possize = 0.0 self.commissions = 0.002 self.model = Pipeline(steps=STEPS) self.Train(self.DateRules.WeekStart(), #self.DateRules.EveryDay(), self.TimeRules.At(0, 15), self.train_model) self.Schedule.On(self.DateRules.EveryDay(), self.TimeRules.At(0, 30), #self.TimeRules.Every(TimeSpan.FromHours(1)), self.trade) def CustomBenchmark(self, time): bmk = [self.Securities[ticker].Price for ticker in self.tickers] return sum(bmk)/len(self.tickers) def train_model(self): """ Train model with new data, model is created if missing """ self.test_start = self.Time-self.test_days train_start = self.test_start-self.train_days x_train, y_train = self.get_data(train_start, self.test_start) self.model.fit(x_train, (y_train > 0)) x_test, y_test = self.get_data(self.test_start, self.Time) self.Log(f"Model score {self.model.score(x_test, (y_test > 0))}") self.calc_kelly(x_test, (y_test > 0), y_test) def calc_kelly(self, x, y, returns): """ Calculate info needed for Kelly position sizing """ winrate = precision_score(y, self.model.predict(x)) avgwin = max(returns[returns > 0].mean() - 2 * self.commissions, 0) avgloss = max(-returns[returns < 0].mean() + 2 * self.commissions, 0) self.possize = winrate / avgloss - (1 - winrate) / avgwin self.possize = min(max(self.possize, 0), 1) self.Debug(f"PT:{len(x)} WR:{winrate:.3f} PS:{self.possize:.3f} " f"AW:{avgwin:.4f} AL:{avgloss:.4f}") self.Plot("Model", "Win Rate", winrate) self.Plot("Model", "Win Loss Ratio", avgwin / avgloss) self.Plot("Model", "Kelly Position", self.possize) def trade(self): x_pred = self.get_data(self.test_start, self.Time, include_y=False) self.Transactions.CancelOpenOrders() if len(x_pred) > 0 and self.model is not None: x_pred = x_pred.sort_index().groupby("symbol").last() tickers = x_pred.index.get_level_values("symbol") pred = pd.Series(self.model.predict(x_pred), index=tickers) self.Log(f"Predictions\n{pred.to_string()}") self.Debug(f"{self.Time} - Predictions symbols {len(pred)}") for ticker in pred.index: target = self.possize / sum(pred) if pred[ticker] else 0 qty = self.CalculateOrderQuantity(ticker, target) self.LimitOrder(ticker, qty, self.Securities[ticker].Price) def get_data(self, start, end, include_y=True): tickers = list(self.ActiveSecurities.Keys) history = self.History(tickers, start, end, self.resolution) # define data history["volatility"] = history["high"] - history["low"] history["vwap"] = ft.vwap(history, max(self.periods)) data = history[["close", "volatility", "volume", "vwap"]] # calculate features indicators = [ft.momentum(data, self.periods), ft.strength(data, self.periods), ft.macd(data, zip(self.periods[:-1], self.periods[1:])), ft.minmax(data, self.periods[1:])] new_indicators = [ft.diff(i, self.periods[1:]) for i in indicators] #new_indicators += [ft.std(i, self.periods[1:]) for i in indicators] # TODO: Fix STD features = ft.join_indicators(indicators+new_indicators+[ft.time(data)]) target = data["close"].groupby("symbol").pct_change(1).shift(-1) if include_y: target = target.reindex_like(features).dropna() return features.loc[target.index], target else: return features
""" Crypto trading bot using maching learning New models and modified Kelly Criterion @version: 0.7 """ import clr clr.AddReference("System") clr.AddReference("QuantConnect.Algorithm") clr.AddReference("QuantConnect.Common") from System import * from QuantConnect import * from QuantConnect.Algorithm import * from QuantConnect.Algorithm.Framework.Execution import * import pickle import pandas as pd pd.set_option('mode.use_inf_as_na', True) from sklearn.pipeline import Pipeline from sklearn.decomposition import PCA from sklearn.neural_network import MLPClassifier from sklearn.model_selection import GridSearchCV from sklearn.metrics import average_precision_score from timeseriescv import PurgedTimeSeriesSplitGroups from sklearn.linear_model import LogisticRegression from sklearn.ensemble import GradientBoostingClassifier from sklearn.model_selection import train_test_split as ttsplit STEPS = [("pca", PCA(n_components=0.99)), ("model", LogisticRegression())] PARAMS = {"pca__n_components": [1, 0.99, 0.8], "model": [MLPClassifier(n_iter_no_change=1, early_stopping=True), GradientBoostingClassifier(n_iter_no_change=1), LogisticRegression()]} class MLCryptoAlgo(QCAlgorithm): def Initialize(self): self.SetStartDate(2018, 1, 1) self.SetCash(100000) self.SetBrokerageModel(BrokerageName.Bitfinex, AccountType.Cash) self.resolution = Resolution.Daily self.SetBenchmark(SecurityType.Crypto, "BTCUSD") self.tickers = ["BTCUSD", "ETHUSD", "LTCUSD", "EOSUSD", "XMRUSD", "XRPUSD"] # NO BCH, ADA, DOT, ATOM [self.AddCrypto(t, self.resolution, Market.Bitfinex) for t in self.tickers] self.lookbacks = [1, 7, 15, 31, 63] self.datapoints = 365 * 5 self.pos_size = 0.0 self.commissions = 0.002 self.model = None self.model_key = "crypto_multi_daily" if self.ObjectStore.ContainsKey(self.model_key): model_buffer = self.ObjectStore.ReadBytes(self.model_key) self.Log(f"Loading model {self.model_key}") self.model = pickle.loads(bytes(model_buffer)) self.Train(self.DateRules.WeekStart(), #self.DateRules.EveryDay(), self.TimeRules.At(0, 30), self.train_model) self.Schedule.On(self.DateRules.EveryDay(), self.TimeRules.At(0, 0), #self.TimeRules.Every(TimeSpan.FromHours(1)), self.trade) def train_model(self): """ Train model with new data, model is created if missing """ if self.model is None: cv = PurgedTimeSeriesSplitGroups(n_splits=10, purge_groups=max(self.lookbacks)) self.model = GridSearchCV(Pipeline(steps=STEPS), PARAMS, scoring="average_precision", cv=cv) x, y = self.get_data(self.datapoints, include_y=True) if len(x) > 0 and len(y) > 0: dates = x.index.get_level_values("time") train_dates, test_dates = ttsplit(dates.unique(), shuffle=False) x_train = x[dates.isin(train_dates)] y_train = y[dates.isin(train_dates)] groups = x_train.index.get_level_values("time") self.model.fit(x_train, (y_train > 0), groups=groups) self.Log(pd.DataFrame(self.model.cv_results_)) self.ObjectStore.SaveBytes(self.model_key, pickle.dumps(self.model)) x_test = x[dates.isin(test_dates)] y_test = y[dates.isin(test_dates)] self.calc_kelly(x_test, (y_test > 0), y_test) def calc_kelly(self, x, y, returns): """ Calculate info needed for Kelly position sizing """ win_rate = average_precision_score(y, self.model.predict(x)) avg_win = max(returns[returns>0].mean()-2*self.commissions, 0) avg_loss = -returns[returns<0].mean()+2*self.commissions self.pos_size = min(max(win_rate/avg_loss-(1-win_rate)/avg_win, 0), 1) self.Plot("Model", "Win Rate", win_rate) self.Plot("Model", "Win Loss Ratio", avg_win/avg_loss) self.Plot("Model", "Kelly Position", self.pos_size) self.Debug(f"WR:{win_rate:.3f} PS:{self.pos_size:.3f} " f"AW:{avg_win:.4f} AL:{avg_loss:.4f}") def trade(self): x = self.get_data(max(self.lookbacks) + 1, include_y=False) if len(x) > 0 and self.model is not None: symbols = x.index.get_level_values("symbol") pred = pd.Series(self.model.predict(x), index=symbols).sort_values() for symbol in pred.index: self.Log(f"Signal for {symbol}: {pred[symbol]}") if pred[symbol] == 1: self.SetHoldings(symbol, self.pos_size/len(self.tickers)) else: self.SetHoldings(symbol, 0) def get_data(self, datapoints=1, include_y=True): tickers = list(self.ActiveSecurities.Keys) data = self.History(tickers, datapoints, self.resolution) data["volatility"] = data["high"] - data["low"] data["spread"] = data["askclose"] - data["bidclose"] data = data[["close", "volatility", "volume", "spread"]] groups = data.groupby("symbol") features = [groups.pct_change(p) for p in self.lookbacks] # Momentum features += [data / groups.apply(lambda x: x.rolling(p).mean()) for p in self.lookbacks] # Feats normalized by their average features = pd.concat(features, join="inner", axis="columns").dropna() if include_y: target = groups["close"].pct_change(1).shift(-1) target = target.reindex_like(features).dropna() return features.loc[target.index], target else: return features