Skip to content
26 changes: 11 additions & 15 deletions project-templates/python/ai/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,81 +3,77 @@
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
num_neurons_2 = 10
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
tf.keras.layers.Dense(num_neurons_2, activation=tf.nn.relu),
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
# Warm up the training dataset to train the model immediately.
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

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)
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Original file line number Diff line number Diff line change
Expand Up @@ -2,28 +2,31 @@
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 = [
x.key.symbol
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.
Expand All @@ -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)
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Loading
Loading