diff --git a/project-templates/python/ai/main.py b/project-templates/python/ai/main.py index 1a4b80312d..4e415ea446 100644 --- a/project-templates/python/ai/main.py +++ b/project-templates/python/ai/main.py @@ -3,15 +3,16 @@ from AlgorithmImports import * # endregion + class TensorFlowAlgorithm(QCAlgorithm): def initialize(self) -> None: self.set_start_date(2024, 9, 1) self.set_end_date(2024, 12, 31) - self.set_cash(100000) + self.set_cash(100_000) + self.settings.seed_initial_prices = True # Request SPY data for model training, prediction and trading. - self._spy = self.add_equity("SPY", Resolution.DAILY) - + self._spy = self.add_equity("SPY") # Hyperparameter to create the MLP model. num_factors = 5 num_neurons_1 = 10 @@ -19,7 +20,6 @@ def initialize(self) -> None: num_neurons_3 = 5 self._epochs = 100 self._learning_rate = 0.0001 - # Create the MLP model with ReLu activiation. self._model = tf.keras.Sequential([ tf.keras.layers.Dense(num_neurons_1, activation=tf.nn.relu, input_shape=(num_factors,)), # input shape required @@ -27,7 +27,6 @@ def initialize(self) -> None: tf.keras.layers.Dense(num_neurons_3, activation=tf.nn.relu), tf.keras.layers.Dense(1) ]) - # 2-year data to train the model. training_length = 500 self._spy.session.size = training_length @@ -35,22 +34,23 @@ def initialize(self) -> None: history = self.history[TradeBar](self._spy, training_length, Resolution.DAILY) for trade_bar in history: self._spy.session.update(trade_bar) - # Train the model to use the prediction right away. self.train(self._my_training_method) # Recalibrate the model weekly to ensure its accuracy on the updated domain. - self.train(self.date_rules.week_start(), self.time_rules.at(8, 0), self._my_training_method) + self.train( + self.date_rules.week_start(), + self.time_rules.at(8, 0), + self._my_training_method + ) def _get_features_and_labels(self, lookback=5): lookback_series = [] - # Train and predict the N differencing data, which is more normalized and stationary. data = pd.Series([bar.close for bar in self._spy.session][::-1]) for i in range(1, lookback + 1): df = data.diff(i)[lookback:-1] df.name = f"close-{i}" lookback_series.append(df) - X = pd.concat(lookback_series, axis=1).reset_index(drop=True).dropna() Y = data.diff(-1)[lookback:-1].reset_index(drop=True) return X.values, Y.values @@ -58,26 +58,22 @@ def _get_features_and_labels(self, lookback=5): def _my_training_method(self) -> None: # Prepare the processed training data. features, labels = self._get_features_and_labels() - - # Define the loss function, we use MSE in this example + # Define the loss function, we use MSE in this example. def loss_mse(target_y, predicted_y): return tf.reduce_mean(tf.square(target_y - predicted_y)) - # Train the model with Adam optimization function. optimizer = tf.keras.optimizers.Adam(learning_rate=self._learning_rate) for i in range(self._epochs): with tf.GradientTape() as t: loss = loss_mse(labels, self._model(features)) - jac = t.gradient(loss, self._model.trainable_weights) optimizer.apply_gradients(zip(jac, self._model.trainable_weights)) - def on_data(self, data) -> None: + def on_data(self, data: Slice) -> None: if data.bars: # Get prediction by the updated features. new_features, __ = self._get_features_and_labels() prediction = self._model(new_features) prediction = float(prediction.numpy()[-1]) - # If the predicted direction is going upward, buy SPY, else sell. self.set_holdings(self._spy, 1 if prediction > 0 else -1) diff --git a/project-templates/python/alternative-data-chain-universe-brainsentimentindicatoruniverse/main.py b/project-templates/python/alternative-data-chain-universe-brainsentimentindicatoruniverse/main.py index 4bb84e410a..641f0e3149 100644 --- a/project-templates/python/alternative-data-chain-universe-brainsentimentindicatoruniverse/main.py +++ b/project-templates/python/alternative-data-chain-universe-brainsentimentindicatoruniverse/main.py @@ -2,41 +2,44 @@ from AlgorithmImports import * # endregion -class BrainSentimentIndicatorChainedUniverseAlgorithm(QCAlgorithm): +class BrainSentimentIndicatorChainedUniverseAlgorithm(QCAlgorithm): _fundamental: list[Symbol] = [] def initialize(self) -> None: self.set_start_date(2024, 9, 1) self.set_end_date(2024, 12, 31) - self.set_cash(100000) - + self.set_cash(100_000) + self.settings.seed_initial_prices = True self.universe_settings.resolution = Resolution.DAILY - # First universe: top 100 US Equities by dollar volume; emits Universe.UNCHANGED. + # Add a fundamental universe to track the most liquid US Equities by dollar volume. self.add_universe(self._fundamental_filter) - # Second universe: positive 7-day media sentiment with active mention coverage, intersected with the fundamental list. + # Add a Brain Sentiment universe, restricted to high-sentiment names within the fundamental list. self._universe = self.add_universe(BrainSentimentIndicatorUniverse, self._select_assets) - - # Rebalance shortly after the open so today's intersection is locked in. - self.schedule.on(self.date_rules.every_day("SPY"), self.time_rules.at(9, 0, 0), self._rebalance) + # Rebalance shortly after the open. + self.schedule.on( + self.date_rules.every_day("SPY"), + self.time_rules.at(9, 0), + self._rebalance + ) def _fundamental_filter(self, fundamental: List[Fundamental]) -> Universe.UnchangedUniverse: - sorted_by_dollar_volume = sorted(fundamental, key=lambda x: x.dollar_volume, reverse=True) - self._fundamental = [c.symbol for c in sorted_by_dollar_volume[:100]] + self._fundamental = [c.symbol for c in sorted(fundamental, key=lambda x: x.dollar_volume)[-100:]] return Universe.UNCHANGED def _select_assets(self, alt_coarse: List[BrainSentimentIndicatorUniverse]) -> List[Symbol]: - # Keep names with both active mention coverage and positive 7-day sentiment. + # Keep only names with active mention coverage and positive sentiment. alt = [d.symbol for d in alt_coarse - if d.total_article_mentions_7_days and d.total_article_mentions_7_days > 0 - and d.sentiment_7_days and d.sentiment_7_days > 0] - return list(set(self._fundamental) & set(alt)) + if d.total_article_mentions_7_days and + d.total_article_mentions_7_days > 0 and + d.sentiment_7_days and + d.sentiment_7_days > 0] + return [s for s in self._fundamental if s in alt] def _rebalance(self) -> None: if not self._universe.selected: return - + # Enter the universe equally weighted across all selected assets. weight = 1 / len(self._universe.selected) targets = [PortfolioTarget(symbol, weight) for symbol in self._universe.selected] - - self.set_holdings(targets, liquidate_existing_holdings=True) + self.set_holdings(targets, True) diff --git a/project-templates/python/alternative-data-universe-braincompanyfilinglanguagemetricsuniverseall/main.py b/project-templates/python/alternative-data-universe-braincompanyfilinglanguagemetricsuniverseall/main.py index d742bcf799..4235b70081 100644 --- a/project-templates/python/alternative-data-universe-braincompanyfilinglanguagemetricsuniverseall/main.py +++ b/project-templates/python/alternative-data-universe-braincompanyfilinglanguagemetricsuniverseall/main.py @@ -2,33 +2,35 @@ from AlgorithmImports import * # endregion + class BrainCompanyFilingLanguageMetricsUniverseAlgorithm(QCAlgorithm): def initialize(self) -> None: self.set_start_date(2024, 9, 1) self.set_end_date(2024, 12, 31) - self.set_cash(100000) - - self.universe_settings.resolution = Resolution.DAILY + self.set_cash(100_000) + self.settings.seed_initial_prices = True + self.universe_settings.resolution = Resolution.MINUTE # Universe of US Equities with positive sentiment in their latest SEC filings. self._universe = self.add_universe(BrainCompanyFilingLanguageMetricsUniverseAll, self._select_assets) - - # Rebalance shortly after the open so today's universe is locked in. - self.schedule.on(self.date_rules.every_day("SPY"), self.time_rules.at(9, 0, 0), self._rebalance) + # Rebalance before market open to trade today's universe. + self.schedule.on( + self.date_rules.every_day("SPY"), + self.time_rules.at(9, 0), + self._rebalance + ) def _select_assets(self, data: List[BrainCompanyFilingLanguageMetricsUniverseAll]) -> List[Symbol]: # Keep names with positive sentiment in both the report and MD&A sections. return [d.symbol for d in data - if d.report_sentiment and d.report_sentiment.sentiment and d.report_sentiment.sentiment > 0 - and d.management_discussion_analyasis_of_financial_condition_and_results_of_operations - and d.management_discussion_analyasis_of_financial_condition_and_results_of_operations.sentiment - and d.management_discussion_analyasis_of_financial_condition_and_results_of_operations.sentiment > 0] + if d.report_sentiment and d.report_sentiment.sentiment and d.report_sentiment.sentiment > 0 and + d.management_discussion_analyasis_of_financial_condition_and_results_of_operations and + d.management_discussion_analyasis_of_financial_condition_and_results_of_operations.sentiment and + d.management_discussion_analyasis_of_financial_condition_and_results_of_operations.sentiment > 0] def _rebalance(self) -> None: if not self._universe.selected: return - weight = 1 / len(self._universe.selected) targets = [PortfolioTarget(symbol, weight) for symbol in self._universe.selected] - - self.set_holdings(targets, liquidate_existing_holdings=True) + self.set_holdings(targets, True) diff --git a/project-templates/python/alternative-data-universe-brainsentimentindicatoruniverse/main.py b/project-templates/python/alternative-data-universe-brainsentimentindicatoruniverse/main.py index ed25fbe71f..d112f6cd60 100644 --- a/project-templates/python/alternative-data-universe-brainsentimentindicatoruniverse/main.py +++ b/project-templates/python/alternative-data-universe-brainsentimentindicatoruniverse/main.py @@ -2,31 +2,33 @@ from AlgorithmImports import * # endregion + class BrainSentimentIndicatorUniverseAlgorithm(QCAlgorithm): def initialize(self) -> None: self.set_start_date(2024, 9, 1) self.set_end_date(2024, 12, 31) - self.set_cash(100000) - - self.universe_settings.resolution = Resolution.DAILY + self.set_cash(100_000) + self.settings.seed_initial_prices = True + self.universe_settings.resolution = Resolution.MINUTE # Universe of US Equities with positive 7-day media sentiment and active mention coverage. self._universe = self.add_universe(BrainSentimentIndicatorUniverse, self._select_assets) - - # Rebalance shortly after the open so today's universe is locked in. - self.schedule.on(self.date_rules.every_day("SPY"), self.time_rules.at(9, 0, 0), self._rebalance) + # Rebalance before market open to trade today's universe. + self.schedule.on( + self.date_rules.every_day("SPY"), + self.time_rules.at(9, 0), + self._rebalance + ) def _select_assets(self, data: List[BrainSentimentIndicatorUniverse]) -> List[Symbol]: # Keep names with both active mention coverage and positive 7-day sentiment. return [d.symbol for d in data - if d.total_article_mentions_7_days and d.total_article_mentions_7_days > 0 - and d.sentiment_7_days and d.sentiment_7_days > 0] + if d.total_article_mentions_7_days and d.total_article_mentions_7_days > 0 and + d.sentiment_7_days and d.sentiment_7_days > 0] def _rebalance(self) -> None: if not self._universe.selected: return - weight = 1 / len(self._universe.selected) targets = [PortfolioTarget(symbol, weight) for symbol in self._universe.selected] - - self.set_holdings(targets, liquidate_existing_holdings=True) + self.set_holdings(targets, True) diff --git a/project-templates/python/alternative-data-universe-brainstockrankinguniverse/main.py b/project-templates/python/alternative-data-universe-brainstockrankinguniverse/main.py index 358436cec8..261355e8f0 100644 --- a/project-templates/python/alternative-data-universe-brainstockrankinguniverse/main.py +++ b/project-templates/python/alternative-data-universe-brainstockrankinguniverse/main.py @@ -2,32 +2,34 @@ from AlgorithmImports import * # endregion + class BrainStockRankingUniverseAlgorithm(QCAlgorithm): def initialize(self) -> None: self.set_start_date(2024, 9, 1) self.set_end_date(2024, 12, 31) - self.set_cash(100000) - - self.universe_settings.resolution = Resolution.DAILY + self.set_cash(100_000) + self.settings.seed_initial_prices = True + self.universe_settings.resolution = Resolution.MINUTE # Universe of US Equities with positive Brain ML rankings across 2-, 3-, and 5-day horizons. self._universe = self.add_universe(BrainStockRankingUniverse, self._select_assets) - - # Rebalance shortly after the open so today's universe is locked in. - self.schedule.on(self.date_rules.every_day("SPY"), self.time_rules.at(9, 0, 0), self._rebalance) + # Rebalance before market open to trade today's universe. + self.schedule.on( + self.date_rules.every_day("SPY"), + self.time_rules.at(9, 0), + self._rebalance + ) def _select_assets(self, data: List[BrainStockRankingUniverse]) -> List[Symbol]: # Keep names with consistent positive momentum across all three horizons. return [d.symbol for d in data - if d.rank_2_days and d.rank_2_days > 0 - and d.rank_3_days and d.rank_3_days > 0 - and d.rank_5_days and d.rank_5_days > 0] + if d.rank_2_days and d.rank_2_days > 0 and + d.rank_3_days and d.rank_3_days > 0 and + d.rank_5_days and d.rank_5_days > 0] def _rebalance(self) -> None: if not self._universe.selected: return - weight = 1 / len(self._universe.selected) targets = [PortfolioTarget(symbol, weight) for symbol in self._universe.selected] - - self.set_holdings(targets, liquidate_existing_holdings=True) + self.set_holdings(targets, True) diff --git a/project-templates/python/alternative-data-universe-coingeckouniverse/main.py b/project-templates/python/alternative-data-universe-coingeckouniverse/main.py index f1f81e5e43..a0e97c9981 100644 --- a/project-templates/python/alternative-data-universe-coingeckouniverse/main.py +++ b/project-templates/python/alternative-data-universe-coingeckouniverse/main.py @@ -2,14 +2,15 @@ from AlgorithmImports import * # endregion + class CoinGeckoUniverseAlgorithm(QCAlgorithm): def initialize(self) -> None: self.set_start_date(2024, 9, 1) self.set_end_date(2024, 12, 31) - self.set_cash(100000) + self.set_cash(100_000) + self.settings.seed_initial_prices = True self.set_account_currency("USD") - # Trade the largest CoinGecko coins on Coinbase quoted in USD. self._market = Market.COINBASE self._market_pairs = [ @@ -17,13 +18,15 @@ def initialize(self) -> None: for x in self.symbol_properties_database.get_symbol_properties_list(self._market) if x.value.quote_currency == self.account_currency ] - - self.universe_settings.resolution = Resolution.DAILY + self.universe_settings.resolution = Resolution.MINUTE # Universe of the top 10 CoinGecko coins by market cap that we can trade. self._universe = self.add_universe(CoinGeckoUniverse, "CoinGeckoUniverse", Resolution.DAILY, self._select_assets) - # Rebalance daily on US trading days, after CoinGecko refreshes overnight. - self.schedule.on(self.date_rules.every_day("SPY"), self.time_rules.at(9, 0, 0), self._rebalance) + self.schedule.on( + self.date_rules.every_day("SPY"), + self.time_rules.at(9, 0), + self._rebalance + ) def _select_assets(self, data: List[CoinGecko]) -> List[Symbol]: # Keep coins quoted in our account currency on the chosen brokerage. @@ -36,8 +39,6 @@ def _select_assets(self, data: List[CoinGecko]) -> List[Symbol]: def _rebalance(self) -> None: if not self._universe.selected: return - weight = 1 / len(self._universe.selected) targets = [PortfolioTarget(symbol, weight) for symbol in self._universe.selected] - - self.set_holdings(targets, liquidate_existing_holdings=True) + self.set_holdings(targets, True) diff --git a/project-templates/python/alternative-data-universe-eodhdupcomingdividends/main.py b/project-templates/python/alternative-data-universe-eodhdupcomingdividends/main.py index ed2694dd19..02cda1e333 100644 --- a/project-templates/python/alternative-data-universe-eodhdupcomingdividends/main.py +++ b/project-templates/python/alternative-data-universe-eodhdupcomingdividends/main.py @@ -2,31 +2,33 @@ from AlgorithmImports import * # endregion + class EODHDUpcomingDividendsUniverseAlgorithm(QCAlgorithm): def initialize(self) -> None: self.set_start_date(2024, 9, 1) self.set_end_date(2024, 12, 31) - self.set_cash(100000) - - self.universe_settings.resolution = Resolution.DAILY + self.set_cash(100_000) + self.settings.seed_initial_prices = True + self.universe_settings.resolution = Resolution.MINUTE # Universe of US Equities going ex-dividend in the next day with a meaningful payout. self._universe = self.add_universe(EODHDUpcomingDividends, self._select_assets) - - # Rebalance shortly after the open so today's universe is locked in. - self.schedule.on(self.date_rules.every_day("SPY"), self.time_rules.at(9, 0, 0), self._rebalance) + # Rebalance before market open to trade today's universe. + self.schedule.on( + self.date_rules.every_day("SPY"), + self.time_rules.at(9, 0), + self._rebalance + ) def _select_assets(self, data: List[EODHDUpcomingDividends]) -> List[Symbol]: # Keep names with a dividend over $0.05 paying within one day. return [d.symbol for d in data - if d.dividend_date and d.dividend - and d.dividend_date <= self.time + timedelta(1) and d.dividend > 0.05] + if d.dividend_date and d.dividend and + d.dividend_date <= self.time + timedelta(1) and d.dividend > 0.05] def _rebalance(self) -> None: if not self._universe.selected: return - weight = 1 / len(self._universe.selected) targets = [PortfolioTarget(symbol, weight) for symbol in self._universe.selected] - - self.set_holdings(targets, liquidate_existing_holdings=True) + self.set_holdings(targets, True) diff --git a/project-templates/python/alternative-data-universe-eodhdupcomingearnings/main.py b/project-templates/python/alternative-data-universe-eodhdupcomingearnings/main.py index 7e7597da05..5cfd8fff89 100644 --- a/project-templates/python/alternative-data-universe-eodhdupcomingearnings/main.py +++ b/project-templates/python/alternative-data-universe-eodhdupcomingearnings/main.py @@ -2,31 +2,33 @@ from AlgorithmImports import * # endregion + class EODHDUpcomingEarningsUniverseAlgorithm(QCAlgorithm): def initialize(self) -> None: self.set_start_date(2024, 9, 1) self.set_end_date(2024, 12, 31) - self.set_cash(100000) - - self.universe_settings.resolution = Resolution.DAILY + self.set_cash(100_000) + self.settings.seed_initial_prices = True + self.universe_settings.resolution = Resolution.MINUTE # Universe of US Equities reporting earnings in the next 3 days with a positive estimate. self._universe = self.add_universe(EODHDUpcomingEarnings, self._select_assets) - - # Rebalance shortly after the open so today's universe is locked in. - self.schedule.on(self.date_rules.every_day("SPY"), self.time_rules.at(9, 0, 0), self._rebalance) + # Rebalance before market open to trade today's universe. + self.schedule.on( + self.date_rules.every_day("SPY"), + self.time_rules.at(9, 0), + self._rebalance + ) def _select_assets(self, data: List[EODHDUpcomingEarnings]) -> List[Symbol]: # Keep names with a positive analyst estimate ahead of the report. return [d.symbol for d in data - if d.report_date and d.estimate - and d.report_date <= self.time + timedelta(3) and d.estimate > 0] + if d.report_date and d.estimate and + d.report_date <= self.time + timedelta(3) and d.estimate > 0] def _rebalance(self) -> None: if not self._universe.selected: return - weight = 1 / len(self._universe.selected) targets = [PortfolioTarget(symbol, weight) for symbol in self._universe.selected] - - self.set_holdings(targets, liquidate_existing_holdings=True) + self.set_holdings(targets, True) diff --git a/project-templates/python/alternative-data-universe-eodhdupcomingipos/main.py b/project-templates/python/alternative-data-universe-eodhdupcomingipos/main.py index 51613dd290..5ab0861cd1 100644 --- a/project-templates/python/alternative-data-universe-eodhdupcomingipos/main.py +++ b/project-templates/python/alternative-data-universe-eodhdupcomingipos/main.py @@ -3,32 +3,34 @@ from QuantConnect.DataSource.EODHD import DealType # endregion + class EODHDUpcomingIPOsUniverseAlgorithm(QCAlgorithm): def initialize(self) -> None: self.set_start_date(2024, 9, 1) self.set_end_date(2024, 12, 31) - self.set_cash(100000) - - self.universe_settings.resolution = Resolution.DAILY + self.set_cash(100_000) + self.settings.seed_initial_prices = True + self.universe_settings.resolution = Resolution.MINUTE # Universe of confirmed non-penny upcoming IPOs. self._universe = self.add_universe(EODHDUpcomingIPOs, self._select_assets) - - # Rebalance shortly after the open so today's universe is locked in. - self.schedule.on(self.date_rules.every_day("SPY"), self.time_rules.at(9, 0, 0), self._rebalance) + # Rebalance before market open to trade today's universe. + self.schedule.on( + self.date_rules.every_day("SPY"), + self.time_rules.at(9, 0), + self._rebalance + ) def _select_assets(self, data: List[EODHDUpcomingIPOs]) -> List[Symbol]: # Keep expected/priced IPOs with a confirmed date and a >$1 minimum across the price band. return [d.symbol for d in data - if d.ipo_date and d.deal_type in [DealType.EXPECTED, DealType.PRICED] - and (prices := [x for x in [d.lowest_price, d.highest_price, d.offer_price] if x]) - and min(prices) > 1] + if d.ipo_date and d.deal_type in [DealType.EXPECTED, DealType.PRICED] and + (prices := [x for x in [d.lowest_price, d.highest_price, d.offer_price] if x]) and + min(prices) > 1] def _rebalance(self) -> None: if not self._universe.selected: return - weight = 1 / len(self._universe.selected) targets = [PortfolioTarget(symbol, weight) for symbol in self._universe.selected] - - self.set_holdings(targets, liquidate_existing_holdings=True) + self.set_holdings(targets, True) diff --git a/project-templates/python/alternative-data-universe-eodhdupcomingsplits/main.py b/project-templates/python/alternative-data-universe-eodhdupcomingsplits/main.py index cde7d57ae5..cba2c14e09 100644 --- a/project-templates/python/alternative-data-universe-eodhdupcomingsplits/main.py +++ b/project-templates/python/alternative-data-universe-eodhdupcomingsplits/main.py @@ -2,31 +2,33 @@ from AlgorithmImports import * # endregion + class EODHDUpcomingSplitsUniverseAlgorithm(QCAlgorithm): def initialize(self) -> None: self.set_start_date(2024, 9, 1) self.set_end_date(2024, 12, 31) - self.set_cash(100000) - - self.universe_settings.resolution = Resolution.DAILY + self.set_cash(100_000) + self.settings.seed_initial_prices = True + self.universe_settings.resolution = Resolution.MINUTE # Universe of US Equities with a forward stock split in the next 3 days. self._universe = self.add_universe(EODHDUpcomingSplits, self._select_assets) - - # Rebalance shortly after the open so today's universe is locked in. - self.schedule.on(self.date_rules.every_day("SPY"), self.time_rules.at(9, 0, 0), self._rebalance) + # Rebalance before market open to trade today's universe. + self.schedule.on( + self.date_rules.every_day("SPY"), + self.time_rules.at(9, 0), + self._rebalance + ) def _select_assets(self, data: List[EODHDUpcomingSplits]) -> List[Symbol]: # Keep names with a forward split (factor > 1) within 3 days. return [d.symbol for d in data - if d.split_date and d.split_factor - and d.split_date <= self.time + timedelta(3) and d.split_factor > 1] + if d.split_date and d.split_factor and + d.split_date <= self.time + timedelta(3) and d.split_factor > 1] def _rebalance(self) -> None: if not self._universe.selected: return - weight = 1 / len(self._universe.selected) targets = [PortfolioTarget(symbol, weight) for symbol in self._universe.selected] - - self.set_holdings(targets, liquidate_existing_holdings=True) + self.set_holdings(targets, True) diff --git a/project-templates/python/alternative-data-universe-quivercnbcsuniverse/main.py b/project-templates/python/alternative-data-universe-quivercnbcsuniverse/main.py index a3dadebadd..211b72d3e8 100644 --- a/project-templates/python/alternative-data-universe-quivercnbcsuniverse/main.py +++ b/project-templates/python/alternative-data-universe-quivercnbcsuniverse/main.py @@ -2,20 +2,24 @@ from AlgorithmImports import * # endregion + class QuiverCNBCsUniverseAlgorithm(QCAlgorithm): def initialize(self) -> None: self.set_start_date(2024, 9, 1) self.set_end_date(2024, 12, 31) - self.set_cash(100000) - + self.set_cash(100_000) + self.settings.seed_initial_prices = True # Trade daily on CNBC opinion updates. - self.universe_settings.resolution = Resolution.DAILY + self.universe_settings.resolution = Resolution.MINUTE # Universe of US Equities flagged by 3+ positive CNBC opinions. self._universe = self.add_universe(QuiverCNBCsUniverse, self._select_assets) - - # Rebalance shortly after the open so today's universe is locked in. - self.schedule.on(self.date_rules.every_day("SPY"), self.time_rules.at(9, 0, 0), self._rebalance) + # Rebalance before market open to trade today's universe. + self.schedule.on( + self.date_rules.every_day("SPY"), + self.time_rules.at(9, 0), + self._rebalance + ) def _select_assets(self, data: List[QuiverCNBCsUniverse]) -> List[Symbol]: # Group raw CNBC opinions by ticker so we can score each name. @@ -29,8 +33,6 @@ def _select_assets(self, data: List[QuiverCNBCsUniverse]) -> List[Symbol]: def _rebalance(self) -> None: if not self._universe.selected: return - weight = 1 / len(self._universe.selected) targets = [PortfolioTarget(symbol, weight) for symbol in self._universe.selected] - - self.set_holdings(targets, liquidate_existing_holdings=True) + self.set_holdings(targets, True) diff --git a/project-templates/python/alternative-data-universe-quivergovernmentcontractuniverse/main.py b/project-templates/python/alternative-data-universe-quivergovernmentcontractuniverse/main.py index aa7f0ad0d5..6aae0230d1 100644 --- a/project-templates/python/alternative-data-universe-quivergovernmentcontractuniverse/main.py +++ b/project-templates/python/alternative-data-universe-quivergovernmentcontractuniverse/main.py @@ -2,19 +2,23 @@ from AlgorithmImports import * # endregion + class QuiverGovernmentContractUniverseAlgorithm(QCAlgorithm): def initialize(self) -> None: self.set_start_date(2024, 9, 1) self.set_end_date(2024, 12, 31) - self.set_cash(100000) - - self.universe_settings.resolution = Resolution.DAILY + self.set_cash(100_000) + self.settings.seed_initial_prices = True + self.universe_settings.resolution = Resolution.MINUTE # Universe of US Equities with frequent, sizable US government contracts. self._universe = self.add_universe(QuiverGovernmentContractUniverse, self._select_assets) - - # Rebalance shortly after the open so today's universe is locked in. - self.schedule.on(self.date_rules.every_day("SPY"), self.time_rules.at(9, 0, 0), self._rebalance) + # Rebalance before market open to trade today's universe. + self.schedule.on( + self.date_rules.every_day("SPY"), + self.time_rules.at(9, 0), + self._rebalance + ) def _select_assets(self, data: List[QuiverGovernmentContractUniverse]) -> List[Symbol]: # Group by ticker and keep names with 3+ contracts totalling over $50K. @@ -27,8 +31,6 @@ def _select_assets(self, data: List[QuiverGovernmentContractUniverse]) -> List[S def _rebalance(self) -> None: if not self._universe.selected: return - weight = 1 / len(self._universe.selected) targets = [PortfolioTarget(symbol, weight) for symbol in self._universe.selected] - - self.set_holdings(targets, liquidate_existing_holdings=True) + self.set_holdings(targets, True) diff --git a/project-templates/python/alternative-data-universe-quiverinsidertradinguniverse/main.py b/project-templates/python/alternative-data-universe-quiverinsidertradinguniverse/main.py index ec45672647..537c7250f6 100644 --- a/project-templates/python/alternative-data-universe-quiverinsidertradinguniverse/main.py +++ b/project-templates/python/alternative-data-universe-quiverinsidertradinguniverse/main.py @@ -2,19 +2,23 @@ from AlgorithmImports import * # endregion + class QuiverInsiderTradingUniverseAlgorithm(QCAlgorithm): def initialize(self) -> None: self.set_start_date(2024, 9, 1) self.set_end_date(2024, 12, 31) - self.set_cash(100000) - - self.universe_settings.resolution = Resolution.DAILY + self.set_cash(100_000) + self.settings.seed_initial_prices = True + self.universe_settings.resolution = Resolution.MINUTE # Universe of the 10 US Equities with the largest insider-trading dollar volume. self._universe = self.add_universe(QuiverInsiderTradingUniverse, self._select_assets) - - # Rebalance shortly after the open so today's universe is locked in. - self.schedule.on(self.date_rules.every_day("SPY"), self.time_rules.at(9, 0, 0), self._rebalance) + # Rebalance before market open to trade today's universe. + self.schedule.on( + self.date_rules.every_day("SPY"), + self.time_rules.at(9, 0), + self._rebalance + ) def _select_assets(self, data: List[QuiverInsiderTradingUniverse]) -> List[Symbol]: # Aggregate insider dollar volume per ticker and keep the 10 largest. @@ -28,8 +32,6 @@ def _select_assets(self, data: List[QuiverInsiderTradingUniverse]) -> List[Symbo def _rebalance(self) -> None: if not self._universe.selected: return - weight = 1 / len(self._universe.selected) targets = [PortfolioTarget(symbol, weight) for symbol in self._universe.selected] - - self.set_holdings(targets, liquidate_existing_holdings=True) + self.set_holdings(targets, True) diff --git a/project-templates/python/alternative-data-universe-quiverlobbyinguniverse/main.py b/project-templates/python/alternative-data-universe-quiverlobbyinguniverse/main.py index 25c1550568..8903ac5671 100644 --- a/project-templates/python/alternative-data-universe-quiverlobbyinguniverse/main.py +++ b/project-templates/python/alternative-data-universe-quiverlobbyinguniverse/main.py @@ -2,19 +2,23 @@ from AlgorithmImports import * # endregion + class QuiverLobbyingUniverseAlgorithm(QCAlgorithm): def initialize(self) -> None: self.set_start_date(2024, 9, 1) self.set_end_date(2024, 12, 31) - self.set_cash(100000) - - self.universe_settings.resolution = Resolution.DAILY + self.set_cash(100_000) + self.settings.seed_initial_prices = True + self.universe_settings.resolution = Resolution.MINUTE # Universe of US Equities with material corporate lobbying spend. self._universe = self.add_universe(QuiverLobbyingUniverse, "QuiverLobbyingUniverse", Resolution.DAILY, self._select_assets) - - # Rebalance shortly after the open so today's universe is locked in. - self.schedule.on(self.date_rules.every_day("SPY"), self.time_rules.at(9, 0, 0), self._rebalance) + # Rebalance before market open to trade today's universe. + self.schedule.on( + self.date_rules.every_day("SPY"), + self.time_rules.at(9, 0), + self._rebalance + ) def _select_assets(self, data: List[QuiverLobbyingUniverse]) -> List[Symbol]: # Aggregate lobbying spend per ticker and keep names spending $100K+. @@ -26,8 +30,6 @@ def _select_assets(self, data: List[QuiverLobbyingUniverse]) -> List[Symbol]: def _rebalance(self) -> None: if not self._universe.selected: return - weight = 1 / len(self._universe.selected) targets = [PortfolioTarget(symbol, weight) for symbol in self._universe.selected] - - self.set_holdings(targets, liquidate_existing_holdings=True) + self.set_holdings(targets, True) diff --git a/project-templates/python/alternative-data-universe-quiverquantcongressuniverse/main.py b/project-templates/python/alternative-data-universe-quiverquantcongressuniverse/main.py index 90109f0be1..3173d07408 100644 --- a/project-templates/python/alternative-data-universe-quiverquantcongressuniverse/main.py +++ b/project-templates/python/alternative-data-universe-quiverquantcongressuniverse/main.py @@ -2,31 +2,33 @@ from AlgorithmImports import * # endregion + class QuiverQuantCongressUniverseAlgorithm(QCAlgorithm): def initialize(self) -> None: self.set_start_date(2024, 9, 1) self.set_end_date(2024, 12, 31) - self.set_cash(100000) - - self.universe_settings.resolution = Resolution.DAILY + self.set_cash(100_000) + self.settings.seed_initial_prices = True + self.universe_settings.resolution = Resolution.MINUTE # Universe of US Equities recently bought by US Congress members in trades over $200K. self._universe = self.add_universe(QuiverQuantCongressUniverse, self._select_assets) - - # Rebalance shortly after the open so today's universe is locked in. - self.schedule.on(self.date_rules.every_day("SPY"), self.time_rules.at(9, 0, 0), self._rebalance) + # Rebalance before market open to trade today's universe. + self.schedule.on( + self.date_rules.every_day("SPY"), + self.time_rules.at(9, 0), + self._rebalance + ) def _select_assets(self, data: List[QuiverQuantCongressUniverse]) -> List[Symbol]: # Keep buy disclosures over $200K to filter out small reports. return [d.symbol for d in data - if d.amount and d.amount > 200000 - and d.transaction == OrderDirection.BUY] + if d.amount and d.amount > 200000 and + d.transaction == OrderDirection.BUY] def _rebalance(self) -> None: if not self._universe.selected: return - weight = 1 / len(self._universe.selected) targets = [PortfolioTarget(symbol, weight) for symbol in self._universe.selected] - - self.set_holdings(targets, liquidate_existing_holdings=True) + self.set_holdings(targets, True) diff --git a/project-templates/python/alternative-data-universe-smartinsiderintentionuniverse/main.py b/project-templates/python/alternative-data-universe-smartinsiderintentionuniverse/main.py index 0a8a4c585f..dfa07ababf 100644 --- a/project-templates/python/alternative-data-universe-smartinsiderintentionuniverse/main.py +++ b/project-templates/python/alternative-data-universe-smartinsiderintentionuniverse/main.py @@ -2,31 +2,33 @@ from AlgorithmImports import * # endregion + class SmartInsiderIntentionUniverseAlgorithm(QCAlgorithm): def initialize(self) -> None: self.set_start_date(2024, 9, 1) self.set_end_date(2024, 12, 31) - self.set_cash(100000) - - self.universe_settings.resolution = Resolution.DAILY + self.set_cash(100_000) + self.settings.seed_initial_prices = True + self.universe_settings.resolution = Resolution.MINUTE # Universe of large-cap US Equities announcing meaningful buyback intentions. self._universe = self.add_universe(SmartInsiderIntentionUniverse, self._select_assets) - - # Rebalance shortly after the open so today's universe is locked in. - self.schedule.on(self.date_rules.every_day("SPY"), self.time_rules.at(9, 0, 0), self._rebalance) + # Rebalance before market open to trade today's universe. + self.schedule.on( + self.date_rules.every_day("SPY"), + self.time_rules.at(9, 0), + self._rebalance + ) def _select_assets(self, data: List[SmartInsiderIntentionUniverse]) -> List[Symbol]: # Keep $100M+ market-cap names announcing a buyback over 0.5% of shares. return [d.symbol for d in data - if d.percentage and d.usd_market_cap - and d.percentage > 0.005 and d.usd_market_cap > 100000000] + if d.percentage and d.usd_market_cap and + d.percentage > 0.005 and d.usd_market_cap > 100000000] def _rebalance(self) -> None: if not self._universe.selected: return - weight = 1 / len(self._universe.selected) targets = [PortfolioTarget(symbol, weight) for symbol in self._universe.selected] - - self.set_holdings(targets, liquidate_existing_holdings=True) + self.set_holdings(targets, True) diff --git a/project-templates/python/alternative-data-universe-smartinsidertransactionuniverse/main.py b/project-templates/python/alternative-data-universe-smartinsidertransactionuniverse/main.py index 62b73f2383..70bb11db96 100644 --- a/project-templates/python/alternative-data-universe-smartinsidertransactionuniverse/main.py +++ b/project-templates/python/alternative-data-universe-smartinsidertransactionuniverse/main.py @@ -2,31 +2,33 @@ from AlgorithmImports import * # endregion + class SmartInsiderTransactionUniverseAlgorithm(QCAlgorithm): def initialize(self) -> None: self.set_start_date(2024, 9, 1) self.set_end_date(2024, 12, 31) - self.set_cash(100000) - - self.universe_settings.resolution = Resolution.DAILY + self.set_cash(100_000) + self.settings.seed_initial_prices = True + self.universe_settings.resolution = Resolution.MINUTE # Universe of large-cap US Equities executing meaningful share buybacks. self._universe = self.add_universe(SmartInsiderTransactionUniverse, self._select_assets) - - # Rebalance shortly after the open so today's universe is locked in. - self.schedule.on(self.date_rules.every_day("SPY"), self.time_rules.at(9, 0, 0), self._rebalance) + # Rebalance before market open to trade today's universe. + self.schedule.on( + self.date_rules.every_day("SPY"), + self.time_rules.at(9, 0), + self._rebalance + ) def _select_assets(self, data: List[SmartInsiderTransactionUniverse]) -> List[Symbol]: # Keep $100M+ market-cap names buying back over 0.5% of shares. return [d.symbol for d in data - if d.buyback_percentage and d.usd_market_cap - and d.buyback_percentage > 0.005 and d.usd_market_cap > 100000000] + if d.buyback_percentage and d.usd_market_cap and + d.buyback_percentage > 0.005 and d.usd_market_cap > 100000000] def _rebalance(self) -> None: if not self._universe.selected: return - weight = 1 / len(self._universe.selected) targets = [PortfolioTarget(symbol, weight) for symbol in self._universe.selected] - - self.set_holdings(targets, liquidate_existing_holdings=True) + self.set_holdings(targets, True) diff --git a/project-templates/python/alternative-data-universe/main.py b/project-templates/python/alternative-data-universe/main.py index 2412fefc76..3ed910c2ea 100644 --- a/project-templates/python/alternative-data-universe/main.py +++ b/project-templates/python/alternative-data-universe/main.py @@ -2,21 +2,20 @@ from AlgorithmImports import * # endregion + class UpcomingEarningsExampleAlgorithm(QCAlgorithm): def initialize(self) -> None: self.set_start_date(2024, 12, 1) self.set_end_date(2024, 12, 31) - self.set_cash(100000) - + self.set_cash(100_000) # Seed the last price as price since we need to use the underlying price for option contract filtering when it join the universe. self.settings.seed_initial_prices = True - # Trade on daily basis based on daily upcoming earnings signals. - self.universe_settings.resolution = Resolution.DAILY + self.universe_settings.resolution = Resolution.MINUTE # Option trading requires raw price for strike price comparison. self.universe_settings.data_normalization_mode = DataNormalizationMode.RAW - # Universe consists of equities with upcoming earnings events. + # Universe consists of Equities with upcoming earnings events. self.add_universe(EODHDUpcomingEarnings, self._selection) def _selection(self, earnings: List[EODHDUpcomingEarnings]) -> List[Symbol]: @@ -32,7 +31,7 @@ def _selection(self, earnings: List[EODHDUpcomingEarnings]) -> List[Symbol]: return [] def on_securities_changed(self, changes: SecurityChanges) -> None: - # Actions only based on the equity universe changes. + # Actions only based on the Equity universe changes. for security in changes.added_securities: if security.type != SecurityType.EQUITY: continue @@ -52,8 +51,7 @@ def on_securities_changed(self, changes: SecurityChanges) -> None: ], 1 ) - - # Actions only based on the equity universe changes. + # Actions only based on the Equity universe changes. for security in changes.removed_securities: if security.type != SecurityType.EQUITY: continue @@ -66,7 +64,6 @@ def on_securities_changed(self, changes: SecurityChanges) -> None: def _select_option_contracts(self, underlying: Equity) -> tuple[Any, Any]: # Get all tradable option contracts for filtering. option_contract_list = self.option_chain(underlying) - # Expiry at least 30 days later to have a smaller theta to reduce time decay loss. # Yet also be ensure liquidity over the volatility fluctuation hence we take the closet expiry after that. long_expiries = [x for x in option_contract_list if x.expiry >= self.time + timedelta(30)] @@ -74,14 +71,12 @@ def _select_option_contracts(self, underlying: Equity) -> tuple[Any, Any]: return None, None expiry = min(x.expiry for x in long_expiries) filtered_contracts = [x for x in option_contract_list if x.expiry == expiry] - # Select ATM call and put to form a straddle for trading the volatility. - strike = sorted(filtered_contracts, + strike = sorted(filtered_contracts, key=lambda x: abs(x.strike - underlying.price))[0].strike atm_contracts = [x for x in filtered_contracts if x.strike == strike] if len(atm_contracts) < 2: return None, None - atm_call = next(filter(lambda x: x.right == OptionRight.CALL, atm_contracts)) atm_put = next(filter(lambda x: x.right == OptionRight.PUT, atm_contracts)) return atm_call, atm_put diff --git a/project-templates/python/alternative-data/main.py b/project-templates/python/alternative-data/main.py index 9ed2768883..90d6453e18 100644 --- a/project-templates/python/alternative-data/main.py +++ b/project-templates/python/alternative-data/main.py @@ -2,34 +2,33 @@ from AlgorithmImports import * # endregion + class BrainMLRankingDataAlgorithm(QCAlgorithm): def initialize(self) -> None: self.set_start_date(2024, 9, 1) self.set_end_date(2024, 12, 31) - self.set_cash(100000) + self.set_cash(100_000) # Seed the price of each asset with its last known price to avoid trading errors. self.settings.seed_initial_prices = True - # We cherry picked 5 largest stocks, high trading volume provides better information and credibility for ML ranking + # Select 5 high-volume, large-cap stocks; higher trading volume improves ML ranking credibility. tickers = ["AAPL", "TSLA", "MSFT", "F", "KO"] for ticker in tickers: - # Requesting data to get 2 days estimated relative ranking - equity = self.add_equity(ticker, Resolution.DAILY) + # Requesting data to get 2 days estimated relative ranking. + equity = self.add_equity(ticker) self.add_data(BrainStockRanking2Day, equity) - - def on_data(self, slice: Slice) -> None: - # Collect rankings for all selected symbols for ranking them - points = slice.get(BrainStockRanking2Day) + + def on_data(self, data: Slice) -> None: + # Get Brain ML ranking data for all symbols in the current slice. + points = data.get(BrainStockRanking2Day) if points is None: return sum_of_ranks = sum(abs(x.rank) for x in points.values()) * .9 if sum_of_ranks == 0: return - - # Rank each symbol's Brain ML ranking relative to each other for positional sizing + # Plot each symbol's ML rank score. for x in points.values(): self.plot("Rank", x.symbol.underlying, x.rank) - - # Place orders according to the ML ranking, the better the rank, the higher the estimated return and hence weight + # Allocate weight proportional to each symbol's ML rank score. targets = [PortfolioTarget(x.symbol.underlying, x.rank/sum_of_ranks) for x in points.values()] self.set_holdings(targets) diff --git a/project-templates/python/cfd/main.py b/project-templates/python/cfd/main.py index 45c95c8100..719436634c 100644 --- a/project-templates/python/cfd/main.py +++ b/project-templates/python/cfd/main.py @@ -2,21 +2,18 @@ from AlgorithmImports import * # endregion + class CfdExampleAlgorithm(QCAlgorithm): - + def initialize(self) -> None: self.set_start_date(2024, 9, 1) self.set_end_date(2024, 12, 31) - # Seed the price of each asset with its last known price to - # avoid trading errors. + # Seed the price of each asset with its last known price to avoid trading errors. self.settings.seed_initial_prices = True - # Let's select CFD contracts that trade in different market - # hours so the algorithm is always invested. + # Trade CFDs across different markets to remain invested around the clock. for ticker in ['DE30EUR', 'SG30SGD', 'US30USD']: # Add the CFD. cfd = self.add_cfd(ticker) - # Set scheduled events to hold each CFD contract during - # their regular trading hours. # Buy after market open. self.schedule.on( self.date_rules.every_day(cfd), diff --git a/project-templates/python/chained-universe/main.py b/project-templates/python/chained-universe/main.py index c8ba0172ac..0cedb95de0 100644 --- a/project-templates/python/chained-universe/main.py +++ b/project-templates/python/chained-universe/main.py @@ -2,21 +2,25 @@ from AlgorithmImports import * # endregion + class ChainedUniverseAlgorithm(QCAlgorithm): _weight_by_symbol: dict[Symbol, float] = {} def initialize(self) -> None: self.set_start_date(2024, 9, 1) self.set_end_date(2024, 12, 31) - self.set_cash(100000) + self.set_cash(100_000) self.settings.seed_initial_prices = True - # Select QQQ constituents first, then by fundamental data. self._universe = self.add_universe( - self.universe.etf("QQQ", Market.USA, self.universe_settings, self._etf_constituents_filter), + self.universe.etf("QQQ", Market.USA, self.universe_settings, self._etf_constituents_filter), self._fundamental_selection ) - self.schedule.on(self.date_rules.every_day("QQQ"), self.time_rules.before_market_open("QQQ", 30), self._place_orders) + self.schedule.on( + self.date_rules.every_day("QQQ"), + self.time_rules.before_market_open("QQQ", 30), + self._place_orders + ) def _etf_constituents_filter(self, constituents: List[ETFConstituentUniverse]) -> List[Symbol]: # Select all QQQ constituents. diff --git a/project-templates/python/crypto-static/main.py b/project-templates/python/crypto-static/main.py index f2b931ab26..cffb16913e 100644 --- a/project-templates/python/crypto-static/main.py +++ b/project-templates/python/crypto-static/main.py @@ -2,23 +2,26 @@ from AlgorithmImports import * # endregion + class CryptoExampleAlgorithm(QCAlgorithm): def initialize(self) -> None: self.set_start_date(2024, 9, 1) self.set_end_date(2024, 12, 31) - # Set the account current to USDT as we will trade USDT quoted coins + # Set the account current to USDT as we will trade USDT quoted coins. self.set_account_currency("USDT", 100000) # Set the brokerage and account type to match your brokerage environment for accurate fee and margin behavior. self.set_brokerage_model(BrokerageName.BITFINEX, AccountType.CASH) - # For daily DCA purchases, subscribe to daily asset data. coins = ["BTC", "ETH", "LTC"] pairs = [self.add_crypto(f"{coin}{self.account_currency}", Resolution.MINUTE) for coin in coins] self.set_benchmark(pairs[0]) - - self.schedule.on(self.date_rules.week_start(), self.time_rules.midnight, self._rebalance) + self.schedule.on( + self.date_rules.week_start(), + self.time_rules.midnight, + self._rebalance + ) def _rebalance(self) -> None: targets = [PortfolioTarget(s, .3) for s in self.securities.keys()] - self.set_holdings(targets, liquidate_existing_holdings=True) + self.set_holdings(targets, True) diff --git a/project-templates/python/crypto-universe/main.py b/project-templates/python/crypto-universe/main.py index b65a12cd13..019b4e7162 100644 --- a/project-templates/python/crypto-universe/main.py +++ b/project-templates/python/crypto-universe/main.py @@ -2,26 +2,28 @@ from AlgorithmImports import * # endregion + class LargeCapCryptoUniverseAlgorithm(QCAlgorithm): def initialize(self) -> None: self.set_start_date(2024, 9, 1) self.set_end_date(2024, 12, 31) self.settings.seed_initial_prices = True - # Set the account currency to USDT - self.set_account_currency("USDT") + # Set the account currency to USDT. + self.set_account_currency("USDT") self.set_brokerage_model(BrokerageName.COINBASE, AccountType.CASH) - def selector(data: List[CryptoUniverse]) -> List[Symbol]: selected = [x for x in data if x.volume_in_usd and x.symbol.value.endswith(self.account_currency)] selected = sorted(selected, key=lambda x: x.volume_in_usd, reverse=True)[:10] return [x.symbol for x in selected] - # Add the Crypto universe and define the selection function. self._universe = self.add_universe(CryptoUniverse.coinbase(selector)) - # Add a Sheduled Event to rebalance the portfolio. - self.schedule.on(self.date_rules.every_day(), self.time_rules.at(12, 0), self._rebalance) + self.schedule.on( + self.date_rules.every_day(), + self.time_rules.at(12, 0), + self._rebalance + ) def _rebalance(self) -> None: if not self._universe.selected: diff --git a/project-templates/python/custom-consolidators/main.py b/project-templates/python/custom-consolidators/main.py index 730247cd0c..c6fe77666d 100644 --- a/project-templates/python/custom-consolidators/main.py +++ b/project-templates/python/custom-consolidators/main.py @@ -2,8 +2,9 @@ from AlgorithmImports import * # endregion + class CalendarConsolidatorExampleAlgorithm(QCAlgorithm): - + def initialize(self) -> None: self.set_start_date(2024, 9, 1) self.set_end_date(2024, 12, 31) @@ -13,8 +14,8 @@ def initialize(self) -> None: self._pair.ema = ExponentialMovingAverage(10) # Create a QuoteBar consolidator with a custom consolidation period. consolidator = QuoteBarConsolidator(self._daily_forex_consolidation_period) - # You can also create a consolidator with a period of one day and start time of 17 - # consolidator = QuoteBarConsolidator(timedelta(1), timedelta(hours=17)) + # You can also create a consolidator with a period of one day and start time of 17. + # Consolidator = QuoteBarConsolidator(timedelta(1), timedelta(hours=17)). # Attach a consolidation handler that will receive the consolidated bars. consolidator.data_consolidated += self._consolidation_handler # Subscribe the consolidator for automatic updates with the prices of the pair. @@ -25,7 +26,7 @@ def initialize(self) -> None: history = self.history[QuoteBar](self._pair.symbol, 29000, Resolution.MINUTE) for bar in history: consolidator.update(bar) - + # Define the consolidation period. def _daily_forex_consolidation_period(self, dt: datetime) -> CalendarInfo: # Set the start of the bar to be 5 PM ET. @@ -44,7 +45,6 @@ def _consolidation_handler(self, sender: object, consolidated_bar: QuoteBar) -> # Plot the closing price and the EMA. self.plot(consolidated_bar.symbol.value, 'Close', consolidated_bar.close) self.plot(consolidated_bar.symbol.value, 'EMA', self._pair.ema.current.value) - if not self._pair.holdings.is_long and consolidated_bar.close > self._pair.ema.current.value: self.set_holdings(self._pair, 1) if consolidated_bar.close < self._pair.ema.current.value: diff --git a/project-templates/python/custom-data-ticker-mapping/main.py b/project-templates/python/custom-data-ticker-mapping/main.py index 7d9565c172..a12fcbe241 100644 --- a/project-templates/python/custom-data-ticker-mapping/main.py +++ b/project-templates/python/custom-data-ticker-mapping/main.py @@ -16,24 +16,21 @@ class CustomDataTradeProviderAlgorithm(QCAlgorithm): def initialize(self) -> None: self.set_start_date(2020, 1, 1) self.set_end_date(2024, 12, 31) - - # Save the data to the object store + # Save the data to the object store. if not self.object_store.contains_key("selected_trades.csv"): self.object_store.save("selected_trades.csv", CONTENT) - self.settings.seed_initial_prices = True - self.add_data(SelectedTrades, "X") - def on_data(self, slice: Slice) -> None: - for symbol, data in slice.get(SelectedTrades).items(): + def on_data(self, data: Slice) -> None: + for symbol, data in data.get(SelectedTrades).items(): if not symbol in self.securities: - self.add_security(symbol) + self.add_security(symbol) self.market_order(symbol, data.quantity) class SelectedTrades(PythonData): - + def get_source(self, config: SubscriptionDataConfig, date: datetime, is_live_mode: bool) -> SubscriptionDataSource: return SubscriptionDataSource("selected_trades.csv", SubscriptionTransportMedium.OBJECT_STORE) @@ -41,19 +38,16 @@ def reader(self, config: SubscriptionDataConfig, line: str, date: datetime, is_l if not line.strip(): return None data = [x.strip() for x in line.split(',')] - try: ticker = data[1] time = datetime.strptime(data[0], "%Y-%m-%d") - # Create the SecurityIdentifier with the point-in-time ticker and the current date - # In this example, we trade META in 2020 when its ticker was FB - # Then, we will see it when it is META + # Create the SecurityIdentifier with the point-in-time ticker and the current date. + # This example uses META, which traded under the ticker FB in 2020. + # From 2024 onwards, the symbol resolves to META. security_id = SecurityIdentifier.generate_equity(ticker, Market.USA, mapping_resolve_date=time) - if security_id.date.year < 1998: - # Ticker not found in QuantConnect database on this date + # Ticker not found in QuantConnect database on this date. return None - trade = SelectedTrades() trade.symbol = Symbol(security_id, ticker) trade.end_time = time diff --git a/project-templates/python/custom-data/main.py b/project-templates/python/custom-data/main.py index cbedbacd7d..3c848f8f2b 100644 --- a/project-templates/python/custom-data/main.py +++ b/project-templates/python/custom-data/main.py @@ -2,20 +2,22 @@ from AlgorithmImports import * # endregion + class CustomDataBitstampAlgorithm(QCAlgorithm): def initialize(self) -> None: self.set_start_date(2020, 9, 1) self.set_end_date(2020, 12, 31) - self.set_cash(100000) + self.set_cash(100_000) + self.settings.seed_initial_prices = True # Define the "type" of our generic data: self._btc = self.add_data(Bitstamp, "BTC") # Get some historical data. history = self.history(Bitstamp, self._btc, 200, Resolution.DAILY) - def on_data(self, slice: Slice) -> None: + def on_data(self, data: Slice) -> None: # Get the data of the current day. - data = slice.get(self._btc) + data = data.get(self._btc) if not data: return self.plot(self._btc.symbol.value, 'Price', data.close) @@ -27,7 +29,7 @@ def get_source(self, config: SubscriptionDataConfig, date: datetime, is_live_mod if is_live_mode: return SubscriptionDataSource('https://www.bitstamp.net/api/ticker/', SubscriptionTransportMedium.REST) return SubscriptionDataSource( - "https://raw.githubusercontent.com/QuantConnect/Documentation/master/Resources/datasets/custom-data/bitstampusd.csv", + "https://raw.githubusercontent.com/QuantConnect/Documentation/master/Resources/datasets/custom-data/bitstampusd.csv", SubscriptionTransportMedium.REMOTE_FILE ) @@ -39,9 +41,9 @@ def reader(self, config: SubscriptionDataConfig, line: str, date: datetime, is_l # In live trading, parse the JSON file. if is_live_mode: # Example Line Format: - # {"high": "441.00", "last": "421.86", "timestamp": "1411606877", "bid": "421.96", "vwap": "428.58", "volume": "14120.40683975", "low": "418.83", "ask": "421.99"} + # {"high": "441.00", "last": "421.86", "timestamp": "1411606877", "bid": "421.96", "vwap": "428.58", "volume": "14120.40683975", "low": "418.83", "ask": "421.99"}. live_btc = json.loads(line) - # If value is zero, return None + # If value is zero, return None. coin.value = float(live_btc["last"]) if coin.value == 0: return None @@ -58,15 +60,14 @@ def reader(self, config: SubscriptionDataConfig, line: str, date: datetime, is_l coin["VolumeBTC"] = float(live_btc["volume"]) coin["WeightedPrice"] = float(live_btc["vwap"]) return coin - # In backtests, parse the CSV file. # Example Line Format: - # Date Open High Low Close Volume (BTC) Volume (Currency) Weighted Price - # 2011-09-13 5.8 6.0 5.65 5.97 58.37138238, 346.0973893944 5.929230648356 + # Date Open High Low Close Volume (BTC) Volume (Currency) Weighted Price. + # 2011-09-13 5.8 6.0 5.65 5.97 58.37138238, 346.0973893944 5.929230648356. if not line[0].isdigit(): return None data = line.split(',') - # If value is zero, return None + # If value is zero, return None. coin.value = float(data[4]) if coin.value == 0: return None diff --git a/project-templates/python/custom-indicator/main.py b/project-templates/python/custom-indicator/main.py index fac808ba2f..576783e0c3 100644 --- a/project-templates/python/custom-indicator/main.py +++ b/project-templates/python/custom-indicator/main.py @@ -15,8 +15,8 @@ def initialize(self) -> None: # Warm up for immediate usage of indicators. self.set_warm_up(20, Resolution.DAILY) - def on_data(self, slice: Slice) -> None: - bar = slice.bars.get(self._spy) + def on_data(self, data: Slice) -> None: + bar = data.bars.get(self._spy) if not bar: return # Update the custom MFI with the updated trade bar to obtain the updated trade signal. @@ -40,31 +40,25 @@ def __init__(self, period: int) -> None: self._previous_typical_price = 0 self._negative_money_flow: RollingWindow[float] = RollingWindow(period) self._positive_money_flow: RollingWindow[float] = RollingWindow(period) - + def update(self, input: BaseData) -> bool: if not isinstance(input, TradeBar): raise TypeError('CustomMoneyFlowIndex.update: input must be a TradeBar') - # Estimate the money flow by averaging the price multiplied by volume. typical_price = (input.high + input.low + input.close) / 3 money_flow = typical_price * input.volume - # We need to avoid double-rounding errors. if abs(self._previous_typical_price / typical_price - 1) < 1e-10: self._previous_typical_price = typical_price - # Add the period money flow to calculate the aggregated money flow. self._negative_money_flow.add(money_flow if typical_price < self._previous_typical_price else 0) self._positive_money_flow.add(money_flow if typical_price > self._previous_typical_price else 0) self._previous_typical_price = typical_price - - positive_money_flow_sum = sum(self._positive_money_flow) + positive_money_flow_sum = sum(self._positive_money_flow) total_money_flow = positive_money_flow_sum + sum(self._negative_money_flow) - # Set the value to be the positive money flow ratio. self.value = 100 if total_money_flow != 0: self.value *= positive_money_flow_sum / total_money_flow - # Set the is_ready property to receive the required bars to fill all windows. return self._positive_money_flow.is_ready diff --git a/project-templates/python/custom-models/main.py b/project-templates/python/custom-models/main.py index 68a6fe1e54..35dbd5278d 100644 --- a/project-templates/python/custom-models/main.py +++ b/project-templates/python/custom-models/main.py @@ -2,39 +2,36 @@ from AlgorithmImports import * # endregion + class CustomModelslgorithm(QCAlgorithm): def initialize(self) -> None: self.set_start_date(2024, 9, 12) self.set_end_date(2024, 10, 1) - self.set_cash(1000000) - - # The brokerage model sets the reality models that reflect the brokerage behavior + self.set_cash(1_000_000) + self.settings.seed_initial_prices = True + # The brokerage model sets the reality models that reflect the brokerage behavior. self.set_brokerage_model(BrokerageName.INTERACTIVE_BROKERS_BROKERAGE, AccountType.MARGIN) - def custom_security_initalizer(security: Security) -> None: security.set_fee_model(CustomFeeModel(self)) security.set_fill_model(CustomFillModel(self)) security.set_slippage_model(CustomSlippageModel(self)) - - # We can set different models for different asset classes + # Apply NullBuyingPowerModel for Options to allow unconstrained position sizing. if security.Type in [SecurityType.OPTION, SecurityType.INDEX_OPTION]: security.set_buying_power_model(BuyingPowerModel.NULL) else: security.set_buying_power_model(CustomBuyingPowerModel(self)) - - # Override some of the models + # Override some of the models. self.add_security_initializer(custom_security_initalizer) - - # Request SPY data to trade after we set the model and security initializer + # Request SPY data to trade after we set the model and security initializer. self.add_equity("SPY") - def on_data(self, slice: Slice) -> None: + def on_data(self, data: Slice) -> None: if not self.portfolio.invested: self.set_holdings("SPY", 1) -# If we want to use methods from other models, you need to inherit from one of them +# Inherit from ImmediateFillModel to reuse base logic and override only market_fill. class CustomFillModel(ImmediateFillModel): def __init__(self, algorithm: QCAlgorithm) -> None: @@ -45,14 +42,11 @@ def __init__(self, algorithm: QCAlgorithm) -> None: def market_fill(self, asset: Security, order: MarketOrder) -> OrderEvent: absolute_remaining = order.absolute_quantity - if order.id in self._absolute_remaining_by_order_id.keys(): absolute_remaining = self._absolute_remaining_by_order_id[order.id] - fill = super().market_fill(asset, order) absolute_fill_quantity = int(min(absolute_remaining, self._random.next(0, 2*int(order.absolute_quantity)))) fill.fill_quantity = np.sign(order.quantity) * absolute_fill_quantity - if absolute_remaining == absolute_fill_quantity: fill.status = OrderStatus.FILLED if self._absolute_remaining_by_order_id.get(order.id): @@ -72,7 +66,7 @@ def __init__(self, algorithm: QCAlgorithm) -> None: self._algorithm = algorithm def get_order_fee(self, parameters: OrderFeeParameters) -> OrderFee: - # custom fee math + # Custom fee math. fee = max(1, parameters.security.price * parameters.order.absolute_quantity * 0.00001) @@ -86,7 +80,7 @@ def __init__(self, algorithm: QCAlgorithm) -> None: self._algorithm = algorithm def get_slippage_approximation(self, asset: Security, order: Order) -> float: - # custom slippage math + # Custom slippage math. slippage = asset.price * 0.0001 * float(np.log10(2 * order.absolute_quantity)) self._algorithm.log(f"CustomSlippageModel: {slippage}") return slippage @@ -99,14 +93,13 @@ def __init__(self, algorithm: QCAlgorithm) -> None: self._algorithm = algorithm def has_sufficient_buying_power_for_order(self, parameters: HasSufficientBuyingPowerForOrderParameters) -> HasSufficientBuyingPowerForOrderResult: - # custom behavior: this model will assume that there is always enough buying power + # Custom behavior: this model will assume that there is always enough buying power. has_sufficient_buying_power_for_order_result = HasSufficientBuyingPowerForOrderResult(True) self._algorithm.log(f"CustomBuyingPowerModel: {has_sufficient_buying_power_for_order_result.is_sufficient}") return has_sufficient_buying_power_for_order_result -# The simple fill model shows how to implement a simpler version of -# the most popular order fills: Market, Stop Market and Limit +# Simplified fill model implementing Market, Stop Market, and Limit fills. class SimpleCustomFillModel(FillModel): def __init__(self) -> None: @@ -125,15 +118,13 @@ def _set_order_event_to_filled(self, fill: OrderEvent, fill_price: float, fill_q def _get_trade_bar(self, asset: Security, order_direction: OrderDirection) -> TradeBar: trade_bar = asset.cache.get_data(TradeBar) if trade_bar: return trade_bar - - # Tick-resolution data doesn't have TradeBar, use the asset price + # Tick-resolution data doesn't have TradeBar, use the asset price. price = asset.price return TradeBar(asset.local_time, asset.symbol, price, price, price, price, 0) def market_fill(self, asset: Security, order: Order) -> OrderEvent: fill = self._create_order_event(asset, order) if order.status == OrderStatus.CANCELED: return fill - return self._set_order_event_to_filled( fill, asset.cache.ask_price if order.direction == OrderDirection.BUY else asset.cache.bid_price, @@ -143,29 +134,21 @@ def market_fill(self, asset: Security, order: Order) -> OrderEvent: def stop_market_fill(self, asset: Security, order: Order) -> OrderEvent: fill = self._create_order_event(asset, order) if order.status == OrderStatus.CANCELED: return fill - stop_price = order.stop_price trade_bar = self._get_trade_bar(asset, order.direction) - if order.direction == OrderDirection.SELL and trade_bar.low < stop_price: return self._set_order_event_to_filled(fill, stop_price, order.quantity) - if order.direction == OrderDirection.BUY and trade_bar.high > stop_price: return self._set_order_event_to_filled(fill, stop_price, order.quantity) - return fill def limit_fill(self, asset: Security, order: Order) -> OrderEvent: fill = self._create_order_event(asset, order) if order.status == OrderStatus.CANCELED: return fill - limit_price = order.limit_price trade_bar = self._get_trade_bar(asset, order.direction) - if order.direction == OrderDirection.SELL and trade_bar.high > limit_price: return self._set_order_event_to_filled(fill, limit_price, order.quantity) - if order.direction == OrderDirection.BUY and trade_bar.low < limit_price: return self._set_order_event_to_filled(fill, limit_price, order.quantity) - return fill diff --git a/project-templates/python/equities-static-universe/main.py b/project-templates/python/equities-static-universe/main.py index 25afb09db8..e0aca7bfd4 100644 --- a/project-templates/python/equities-static-universe/main.py +++ b/project-templates/python/equities-static-universe/main.py @@ -2,19 +2,25 @@ from AlgorithmImports import * # endregion + class EquitiesStaticTemplateAlgorithm(QCAlgorithm): _tolerance = 0.0025 def initialize(self) -> None: self.set_start_date(2024, 9, 1) self.set_end_date(2024, 12, 31) - self.set_cash(100000) + self.set_cash(100_000) + self.settings.seed_initial_prices = True self.settings.automatic_indicator_warm_up = True for ticker in ["SPY", "QQQ", "IWM"]: equity = self.add_equity(ticker) equity.macd = self.macd(equity, 12, 26, 9, MovingAverageType.EXPONENTIAL, Resolution.DAILY) self.plot_indicator(ticker, equity.macd) - self.schedule.on(self.date_rules.every_day('SPY'), self.time_rules.after_market_open('SPY', 1), self._rebalance) + self.schedule.on( + self.date_rules.every_day('SPY'), + self.time_rules.after_market_open('SPY', 1), + self._rebalance + ) def _rebalance(self) -> None: for security in self.securities.values(): diff --git a/project-templates/python/equities-universe/main.py b/project-templates/python/equities-universe/main.py index 2149c0a4ed..b25dc0e373 100644 --- a/project-templates/python/equities-universe/main.py +++ b/project-templates/python/equities-universe/main.py @@ -2,53 +2,52 @@ from AlgorithmImports import * # endregion + class EmaCrossUniverseSelectionAlgorithm(QCAlgorithm): _averages: dict[Symbol, 'SelectionData'] = {} _count = 10 _tolerance = 0.01 _target_percent = 0.09 - + def initialize(self) -> None: self.set_start_date(2021, 1, 1) self.set_end_date(2024, 12, 31) - self.set_cash(100000) + self.set_cash(100_000) self.settings.seed_initial_prices = True self.universe_settings.leverage = 2 - self.universe_settings.resolution = Resolution.DAILY - + self.universe_settings.resolution = Resolution.MINUTE def _filter(fundamentals: List[Fundamental]) -> List[Symbol]: selected = {} for f in fundamentals: - # grab the SelectionData instance for this symbol + # Grab the SelectionData instance for this symbol. self._averages[f.symbol] = avg = self._averages.get(f.symbol, SelectionData()) - # Update returns true when the indicators are ready, so don't accept until they are - # and only pick symbols who have their market cap above 1Bi and 100 day ema over their 300 day ema + # Update returns true when the indicators are ready, so don't accept until they are. + # Accept symbols with market cap above $10B and the fast EMA above the slow EMA. if (avg.update(f.end_time, f.adjusted_price) and f.market_cap > 10_000_000_000 and avg.fast.current.value > avg.slow.current.value * (1 + self._tolerance)): selected[f.symbol] = avg if len(selected) > self._count: - # Prefer symbols with a larger delta by percentage between the two averages + # Prefer symbols with a larger delta by percentage between the two averages. selected = dict(sorted(selected.items(), key=lambda x: x[1].scaled_delta(), reverse=True)[:self._count]) return list(selected.keys()) - self._universe = self.add_universe(_filter) self.set_warm_up(400, Resolution.DAILY) def on_data(self, data: Slice) -> None: - # we'll simply go long each security in the universe - targets = [PortfolioTarget(x, self._target_percent) + # Go long each universe security at the target weight. + targets = [PortfolioTarget(x, self._target_percent) for x in self._universe.selected if self.securities[x].has_data] self.set_holdings(targets, True) - -# class used to improve readability of the fundamental selection function + +# Class used to improve readability of the fundamental selection function. class SelectionData: def __init__(self) -> None: self.fast = ExponentialMovingAverage(100) self.slow = ExponentialMovingAverage(300) - - # Updates the EMA100 and EMA300 indicators, returning true when they're both ready + + # Updates the EMA100 and EMA300 indicators, returning true when they're both ready. def update(self, time: datetime, value: float) -> bool: return self.fast.update(time, value) & self.slow.update(time, value) diff --git a/project-templates/python/equity-options-static/main.py b/project-templates/python/equity-options-static/main.py index 95dbae620e..80d77c644b 100644 --- a/project-templates/python/equity-options-static/main.py +++ b/project-templates/python/equity-options-static/main.py @@ -2,45 +2,44 @@ from AlgorithmImports import * # endregion + class EquityOptionAlgorithm(QCAlgorithm): def initialize(self) -> None: self.set_start_date(2024, 9, 1) self.set_end_date(2024, 12, 31) - self.set_cash(100000) + self.set_cash(100_000) # Seed the price of each asset with its last known price to avoid trading errors. self.settings.seed_initial_prices = True # Set the data normalization mode as raw for option strike-price comparability. self._spy = self.add_equity("SPY", data_normalization_mode=DataNormalizationMode.RAW) - self.schedule.on(self.date_rules.every(DayOfWeek.MONDAY), self.time_rules.after_market_open(self._spy, 1), self._buy_covered_call) + self.schedule.on( + self.date_rules.every(DayOfWeek.MONDAY), + self.time_rules.after_market_open(self._spy, 1), + self._buy_covered_call + ) def _buy_covered_call(self) -> None: '''Buy a Covered Call: Buy the underlying and sell the ATM call. ''' next_friday = (self.time + timedelta(4)).date() - chain = self.option_chain(self._spy) contracts = sorted([x for x in chain if x.right == OptionRight.CALL and x.expiry.date() == next_friday], key=lambda x: abs(self._spy.price - x.strike)) - - # If we cannot find a contract expiring on Friday, it is likely a holiday + # If we cannot find a contract expiring on Friday, it is likely a holiday. # For simplicity, we will not trade and close the underlying position, if any. if not contracts: self.liquidate(self._spy, tag=f"Cannot find ATM Call expiring next Friday {next_friday:%yMd}") return - atm_call = contracts[0] contract = self.add_option_contract(atm_call) contract_multiplier = contract.contract_multiplier - - # We will invest 100% of the portfolio value observing the contract multiplier - # For example, if 100% of the portfolio value is 345 shares of SPY, we will invest 300 + # Size the equity position to 100% of portfolio, rounded down to the nearest contract multiple. + # For example, 345 shares rounds down to 300 to match the 100-share contract size. # Then, we sell 3 contracts of ATM call. If we are exercised, the position is closed. equity_order_quantity = np.floor(self.calculate_order_quantity(self._spy, 1) / contract_multiplier) * contract_multiplier - # If we are invested in the underlying, the hedge takes into account the final quantity + # Sell one call contract per 100 shares, accounting for any existing underlying position. atm_call_order_quantity = -(self._spy.holdings.quantity + equity_order_quantity) / contract_multiplier - if equity_order_quantity: self.market_order(self._spy, equity_order_quantity) - self.market_order(atm_call, atm_call_order_quantity) diff --git a/project-templates/python/equity-options-universe/main.py b/project-templates/python/equity-options-universe/main.py index 61e48d303b..6a1d885f6f 100644 --- a/project-templates/python/equity-options-universe/main.py +++ b/project-templates/python/equity-options-universe/main.py @@ -2,13 +2,14 @@ from AlgorithmImports import * # endregion + class EquityOptionAlgorithm(QCAlgorithm): - + def initialize(self) -> None: self.set_start_date(2024, 9, 1) self.set_end_date(2024, 12, 31) - self.set_cash(100000) - + self.set_cash(100_000) + self.settings.seed_initial_prices = True self._option = self.add_option("SPY") # Using Staddle() method, it will only return the best-matched ATM call and put contracts expiring after 30 days. # It provides better accuracy in filtering, and subscribe only to the needed contracts to save computation resources. @@ -22,7 +23,6 @@ def on_data(self, data: Slice) -> None: if chain: # There should only be 1 expiry and 1 strike from the 2 contracts returned, getting from either contract is fine. expiry, strike = chain[0].expiry, chain[0].strike - # Forms a long straddle option strategy using abstraction method ensure accuracy. long_straddle = OptionStrategies.straddle(self._option, strike, expiry) - self.buy(long_straddle, 1) \ No newline at end of file + self.buy(long_straddle, 1) diff --git a/project-templates/python/financial-advisors/main.py b/project-templates/python/financial-advisors/main.py index e873d1df77..8d232f0c70 100644 --- a/project-templates/python/financial-advisors/main.py +++ b/project-templates/python/financial-advisors/main.py @@ -2,37 +2,36 @@ from AlgorithmImports import * # endregion + class FinancialAdvisorAlgorithmAlgorithm(QCAlgorithm): _connected = False def initialize(self) -> None: self.set_start_date(2024, 9, 12) self.set_end_date(2024, 10, 1) - self.set_cash(1000000) - - # Financial Advisor is a live trading feature of the Interactive Brokers integration + self.set_cash(1_000_000) + self.settings.seed_initial_prices = True + # Financial Advisor is a live trading feature of the Interactive Brokers integration. self.set_brokerage_model(BrokerageName.INTERACTIVE_BROKERS_BROKERAGE, AccountType.MARGIN) - - # Set the default order properties + # Set the default order properties. self.default_order_properties = InteractiveBrokersOrderProperties() self.default_order_properties.fa_group = "TestGroupEQ" self.default_order_properties.fa_method = "Equal" self.default_order_properties.account = "DU123456" - # Request SPY data to trade. self.add_equity("SPY") - def on_data(self, slice: Slice) -> None: + def on_data(self, data: Slice) -> None: if not self.portfolio.invested: self.set_holdings("SPY", 1) - #region Live trading features + # region Live trading features def _notify_all(self, subject: str, message: str) -> None: self.notify.email("email@address.com", subject, message) message = f"{self.time:yyyyMMdd}: {subject} > {message}" self.log(message) # See https://www.quantconnect.com/docs/v2/writing-algorithms/live-trading/notifications - # for all notification methods + # For all notification methods. self.notify.sms("+16191234567", message) def on_brokerage_disconnect(self) -> None: @@ -49,4 +48,4 @@ def on_brokerage_message(self, message_event: BrokerageMessageEvent) -> None: self._notify_all(f"Brokerage Message", str(message_event)) case _: self.log(str(message_event)) - #endregion \ No newline at end of file + # endregion diff --git a/project-templates/python/forex/main.py b/project-templates/python/forex/main.py index a0f77ad63d..02d3da13ba 100644 --- a/project-templates/python/forex/main.py +++ b/project-templates/python/forex/main.py @@ -2,12 +2,13 @@ from AlgorithmImports import * # endregion + class ForexExampleAlgorithm(QCAlgorithm): def initialize(self) -> None: self.set_start_date(2024, 9, 1) self.set_end_date(2024, 12, 31) - # Add the some trading pair. + # Subscribe to each Forex pair. pairs = ["EURUSD", "USDJPY", "USDCAD"] for pair in pairs: forex = self.add_forex(pair) @@ -17,16 +18,14 @@ def initialize(self) -> None: for quote_bar in self.history[QuoteBar](forex.symbol, 12*60): forex.spread_low.update(quote_bar.end_time, quote_bar.ask.close - quote_bar.bid.close) - def on_data(self, slice: Slice) -> None: - # Ensure we have quote data in the current slice. - for symbol, quote_bar in slice.quote_bars.items(): - # Bid-ask spread = Ask price - Bid price + def on_data(self, data: Slice) -> None: + # Iterate over available quote bars to evaluate spread conditions. + for symbol, quote_bar in data.quote_bars.items(): + # Bid-ask spread = Ask price - Bid price. bid_ask_spread = round(quote_bar.ask.close - quote_bar.bid.close, 6) # Update the spread minimum indicator to calculate the lowest bid-ask spread over the last 12 hours. forex = self.securities[symbol] forex.spread_low.update(quote_bar.end_time, bid_ask_spread) - - # Trade if the current spread is the lowest bid-ask spread, - # since it is the most efficient, liquid price with lowest slippage. + # Enter long when the spread equals the 12-hour minimum, indicating peak liquidity. if not forex.invested and bid_ask_spread == forex.spread_low.current.value: self.market_order(forex, 1000) diff --git a/project-templates/python/future-options/main.py b/project-templates/python/future-options/main.py index da5fcf3e60..1b1258b362 100644 --- a/project-templates/python/future-options/main.py +++ b/project-templates/python/future-options/main.py @@ -2,6 +2,7 @@ from AlgorithmImports import * # endregion + class FutureOptionAlgorithm(QCAlgorithm): def initialize(self) -> None: @@ -19,25 +20,22 @@ def initialize(self) -> None: # Use CallSpread filter to obtain the 2 best-matched contracts that forms a call spread. # It simplifies from further filtering and reduce computation on redundant subscription. self.add_future_option(self._underlying, lambda u: u.call_spread(5, 5, -5)) - - def on_data(self, slice: Slice) -> None: + + def on_data(self, data: Slice) -> None: if self.portfolio.invested: return # Create canonical symbol for the mapped future contract, since we need that to access the option chain. symbol = Symbol.create_canonical_option(self._underlying.mapped) - # Get option chain data for the mapped future only. # It requires 2 contracts with different strikes to form a call spread, so we make sure at least 2 contracts are present. - chain = slice.option_chains.get(symbol) + chain = data.option_chains.get(symbol) if not chain or len(list(chain)) < 2: return - # Separate the contracts by strike, as we need to access their strike. expiry = min([x.expiry for x in chain]) sorted_by_strike = sorted([x.strike for x in chain]) itm_strike = sorted_by_strike[0] otm_strike = sorted_by_strike[-1] - # Use abstraction method to order a bull call spread to avoid manual error. option_strategy = OptionStrategies.bull_call_spread(symbol, itm_strike, otm_strike, expiry) self.buy(option_strategy, 1) diff --git a/project-templates/python/futures/main.py b/project-templates/python/futures/main.py index 571d366856..f78cb0fbcf 100644 --- a/project-templates/python/futures/main.py +++ b/project-templates/python/futures/main.py @@ -2,6 +2,7 @@ from AlgorithmImports import * # endregion + class BasicFutureAlgorithm(QCAlgorithm): def initialize(self) -> None: @@ -28,8 +29,7 @@ def on_symbol_changed_events(self, symbol_changed_events: SymbolChangedEvents) - old_symbol = self.symbol(changed_event.old_symbol) new_symbol = self.symbol(changed_event.new_symbol) quantity = self.portfolio[old_symbol].quantity - - # Rolling over: to liquidate any position of the old mapped contract and switch to the newly mapped contract + # Rolling over: to liquidate any position of the old mapped contract and switch to the newly mapped contract. tag = f"Rollover - Symbol changed at {self.time}: {old_symbol.value} -> {new_symbol.value}" self.liquidate(old_symbol, tag=tag) if quantity: self.market_order(new_symbol, quantity, tag=tag) diff --git a/project-templates/python/history-and-etf-universe/main.py b/project-templates/python/history-and-etf-universe/main.py index 7f19fb3245..bc777c7c77 100644 --- a/project-templates/python/history-and-etf-universe/main.py +++ b/project-templates/python/history-and-etf-universe/main.py @@ -2,35 +2,36 @@ from AlgorithmImports import * # endregion + class ChainedUniverseAlgorithm(QCAlgorithm): _weight_by_symbol: dict[Symbol, float] = {} def initialize(self) -> None: self.set_start_date(2024, 9, 1) self.set_end_date(2024, 12, 31) - self.set_cash(100000) + self.set_cash(100_000) self.settings.seed_initial_prices = True # Select QQQ constituents first, then by fundamental data. self._universe = self.add_universe(self.universe.etf("QQQ", self._etf_constituents_filter)) - self.schedule.on(self.date_rules.every_day("QQQ"), self.time_rules.before_market_open("QQQ", 30), self._place_orders) + self.schedule.on( + self.date_rules.every_day("QQQ"), + self.time_rules.before_market_open("QQQ", 30), + self._place_orders + ) def _etf_constituents_filter(self, constituents: List[ETFConstituentUniverse]) -> List[Symbol]: - def get_atr(symbol: Symbol) -> float: atr = AverageTrueRange(14) self.warm_up_indicator(symbol, atr, Resolution.DAILY) return atr.current.value - - # Select all QQQ constituents by high ATR value + # Select all QQQ constituents by high ATR value. atr_by_symbol = {c.symbol: get_atr(c.symbol) for c in constituents if c.weight} atr_by_symbol = dict(sorted(atr_by_symbol.items(), key=lambda x: x[1], reverse=True)[:10]) - self._weight_by_symbol = {c.symbol: c.weight for c in constituents if c.symbol in atr_by_symbol} - return list(atr_by_symbol.keys()) def _place_orders(self) -> None: - # We will keep the ETF weights by scale it up to sum 1 + # Normalise ETF weights to sum to 1 before setting target holdings. sum_of_weight = sum([self._weight_by_symbol[x] for x in self._universe.selected]) self.plot("Universe", "Sum Of Weight (%)", sum_of_weight * 100) targets = [PortfolioTarget(x, self._weight_by_symbol[x] / sum_of_weight) for x in self._universe.selected] diff --git a/project-templates/python/index-options-static/main.py b/project-templates/python/index-options-static/main.py index 21b75849ce..9e25864fbf 100644 --- a/project-templates/python/index-options-static/main.py +++ b/project-templates/python/index-options-static/main.py @@ -2,39 +2,33 @@ from AlgorithmImports import * # endregion + class OptionChainFullExample(QCAlgorithm): _last_ticket: OrderTicket = None def initialize(self) -> None: self.set_start_date(2024, 9, 1) self.set_end_date(2024, 9, 5) - self.set_cash(500000) + self.set_cash(500_000) self.settings.automatic_indicator_warm_up = True self.universe_settings.minimum_time_in_universe = timedelta(0) - - # Warm-up the option contracts as soon as it is added to the algorithm + # Warm-up the option contracts as soon as it is added to the algorithm. self.settings.seed_initial_prices = True - - # The EMA/price cross will determine we trade ATM contracts + # The EMA/price cross will determine we trade ATM contracts. index = self.add_index("SPX") self.ema(index, 60).updated += self._trade_at_the_money_contract - self._option_chain_symbol = Symbol.create_canonical_option(index, "SPXW", Market.USA, "?SPXW") def _trade_at_the_money_contract(self, ema: ExponentialMovingAverage, current: IndicatorDataPoint) -> None: - # Pace trades every 10 minutes + # Pace trades every 10 minutes. last_trade_time = self._last_ticket.time if self._last_ticket else None if last_trade_time and (self.utc_time-last_trade_time).total_seconds() < 600: return - if not ema.is_ready: return - spot = self.securities[current.symbol].price - if spot > current.value and spot > ema[-1].value: atm_call = self._get_at_the_money_contract(OptionRight.CALL, spot) if atm_call and not atm_call.invested: self._last_ticket = self.market_order(atm_call, 1) - if spot < current.value and spot < ema[-1].value: atm_put = self._get_at_the_money_contract(OptionRight.PUT, spot) if atm_put and not atm_put.invested: @@ -45,8 +39,6 @@ def _get_at_the_money_contract(self, right: OptionRight, spot: float) -> Option expiry = min([x.expiry for x in chain]) contracts = sorted([x for x in chain if x.expiry == expiry and x.right == right], key=lambda x: abs(spot - x.strike)) - if not contracts: return None - return self.add_option_contract(contracts[0]) diff --git a/project-templates/python/index-options-universe/main.py b/project-templates/python/index-options-universe/main.py index 73e3dd3cc6..5381767eb4 100644 --- a/project-templates/python/index-options-universe/main.py +++ b/project-templates/python/index-options-universe/main.py @@ -2,30 +2,30 @@ from AlgorithmImports import * # endregion + class IndexOptionAlgorithm(QCAlgorithm): def initialize(self) -> None: self.set_start_date(2024, 9, 1) self.set_end_date(2024, 12, 31) - self.set_cash(100000) + self.set_cash(100_000) + self.settings.seed_initial_prices = True # Subscribe to the Option chain. self._option = self.add_index_option("SPX", "SPXW") # Filter the Option universe to only select 0DTE Options. self._option.set_filter(lambda u: u.expiration(0, 0).strikes(-1, 1)) # Filter the Option universe by Delta. The last set_filter call prevails. - # self._option.set_filter(lambda u: u.delta(0.25, 0.75)) + # self._option.set_filter(lambda u: u.delta(0.25, 0.75)). - def on_data(self, slice: Slice) -> None: + def on_data(self, data: Slice) -> None: if self.portfolio.invested: return # Get the Option chain data. - chain = slice.option_chains.get(self._option) + chain = data.option_chains.get(self._option) if not chain: return - - # Sorted the call Option contracts according to their strike prices. + # Sort call contracts by strike price. calls = sorted([x for x in chain if x.right == OptionRight.CALL], key=lambda x: x.strike) if not calls: return - # Buy 1 0DTE call Option contract for the SPX index. self.buy(calls[0], 1) diff --git a/project-templates/python/intraday-greeks-based-options-selection/main.py b/project-templates/python/intraday-greeks-based-options-selection/main.py index 6d40632e32..76c3c04010 100644 --- a/project-templates/python/intraday-greeks-based-options-selection/main.py +++ b/project-templates/python/intraday-greeks-based-options-selection/main.py @@ -2,6 +2,7 @@ from AlgorithmImports import * # endregion + class OptionChainFullExample(QCAlgorithm): _interest_rate_model = InterestRateProvider() _last_ticket: OrderTicket = None @@ -9,34 +10,27 @@ class OptionChainFullExample(QCAlgorithm): def initialize(self) -> None: self.set_start_date(2024, 9, 1) self.set_end_date(2024, 9, 5) - self.set_cash(500000) + self.set_cash(500_000) self.settings.automatic_indicator_warm_up = True self.universe_settings.minimum_time_in_universe = timedelta(0) - - # Warm-up the option contracts as soon as it is added to the algorithm + # Warm-up the option contracts as soon as it is added to the algorithm. self.settings.seed_initial_prices = True - - # The EMA/price cross will determine we trade ATM contracts + # The EMA/price cross will determine we trade ATM contracts. self._index = self.add_index("RUT") self.ema(self._index, 60).updated += self._trade_target_delta_contract - self._option_chain_symbol = Symbol.create_canonical_option(self._index, "RUTW", Market.USA, "?RUTW") self._dividend_yield_model = DividendYieldProvider(self._index) def _trade_target_delta_contract(self, ema: ExponentialMovingAverage, current: IndicatorDataPoint) -> None: - # Pace trades every 10 minutes + # Pace trades every 10 minutes. last_trade_time = self._last_ticket.time if self._last_ticket else None if last_trade_time and (self.utc_time-last_trade_time).total_seconds() < 600: return - if not ema.is_ready: return - spot = self._index.price - if spot > current.value and spot > ema[-1].value: atm_call = self._get_target_delta_contract(OptionRight.CALL, spot) if atm_call and not self.portfolio[atm_call].invested: self._last_ticket = self.market_order(atm_call, 1) - if spot < current.value and spot < ema[-1].value: atm_put = self._get_target_delta_contract(OptionRight.PUT, spot) if atm_put and not self.portfolio[atm_put].invested: @@ -48,19 +42,15 @@ def _get_target_delta_contract(self, right: OptionRight, spot: float, target_del if not chain: return None expiry = min([x.expiry for x in chain]) - def get_delta(x: OptionContract) -> tuple[OptionContract, float]: mirror_option = Symbol.create_option(x.symbol.underlying, "RUT", Market.USA, OptionStyle.EUROPEAN, mirror_option_right, x.strike, x.expiry) delta = Delta(x, self._interest_rate_model, self._dividend_yield_model, mirror_option) self.warm_up_indicator([x.symbol, mirror_option, x.symbol.underlying], delta, Resolution.MINUTE) return x, abs(delta.current.value) - contracts = sorted([x for x in chain if x.expiry == expiry and x.right == right], key=lambda x: abs(spot - x.strike))[:10] delta_by_contract = sorted([get_delta(x) for x in contracts], key=lambda x: abs(target_delta - x[1])) - if not delta_by_contract: return None - return self.add_option_contract(delta_by_contract[0][0]) diff --git a/project-templates/python/intraday-index-options-selection/main.py b/project-templates/python/intraday-index-options-selection/main.py index af5f0a3da4..2080608c5e 100644 --- a/project-templates/python/intraday-index-options-selection/main.py +++ b/project-templates/python/intraday-index-options-selection/main.py @@ -2,6 +2,7 @@ from AlgorithmImports import * # endregion + class IndexOptionAlgorithm(QCAlgorithm): _chain: list[OptionContract] = [] _min_strike = -1 @@ -10,24 +11,28 @@ class IndexOptionAlgorithm(QCAlgorithm): def initialize(self) -> None: self.set_start_date(2024, 9, 1) self.set_end_date(2024, 12, 31) - self.set_cash(500000) + self.set_cash(500_000) self.universe_settings.minimum_time_in_universe = timedelta(0) - - # Warm-up the option contracts as soon as it is added to the algorithm + # Warm-up the option contracts as soon as it is added to the algorithm. self.settings.seed_initial_prices = True - # Create the option chain symbol for the SPXW weekly index option. index = self.add_index("SPX") self._option_chain_symbol = Symbol.create_canonical_option(index, "SPXW", Market.USA, "?SPXW") - # Populate the updated option chain immediately to trade with. self._populate_option_chain() - date_rule = self.date_rules.every_day(self._option_chain_symbol) # Set a schedule event to populate the option chain when the market opens since the option contracts are updated daily. - self.schedule.on(date_rule, self.time_rules.after_market_open(self._option_chain_symbol, 1), self._populate_option_chain) + self.schedule.on( + date_rule, + self.time_rules.after_market_open(self._option_chain_symbol, 1), + self._populate_option_chain + ) # Set a scheduled event to filter the closed ATM calls every 5 minutes. - self.schedule.on(date_rule, self.time_rules.every(timedelta(minutes=5)), self._filter) + self.schedule.on( + date_rule, + self.time_rules.every(timedelta(minutes=5)), + self._filter + ) def _populate_option_chain(self) -> None: # Filter the expiry daily only since the contract list is updated daily. @@ -39,7 +44,6 @@ def _filter(self) -> None: if not self._chain: return underlying = self.securities[self._option_chain_symbol.underlying] - # Filter the contracts with strike range spread between the preset level. strikes = sorted(set([x.strike for x in self._chain])) spot = underlying.price @@ -48,30 +52,23 @@ def _filter(self) -> None: min_strike = strikes[max(0, index + self._min_strike)] max_strike = strikes[min(len(strikes) - 1, index + self._max_strike)] contracts = [x for x in self._chain if min_strike <= x.strike and x.strike <= max_strike] - # Request data of the newly identified ATM contracts. for contract in contracts: if contract not in self.securities: self.add_option_contract(contract) - # Since we are trading 0DTE, they will expire on end of day - # so we don't need to remove them explicitly - - def on_data(self, slice: Slice) -> None: + # 0DTE contracts expire at end of day and are removed automatically. + + def on_data(self, data: Slice) -> None: # Only trade on updated data. - chain = slice.option_chains.get(self._option_chain_symbol) + chain = data.option_chains.get(self._option_chain_symbol) if not chain: return - # Filter the closest ATM call contract and trade. expiry = min([x.expiry for x in chain]) calls = [x for x in chain if x.expiry == expiry and x.right == OptionRight.CALL and self.securities[x].is_tradable] if not calls: return - atm_call = sorted(calls, key=lambda x: abs(chain.underlying.price - x.strike))[0] - - # We will buy the ATM call if we don't have it. - # We are not selling the calls we have purchased previously - # So, we will buy a lot of contracts if underlying price moves a lot + # Buy the nearest ATM call each time the underlying moves to a new strike. if not self.portfolio[atm_call].invested: self.market_order(atm_call, 1) diff --git a/project-templates/python/intraday-indicator-scans/main.py b/project-templates/python/intraday-indicator-scans/main.py index 33e0fd80c0..d3a6a759fa 100644 --- a/project-templates/python/intraday-indicator-scans/main.py +++ b/project-templates/python/intraday-indicator-scans/main.py @@ -2,18 +2,23 @@ from AlgorithmImports import * # endregion + class ETFUniverseAlgorithm(QCAlgorithm): _weight_by_symbol: dict[Symbol, float] = {} def initialize(self) -> None: self.set_start_date(2024, 9, 1) self.set_end_date(2024, 12, 31) - self.set_cash(100000) + self.set_cash(100_000) self.settings.seed_initial_prices = True self.settings.automatic_indicator_warm_up = True # Select QQQ constituents first, then by fundamental data. self._universe = self.add_universe(self.universe.etf("QQQ", self._etf_constituents_filter)) - self.schedule.on(self.date_rules.every_day("QQQ"), self.time_rules.every(timedelta(minutes=15)), self._place_orders) + self.schedule.on( + self.date_rules.every_day("QQQ"), + self.time_rules.every(timedelta(minutes=15)), + self._place_orders + ) def on_securities_changed(self, changes: SecurityChanges) -> None: for security in changes.added_securities: @@ -32,11 +37,11 @@ def _place_orders(self) -> None: # Select the stocks with the greatest ATR. securities = [self.securities[symbol] for symbol in self._universe.selected] selected = sorted([s for s in securities if s.atr.is_ready], key=lambda security: security.atr)[-10:] - # We will keep the ETF weights by scale it up to sum 1 + # Normalise ETF weights to sum to 1 before setting target holdings. sum_of_weight = sum([self._weight_by_symbol[security.symbol] for security in selected]) self.plot("Universe", "Sum Of Weight (%)", sum_of_weight * 100) if not sum_of_weight: return targets = [PortfolioTarget(s, self._weight_by_symbol[s.symbol] / sum_of_weight) for s in selected] - # Remove securities that are not top ATR - self.set_holdings(targets, True) \ No newline at end of file + # Rebalance and liquidate securities outside the top ATR selection. + self.set_holdings(targets, True) diff --git a/project-templates/python/live-trading-features/main.py b/project-templates/python/live-trading-features/main.py index 86dae51163..47f6195137 100644 --- a/project-templates/python/live-trading-features/main.py +++ b/project-templates/python/live-trading-features/main.py @@ -2,27 +2,26 @@ from AlgorithmImports import * # endregion + class LiveTradingFeaturesAlgorithm(QCAlgorithm): _connected = False - + def initialize(self) -> None: self.set_start_date(2024, 9, 12) self.set_end_date(2024, 10, 1) - self.set_cash(1000000) - + self.set_cash(1_000_000) + self.settings.seed_initial_prices = True self.settings.automatic_indicator_warm_up = True - # Request SPY data to trade. self._spy = self.add_equity("SPY") # Create an EMA indicator to generate trade signals. self._spy.ema = self.ema(self._spy, 20, Resolution.DAILY) - + def _notify_all(self, subject: str, message: str) -> None: self.notify.email("email@address.com", subject, message) message = f"{self.time:yyyyMMdd}: {subject} > {message}" self.log(message) # See https://www.quantconnect.com/docs/v2/writing-algorithms/live-trading/notifications - # for all notification methods self.notify.sms("+16191234567", message) def on_brokerage_disconnect(self) -> None: @@ -40,8 +39,8 @@ def on_brokerage_message(self, message_event: BrokerageMessageEvent) -> None: case _: self.log(str(message_event)) - def on_data(self, slice: Slice) -> None: - self._notify_ema_cross(slice) + def on_data(self, data: Slice) -> None: + self._notify_ema_cross(data) def _notify_ema_cross(self, slice: Slice) -> None: if not self.live_mode: @@ -49,9 +48,7 @@ def _notify_ema_cross(self, slice: Slice) -> None: bar = slice.bars.get(self._spy) if not bar: return - # Trend-following strategy using price and EMA. - # If the price is above EMA, SPY is in an uptrend, and we buy it. - # We sent a link to our email address and await confirmation. + # Send a trade confirmation link when the EMA cross condition is detected. if bar.close > self._spy.ema.current.value and not self._spy.holdings.is_long: link = self.link({"ticker": "SPY", "size": 1}) self._notify_all("Trade Confirmation Needed", f"Click here to run: {link}") diff --git a/project-templates/python/trade-management/main.py b/project-templates/python/trade-management/main.py index 43b89c2d77..9b6ca98cff 100644 --- a/project-templates/python/trade-management/main.py +++ b/project-templates/python/trade-management/main.py @@ -2,6 +2,7 @@ from AlgorithmImports import * # endregion + class OneCancelOtherExampleAlgorithm(QCAlgorithm): def initialize(self) -> None: @@ -18,22 +19,21 @@ def on_data(self, data: Slice) -> None: bar = data.bars.get(self._spy) if not bar: return - # If the price is above the EMA, we will buy 75% of the portfolio value - # and place the OCO orders to sell it. - # Otherwise, we will short 75% of the portfolio value - # and place OCO orders to rebuy. + # If the price is above the EMA, we will buy 75% of the portfolio value. + # And place the OCO orders to sell it. + # Otherwise, we will short 75% of the portfolio value. + # And place OCO orders to rebuy. ema = self._spy.ema.current.value price = bar.close weight = 0.75 if ema > price else -.75 stop_price = bar.close * (.95 if ema > price else 1.05) limit_price = bar.close * (1.05 if ema > price else .95) - quantity = self.calculate_order_quantity(self._spy, weight) self.market_order(self._spy, quantity) self._spy.stop_loss = self.stop_market_order(self._spy, -quantity, stop_price) self._spy.take_profit = self.limit_order(self._spy, -quantity, limit_price) self._spy.has_oco = True - + def on_order_event(self, order_event: OrderEvent) -> None: if order_event.status == OrderStatus.FILLED: equity = self.securities[order_event.symbol]