diff --git a/Algorithm.Python/AutoRegressiveIntegratedMovingAverageRegressionAlgorithm.py b/Algorithm.Python/AutoRegressiveIntegratedMovingAverageRegressionAlgorithm.py index a7f9ba64942c..a155fdecfde2 100644 --- a/Algorithm.Python/AutoRegressiveIntegratedMovingAverageRegressionAlgorithm.py +++ b/Algorithm.Python/AutoRegressiveIntegratedMovingAverageRegressionAlgorithm.py @@ -19,7 +19,7 @@ # is sufficiently large (which would be due to the inclusion of the MA(1) term). # class AutoRegressiveIntegratedMovingAverageRegressionAlgorithm(QCAlgorithm): - def initialize(self): + def initialize(self) -> None: '''Initialise the data and resolution required, as well as the cash and start-end dates for your algorithm. All algorithms must initialized.''' self.set_start_date(2013, 1, 7) self.set_end_date(2013, 12, 11) @@ -27,8 +27,9 @@ def initialize(self): self.add_equity("SPY", Resolution.DAILY) self._arima = self.arima("SPY", 1, 1, 1, 50) self._ar = self.arima("SPY", 1, 1, 0, 50) + self._last = None - def on_data(self, data): + def on_data(self, data: Slice) -> None: '''OnData event is the primary entry point for your algorithm. Each new data point will be pumped in here. Arguments: @@ -36,8 +37,8 @@ def on_data(self, data): ''' if self._arima.is_ready: if abs(self._arima.current.value - self._ar.current.value) > 1: - if self._arima.current.value > self.last: + if self._arima.current.value > self._last: self.market_order("SPY", 1) else: self.market_order("SPY", -1) - self.last = self._arima.current.value + self._last = self._arima.current.value diff --git a/Algorithm.Python/BasicTemplateOptionEquityStrategyAlgorithm.py b/Algorithm.Python/BasicTemplateOptionEquityStrategyAlgorithm.py index 2f3100fbb323..ea0323452d43 100644 --- a/Algorithm.Python/BasicTemplateOptionEquityStrategyAlgorithm.py +++ b/Algorithm.Python/BasicTemplateOptionEquityStrategyAlgorithm.py @@ -24,13 +24,13 @@ class BasicTemplateOptionEquityStrategyAlgorithm(QCAlgorithm): underlying_ticker = "GOOG" - def initialize(self): + def initialize(self) -> None: self.set_start_date(2015, 12, 24) self.set_end_date(2015, 12, 24) equity = self.add_equity(self.underlying_ticker) option = self.add_option(self.underlying_ticker) - self.option_symbol = option.symbol + self._option_symbol = option.symbol # set our strike/expiry filter for this option chain option.set_filter(lambda u: (u.strikes(-2, +2) @@ -38,10 +38,11 @@ def initialize(self): # The following statements yield the same filtering criteria .expiration(0, 180))) - def on_data(self, slice): - if self.portfolio.invested or not self.is_market_open(self.option_symbol): return + def on_data(self, slice: Slice) -> None: + if self.portfolio.invested or not self.is_market_open(self._option_symbol): + return - chain = slice.option_chains.get_value(self.option_symbol) + chain = slice.option_chains.get_value(self._option_symbol) if chain is None: return @@ -57,9 +58,9 @@ def on_data(self, slice): middle_strike = call_contracts[1].strike higher_strike = call_contracts[2].strike - option_strategy = OptionStrategies.call_butterfly(self.option_symbol, higher_strike, middle_strike, lower_strike, expiry) + option_strategy = OptionStrategies.call_butterfly(self._option_symbol, higher_strike, middle_strike, lower_strike, expiry) self.order(option_strategy, 10) - def on_order_event(self, order_event): + def on_order_event(self, order_event: OrderEvent) -> None: self.log(str(order_event)) diff --git a/Algorithm.Python/Benchmarks/IndicatorRibbonBenchmark.py b/Algorithm.Python/Benchmarks/IndicatorRibbonBenchmark.py index 2dabda2908e7..46667da89cff 100644 --- a/Algorithm.Python/Benchmarks/IndicatorRibbonBenchmark.py +++ b/Algorithm.Python/Benchmarks/IndicatorRibbonBenchmark.py @@ -19,25 +19,25 @@ class IndicatorRibbonBenchmark(QCAlgorithm): def initialize(self): self.set_start_date(2010, 1, 1) #Set Start Date self.set_end_date(2018, 1, 1) #Set End Date - self.spy = self.add_equity("SPY", Resolution.MINUTE).symbol + self._spy = self.add_equity("SPY", Resolution.MINUTE).symbol count = 50 offset = 5 period = 15 - self.ribbon = [] + self._ribbon = [] # define our sma as the base of the ribbon - self.sma = SimpleMovingAverage(period) + self._sma = SimpleMovingAverage(period) for x in range(count): # define our offset to the zero sma, these various offsets will create our 'displaced' ribbon delay = Delay(offset*(x+1)) # define an indicator that takes the output of the sma and pipes it into our delay indicator - delayed_sma = IndicatorExtensions.of(delay, self.sma) + delayed_sma = IndicatorExtensions.of(delay, self._sma) # register our new 'delayed_sma' for automatic updates on a daily resolution - self.register_indicator(self.spy, delayed_sma, Resolution.DAILY) - self.ribbon.append(delayed_sma) + self.register_indicator(self._spy, delayed_sma, Resolution.DAILY) + self._ribbon.append(delayed_sma) def on_data(self, data): # wait for our entire ribbon to be ready - if not all(x.is_ready for x in self.ribbon): return - for x in self.ribbon: + if not all(x.is_ready for x in self._ribbon): return + for x in self._ribbon: value = x.current.value diff --git a/Algorithm.Python/BybitCustomDataCryptoRegressionAlgorithm.py b/Algorithm.Python/BybitCustomDataCryptoRegressionAlgorithm.py index 34b8394d1bd2..2f8417c7722b 100644 --- a/Algorithm.Python/BybitCustomDataCryptoRegressionAlgorithm.py +++ b/Algorithm.Python/BybitCustomDataCryptoRegressionAlgorithm.py @@ -11,15 +11,16 @@ # See the License for the specific language governing permissions and # limitations under the License. +from datetime import time +import os from AlgorithmImports import * -import datetime ### ### Algorithm demonstrating and ensuring that Bybit crypto brokerage model works as expected with custom data types ### class BybitCustomDataCryptoRegressionAlgorithm(QCAlgorithm): - def initialize(self): + def initialize(self) -> None: self.set_start_date(2022, 12, 13) self.set_end_date(2022, 12, 13) @@ -29,42 +30,42 @@ def initialize(self): self.set_brokerage_model(BrokerageName.BYBIT, AccountType.CASH) symbol = self.add_crypto("BTCUSDT").symbol - self.btc_usdt = self.add_data(CustomCryptoData, symbol, Resolution.MINUTE).symbol + self._btc_usdt = self.add_data(CustomCryptoData, symbol, Resolution.MINUTE).symbol # create two moving averages - self.fast = self.ema(self.btc_usdt, 30, Resolution.MINUTE) - self.slow = self.ema(self.btc_usdt, 60, Resolution.MINUTE) + self._fast = self.ema(self._btc_usdt, 30, Resolution.MINUTE) + self._slow = self.ema(self._btc_usdt, 60, Resolution.MINUTE) - def on_data(self, data): - if not self.slow.is_ready: + def on_data(self, data: Slice) -> None: + if not self._slow.is_ready: return - if self.fast.current.value > self.slow.current.value: + if self._fast.current.value > self._slow.current.value: if self.transactions.orders_count == 0: - self.buy(self.btc_usdt, 1) + self.buy(self._btc_usdt, 1) else: if self.transactions.orders_count == 1: - self.liquidate(self.btc_usdt) + self.liquidate(self._btc_usdt) - def on_order_event(self, order_event): + def on_order_event(self, order_event: OrderEvent) -> None: self.debug(f"{self.time} {order_event}") class CustomCryptoData(PythonData): - def get_source(self, config, date, is_live_mode): + def get_source(self, config: SubscriptionDataConfig, date: datetime, is_live_mode: bool) -> SubscriptionDataSource: tick_type_string = Extensions.tick_type_to_lower(config.tick_type) formatted_date = date.strftime("%Y%m%d") - source = os.path.join(Globals.DataFolder, "crypto", "bybit", "minute", + source = os.path.join(Globals.data_folder, "crypto", "bybit", "minute", config.symbol.value.lower(), f"{formatted_date}_{tick_type_string}.zip") return SubscriptionDataSource(source, SubscriptionTransportMedium.LOCAL_FILE, FileFormat.CSV) - def reader(self, config, line, date, is_live_mode): + def reader(self, config: SubscriptionDataConfig, line: str, date: datetime, is_live_mode: bool) -> BaseData: csv = line.split(',') data = CustomCryptoData() data.symbol = config.symbol - data_datetime = datetime.datetime.combine(date.date(), datetime.time()) + timedelta(milliseconds=int(csv[0])) + data_datetime = datetime.combine(date.date(), time()) + timedelta(milliseconds=int(csv[0])) data.time = Extensions.convert_to(data_datetime, config.data_time_zone, config.exchange_time_zone) data.end_time = data.time + timedelta(minutes=1) diff --git a/Algorithm.Python/CallbackCommandRegressionAlgorithm.py b/Algorithm.Python/CallbackCommandRegressionAlgorithm.py index 23afd5435a68..848bcf2f8325 100644 --- a/Algorithm.Python/CallbackCommandRegressionAlgorithm.py +++ b/Algorithm.Python/CallbackCommandRegressionAlgorithm.py @@ -22,11 +22,12 @@ class VoidCommand(): parameters = {} targettime = None - def run(self, algo: QCAlgorithm) -> bool | None: + def run(self, algo: IAlgorithm) -> bool: if not self.targettime or self.targettime != algo.time: - return + return False tag = self.parameters["tag"] algo.order(self.target[0], self.get_quantity(), tag=tag) + return True def get_quantity(self): return self.quantity @@ -36,7 +37,7 @@ class BoolCommand(Command): array_test = [] result = False - def run(self, algo: QCAlgorithm) -> bool | None: + def run(self, algo: QCAlgorithm) -> bool: trade_ibm = self.my_custom_method() if trade_ibm: algo.debug(f"BoolCommand.run: {str(self)}") @@ -96,13 +97,13 @@ def initialize(self): # We need to create a project on QuantConnect to test the broadcast_command method # and use the project_id in the broadcast_command call - self.project_id = 21805137; + self.project_id = 21805137 # All live deployments receive the broadcasts below - broadcast_result = self.broadcast_command(potential_command); - broadcast_result2 = self.broadcast_command({ "symbol": "SPY", "parameters": { "quantity": 10 } }); + broadcast_result = self.broadcast_command(potential_command) + broadcast_result2 = self.broadcast_command({ "symbol": "SPY", "parameters": { "quantity": 10 } }) - def on_command(self, data): + def on_command(self, data: object) -> bool: self.debug(f"on_command: {str(data)}") self.buy(data.symbol, data.parameters["quantity"]) return True # False, None diff --git a/Algorithm.Python/ClassicRangeConsolidatorAlgorithm.py b/Algorithm.Python/ClassicRangeConsolidatorAlgorithm.py index d8c49ad7824c..d942128d78d0 100644 --- a/Algorithm.Python/ClassicRangeConsolidatorAlgorithm.py +++ b/Algorithm.Python/ClassicRangeConsolidatorAlgorithm.py @@ -19,10 +19,10 @@ ### Example algorithm of how to use ClassicRangeConsolidator ### class ClassicRangeConsolidatorAlgorithm(RangeConsolidatorAlgorithm): - def create_range_consolidator(self): + def create_range_consolidator(self) -> ClassicRangeConsolidator: return ClassicRangeConsolidator(self.get_range()) - def on_data_consolidated(self, sender, range_bar): + def on_data_consolidated(self, sender: object, range_bar: RangeBar): super().on_data_consolidated(sender, range_bar) if range_bar.volume == 0: diff --git a/Algorithm.Python/ClassicRenkoConsolidatorAlgorithm.py b/Algorithm.Python/ClassicRenkoConsolidatorAlgorithm.py index d67a40a171ee..12cbc70368d5 100644 --- a/Algorithm.Python/ClassicRenkoConsolidatorAlgorithm.py +++ b/Algorithm.Python/ClassicRenkoConsolidatorAlgorithm.py @@ -23,8 +23,7 @@ class ClassicRenkoConsolidatorAlgorithm(QCAlgorithm): '''Demonstration of how to initialize and use the RenkoConsolidator''' - def initialize(self): - + def initialize(self) -> None: self.set_start_date(2012, 1, 1) self.set_end_date(2013, 1, 1) @@ -48,11 +47,11 @@ def initialize(self): # We're doing our analysis in the on_renko_bar method, but the framework verifies that this method exists, so we define it. - def on_data(self, data): + def on_data(self, data: Slice) -> None: pass - def handle_renko_close(self, sender, data): + def handle_renko_close(self, sender: object, data: RenkoBar) -> None: '''This function is called by our renko_close consolidator defined in Initialize() Args: data: The new renko bar produced by the consolidator''' @@ -62,7 +61,7 @@ def handle_renko_close(self, sender, data): self.log(f"CLOSE - {data.time} - {data.open} {data.close}") - def handle_renko7_bar(self, sender, data): + def handle_renko7_bar(self, sender: object, data: RenkoBar) -> None: '''This function is called by our renko7bar consolidator defined in Initialize() Args: data: The new renko bar produced by the consolidator''' diff --git a/Algorithm.Python/CoarseFineOptionUniverseChainRegressionAlgorithm.py b/Algorithm.Python/CoarseFineOptionUniverseChainRegressionAlgorithm.py index 87bad577348a..2555f489f187 100644 --- a/Algorithm.Python/CoarseFineOptionUniverseChainRegressionAlgorithm.py +++ b/Algorithm.Python/CoarseFineOptionUniverseChainRegressionAlgorithm.py @@ -19,7 +19,7 @@ ### class CoarseFineOptionUniverseChainRegressionAlgorithm(QCAlgorithm): - def initialize(self): + def initialize(self) -> None: '''Initialise the data and resolution required, as well as the cash and start-end dates for your algorithm. All algorithms must initialized.''' self.set_start_date(2014,6,4) @@ -41,27 +41,27 @@ def initialize(self): self.add_universe_options(universe, self.option_filter_function) - def option_filter_function(self, universe): + def option_filter_function(self, universe: OptionFilterUniverse) -> OptionFilterUniverse: universe.include_weeklys().front_month() - contracts: list[OptionUniverse] = list() + contracts = list() for contract in universe: if len(contracts) == 5: break contracts.append(contract) return universe.contracts(contracts) - def coarse_selection_function(self, coarse): + def coarse_selection_function(self, coarse: list[CoarseFundamental]) -> list[Symbol]: if self.time <= datetime(2014,6,5): return [ self._twx ] return [ self._aapl ] - def fine_selection_function(self, fine): + def fine_selection_function(self, fine: list[FineFundamental]) -> list[Symbol]: if self.time <= datetime(2014,6,5): return [ self._twx ] return [ self._aapl ] - def on_data(self, data): + def on_data(self, data: Slice) -> None: if self._changes == None or any(security.price == 0 for security in self._changes.added_securities): return @@ -83,12 +83,12 @@ def on_data(self, data): self._changes = None # this event fires whenever we have changes to our universe - def on_securities_changed(self, changes): + def on_securities_changed(self, changes: SecurityChanges) -> None: if self._changes == None: self._changes = changes return self._changes = self._changes + changes - def on_end_of_algorithm(self): + def on_end_of_algorithm(self) -> None: if self._option_count == 0: raise ValueError("Option universe chain did not add any option!") diff --git a/Algorithm.Python/ComboOrderTicketDemoAlgorithm.py b/Algorithm.Python/ComboOrderTicketDemoAlgorithm.py index 4b5cdea04a8b..de569c60523b 100644 --- a/Algorithm.Python/ComboOrderTicketDemoAlgorithm.py +++ b/Algorithm.Python/ComboOrderTicketDemoAlgorithm.py @@ -19,7 +19,7 @@ ### class ComboOrderTicketDemoAlgorithm(QCAlgorithm): - def initialize(self): + def initialize(self) -> None: self.set_start_date(2015, 12, 24) self.set_end_date(2015, 12, 24) self.set_cash(100000) @@ -36,7 +36,7 @@ def initialize(self): self._order_legs = None - def on_data(self, data: Slice): + def on_data(self, data: Slice) -> None: if self._order_legs is None: if self.is_market_open(self._option_symbol): chain = data.option_chains.get_value(self._option_symbol) @@ -57,18 +57,13 @@ def on_data(self, data: Slice): self._order_legs.append(leg) else: # COMBO MARKET ORDERS - self.combo_market_orders() - # COMBO LIMIT ORDERS - self.combo_limit_orders() - # COMBO LEG LIMIT ORDERS - self.combo_leg_limit_orders() - def combo_market_orders(self): + def combo_market_orders(self) -> None: if len(self._open_market_orders) != 0 or self._order_legs is None: return @@ -85,7 +80,7 @@ def combo_market_orders(self): if response.is_success: raise AssertionError("Combo market orders should fill instantly, they should not be cancelable in backtest mode: " + response.order_id) - def combo_limit_orders(self): + def combo_limit_orders(self) -> None: if len(self._open_limit_orders) == 0: self.log("Submitting ComboLimitOrder") @@ -122,7 +117,7 @@ def combo_limit_orders(self): fields.tag = f"Update #{len(ticket.update_requests) + 1}" ticket.update(fields) - def combo_leg_limit_orders(self): + def combo_leg_limit_orders(self) -> None: if len(self._open_leg_limit_orders) == 0: self.log("Submitting ComboLegLimitOrder") @@ -166,7 +161,7 @@ def combo_leg_limit_orders(self): fields.tag = f"Update #{len(ticket.update_requests) + 1}" ticket.update(fields) - def on_order_event(self, order_event): + def on_order_event(self, order_event: OrderEvent) -> None: order = self.transactions.get_order_by_id(order_event.order_id) if order_event.quantity == 0: @@ -179,7 +174,7 @@ def on_order_event(self, order_event): if order.type == OrderType.COMBO_LEG_LIMIT and order_event.limit_price == 0: raise AssertionError("OrderEvent.LIMIT_PRICE is not expected to be 0 for ComboLegLimitOrder") - def check_group_orders_for_fills(self, combo1, combo2): + def check_group_orders_for_fills(self, combo1: list[OrderTicket], combo2: list[OrderTicket]) -> None: if all(x.status == OrderStatus.FILLED for x in combo1): self.log(f"{combo1[0].order_type}: Canceling combo #2, combo #1 is filled.") if any(OrderExtensions.is_open(x.status) for x in combo2): @@ -196,7 +191,7 @@ def check_group_orders_for_fills(self, combo1, combo2): return False - def on_end_of_algorithm(self): + def on_end_of_algorithm(self) -> None: filled_orders = self.transactions.get_orders(lambda x: x.status == OrderStatus.FILLED).to_list() order_tickets = self.transactions.get_order_tickets().to_list() open_orders = self.transactions.get_open_orders() diff --git a/Algorithm.Python/ComboOrdersFillModelAlgorithm.py b/Algorithm.Python/ComboOrdersFillModelAlgorithm.py index 811d63f7eea9..f8feed59bb15 100644 --- a/Algorithm.Python/ComboOrdersFillModelAlgorithm.py +++ b/Algorithm.Python/ComboOrdersFillModelAlgorithm.py @@ -20,7 +20,7 @@ class ComboOrdersFillModelAlgorithm(QCAlgorithm): '''Basic template algorithm that implements a fill model with combo orders''' - def initialize(self): + def initialize(self) -> None: self.set_start_date(2019, 1, 1) self.set_end_date(2019, 1, 20) @@ -31,9 +31,9 @@ def initialize(self): self.spy.set_fill_model(CustomPartialFillModel()) self.ibm.set_fill_model(CustomPartialFillModel()) - self.order_types = {} + self._order_types = {} - def on_data(self, data): + def on_data(self, data: Slice) -> None: if not self.portfolio.invested: legs = [Leg.create(self.spy.symbol, 1), Leg.create(self.ibm.symbol, -1)] self.combo_market_order(legs, 100) @@ -42,7 +42,7 @@ def on_data(self, data): legs = [Leg.create(self.spy.symbol, 1, round(self.spy.bid_price) + 1), Leg.create(self.ibm.symbol, -1, round(self.ibm.bid_price) + 1)] self.combo_leg_limit_order(legs, 100) - def on_order_event(self, order_event): + def on_order_event(self, order_event: OrderEvent) -> None: if order_event.status == OrderStatus.FILLED: order_type = self.transactions.get_order_by_id(order_event.order_id).type if order_type == OrderType.COMBO_MARKET and order_event.absolute_fill_quantity != 50: @@ -52,19 +52,19 @@ def on_order_event(self, order_event): elif order_type == OrderType.COMBO_LEG_LIMIT and order_event.absolute_fill_quantity != 10: raise AssertionError(f"The absolute quantity filled for all combo leg limit orders should be 10, but for order {order_event.order_id} was {order_event.absolute_fill_quantity}") - self.order_types[order_type] = 1 + self._order_types[order_type] = 1 - def on_end_of_algorithm(self): - if len(self.order_types) != 3: - raise AssertionError(f"Just 3 different types of order were submitted in this algorithm, but the amount of order types was {len(self.order_types)}") + def on_end_of_algorithm(self) -> None: + if len(self._order_types) != 3: + raise AssertionError(f"Just 3 different types of order were submitted in this algorithm, but the amount of order types was {len(self._order_types)}") - if OrderType.COMBO_MARKET not in self.order_types.keys(): + if OrderType.COMBO_MARKET not in self._order_types.keys(): raise AssertionError(f"One Combo Market Order should have been submitted but it was not") - if OrderType.COMBO_LIMIT not in self.order_types.keys(): + if OrderType.COMBO_LIMIT not in self._order_types.keys(): raise AssertionError(f"One Combo Limit Order should have been submitted but it was not") - if OrderType.COMBO_LEG_LIMIT not in self.order_types.keys(): + if OrderType.COMBO_LEG_LIMIT not in self._order_types.keys(): raise AssertionError(f"One Combo Leg Limit Order should have been submitted but it was not") @@ -72,10 +72,10 @@ class CustomPartialFillModel(FillModel): '''Implements a custom fill model that inherit from FillModel. Overrides combo_market_fill, combo_limit_fill and combo_leg_limit_fill methods to test FillModelPythonWrapper works as expected''' - def __init__(self): + def __init__(self) -> None: self.absolute_remaining_by_order_id = {} - def fill_orders_partially(self, parameters, fills, quantity): + def fill_orders_partially(self, parameters: FillModelParameters, fills: list[OrderEvent], quantity: int) -> list[OrderEvent]: partial_fills = [] if len(fills) == 0: return partial_fills @@ -102,17 +102,17 @@ def fill_orders_partially(self, parameters, fills, quantity): return partial_fills - def combo_market_fill(self, order, parameters): + def combo_market_fill(self, order: Order, parameters: FillModelParameters) -> list[OrderEvent]: fills = super().combo_market_fill(order, parameters) partial_fills = self.fill_orders_partially(parameters, fills, 50) return partial_fills - def combo_limit_fill(self, order, parameters): + def combo_limit_fill(self, order: Order, parameters: FillModelParameters) -> list[OrderEvent]: fills = super().combo_limit_fill(order, parameters) partial_fills = self.fill_orders_partially(parameters, fills, 20) return partial_fills - def combo_leg_limit_fill(self, order, parameters): + def combo_leg_limit_fill(self, order: Order, parameters: FillModelParameters) -> list[OrderEvent]: fills = super().combo_leg_limit_fill(order, parameters) partial_fills = self.fill_orders_partially(parameters, fills, 10) return partial_fills diff --git a/Algorithm.Python/CompleteOrderTagUpdateAlgorithm.py b/Algorithm.Python/CompleteOrderTagUpdateAlgorithm.py index 76f3edf03370..f5f4705fcb2a 100644 --- a/Algorithm.Python/CompleteOrderTagUpdateAlgorithm.py +++ b/Algorithm.Python/CompleteOrderTagUpdateAlgorithm.py @@ -18,8 +18,8 @@ ### class CompleteOrderTagUpdateAlgorithm(QCAlgorithm): - tag_after_fill = "This is the tag set after order was filled." - tag_after_canceled = "This is the tag set after order was canceled." + _tag_after_fill = "This is the tag set after order was filled." + _tag_after_canceled = "This is the tag set after order was canceled." def initialize(self) -> None: self.set_start_date(2013,10, 7) @@ -47,27 +47,27 @@ def on_data(self, data: Slice) -> None: def on_order_event(self, order_event: OrderEvent) -> None: if order_event.status == OrderStatus.CANCELED: - if order_event.order_id != self._limit_order_ticket.order_id: + if not self._limit_order_ticket or order_event.order_id != self._limit_order_ticket.order_id: raise AssertionError("The only canceled order should have been the limit order.") # update canceled order tag - self.update_order_tag(self._limit_order_ticket, self.tag_after_canceled, "Error updating order tag after canceled") + self.update_order_tag(self._limit_order_ticket, self._tag_after_canceled, "Error updating order tag after canceled") elif order_event.status == OrderStatus.FILLED: self._market_order_ticket = list(self.transactions.get_order_tickets(lambda x: x.order_type == OrderType.MARKET))[0] - if order_event.order_id != self._market_order_ticket.order_id: + if not self._market_order_ticket or order_event.order_id != self._market_order_ticket.order_id: raise AssertionError("The only filled order should have been the market order.") # update filled order tag - self.update_order_tag(self._market_order_ticket, self.tag_after_fill, "Error updating order tag after fill") + self.update_order_tag(self._market_order_ticket, self._tag_after_fill, "Error updating order tag after fill") def on_end_of_algorithm(self) -> None: # check the filled order - self.assert_order_tag_update(self._market_order_ticket, self.tag_after_fill, "filled") + self.assert_order_tag_update(self._market_order_ticket, self._tag_after_fill, "filled") if self._market_order_ticket.quantity != self._quantity or self._market_order_ticket.quantity_filled != self._quantity: raise AssertionError("The market order quantity should not have been updated.") # check the canceled order - self.assert_order_tag_update(self._limit_order_ticket, self.tag_after_canceled, "canceled") + self.assert_order_tag_update(self._limit_order_ticket, self._tag_after_canceled, "canceled") def assert_order_tag_update(self, ticket: OrderTicket, expected_tag: str, order_action: str) -> None: if ticket is None: diff --git a/Algorithm.Python/CrunchDAOSignalExportDemonstrationAlgorithm.py b/Algorithm.Python/CrunchDAOSignalExportDemonstrationAlgorithm.py index e6d7e1622dc8..a1fd474f61d5 100644 --- a/Algorithm.Python/CrunchDAOSignalExportDemonstrationAlgorithm.py +++ b/Algorithm.Python/CrunchDAOSignalExportDemonstrationAlgorithm.py @@ -23,7 +23,7 @@ class CrunchDAOSignalExportDemonstrationAlgorithm(QCAlgorithm): crunch_universe = [] - def initialize(self): + def initialize(self) -> None: self.set_start_date(2023, 5, 22) self.set_end_date(2023, 5, 26) self.set_cash(1_000_000) @@ -44,7 +44,7 @@ def initialize(self): self.add_universe(CrunchDaoSkeleton, "CrunchDaoSkeleton", Resolution.DAILY, self.select_symbols) # Create a Scheduled Event to submit signals every monday before the market opens - self.week = -1 + self._week = -1 self.schedule.on( self.date_rules.every([DayOfWeek.MONDAY, DayOfWeek.TUESDAY, DayOfWeek.WEDNESDAY, DayOfWeek.THURSDAY, DayOfWeek.FRIDAY]), self.time_rules.at(13, 15, TimeZones.UTC), @@ -54,24 +54,24 @@ def initialize(self): self.set_warm_up(timedelta(45)) - def select_symbols(self, data: List[CrunchDaoSkeleton]) -> List[Symbol]: + def select_symbols(self, data: list[CrunchDaoSkeleton]) -> list[Symbol]: return [x.symbol for x in data] - def on_securities_changed(self, changes): + def on_securities_changed(self, changes: SecurityChanges) -> None: for security in changes.removed_securities: if security in self.crunch_universe: self.crunch_universe.remove(security) self.crunch_universe.extend(changes.added_securities) - def submit_signals(self): + def submit_signals(self) -> None: if self.is_warming_up: return # Submit signals once per week week_num = self.time.isocalendar()[1] - if self.week == week_num: + if self._week == week_num: return - self.week = week_num + self._week = week_num symbols = [security.symbol for security in self.crunch_universe if security.price > 0] @@ -93,11 +93,12 @@ def submit_signals(self): class CrunchDaoSkeleton(PythonData): - def get_source(self, config, date, is_live): + def get_source(self, config: SubscriptionDataConfig, date: datetime, is_live_mode: bool) -> SubscriptionDataSource: return SubscriptionDataSource("https://tournament.crunchdao.com/data/skeleton.csv", SubscriptionTransportMedium.REMOTE_FILE) - def reader(self, config, line, date, is_live): - if not line[0].isdigit(): return None + def reader(self, config: SubscriptionDataConfig, line: str, date: datetime, is_live_mode: bool) -> DynamicData: + if not line[0].isdigit(): + return None skeleton = CrunchDaoSkeleton() skeleton.symbol = config.symbol diff --git a/Algorithm.Python/CustomDataPropertiesRegressionAlgorithm.py b/Algorithm.Python/CustomDataPropertiesRegressionAlgorithm.py index bf7c9ba4f6af..4f373c3c5f38 100644 --- a/Algorithm.Python/CustomDataPropertiesRegressionAlgorithm.py +++ b/Algorithm.Python/CustomDataPropertiesRegressionAlgorithm.py @@ -11,6 +11,7 @@ # See the License for the specific language governing permissions and # limitations under the License. +import json from AlgorithmImports import * ### @@ -23,59 +24,56 @@ ### class CustomDataPropertiesRegressionAlgorithm(QCAlgorithm): - def initialize(self): + def initialize(self) -> None: self.set_start_date(2020, 1, 5) # Set Start Date - self.set_end_date(2020, 1, 10) # Set End Date - self.set_cash(100000) # Set Strategy Cash + self.set_end_date(2020, 1, 10) # Set End Date + self.set_cash(100000) # Set Strategy Cash # Define our custom data properties and exchange hours - self.ticker = 'BTC' - properties = SymbolProperties("Bitcoin", "USD", 1, 0.01, 0.01, self.ticker) + ticker = 'BTC' + properties = SymbolProperties("Bitcoin", "USD", 1, 0.01, 0.01, ticker) exchange_hours = SecurityExchangeHours.always_open(TimeZones.NEW_YORK) # Add the custom data to our algorithm with our custom properties and exchange hours - self.bitcoin = self.add_data(Bitcoin, self.ticker, properties, exchange_hours, leverage=1, fill_forward=False) + self._bitcoin = self.add_data(Bitcoin, ticker, properties, exchange_hours, leverage=1, fill_forward=False) # Verify our symbol properties were changed and loaded into this security - if self.bitcoin.symbol_properties != properties : + if self._bitcoin.symbol_properties != properties : raise AssertionError("Failed to set and retrieve custom SymbolProperties for BTC") # Verify our exchange hours were changed and loaded into this security - if self.bitcoin.exchange.hours != exchange_hours : + if self._bitcoin.exchange.hours != exchange_hours : raise AssertionError("Failed to set and retrieve custom ExchangeHours for BTC") # For regression purposes on AddData overloads, this call is simply to ensure Lean can accept this # with default params and is not routed to a breaking function. self.add_data(Bitcoin, "BTCUSD") - - def on_data(self, data): + def on_data(self, data: Slice) -> None: if not self.portfolio.invested: if data['BTC'].close != 0 : self.order('BTC', self.portfolio.margin_remaining/abs(data['BTC'].close + 1)) - def on_end_of_algorithm(self): + def on_end_of_algorithm(self) -> None: #Reset our Symbol property value, for testing purposes. - self.symbol_properties_database.set_entry(Market.USA, self.market_hours_database.get_database_symbol_key(self.bitcoin.symbol), SecurityType.BASE, + self.symbol_properties_database.set_entry(Market.USA, self.market_hours_database.get_database_symbol_key(self._bitcoin.symbol), SecurityType.BASE, SymbolProperties.get_default("USD")) - class Bitcoin(PythonData): '''Custom Data Type: Bitcoin data from Quandl - http://www.quandl.com/help/api-for-bitcoin-data''' - def get_source(self, config, date, is_live_mode): + def get_source(self, config: SubscriptionDataConfig, date: datetime, is_live_mode: bool) -> SubscriptionDataSource: if is_live_mode: return SubscriptionDataSource("https://www.bitstamp.net/api/ticker/", SubscriptionTransportMedium.REST) #return "http://my-ftp-server.com/futures-data-" + date.to_string("Ymd") + ".zip" # OR simply return a fixed small data file. Large files will slow down your backtest subscription = SubscriptionDataSource("https://www.quantconnect.com/api/v2/proxy/nasdaq/api/v3/datatables/QDL/BITFINEX.csv?code=BTCUSD&api_key=WyAazVXnq7ATy_fefTqm") - subscription.Sort = True + subscription.sort = True return subscription - - def reader(self, config, line, date, is_live_mode): + def reader(self, config: SubscriptionDataConfig, line: str, date: datetime, is_live_mode: bool) -> DynamicData: coin = Bitcoin() coin.symbol = config.symbol @@ -87,7 +85,7 @@ def reader(self, config, line, date, is_live_mode): # If value is zero, return None value = live_btc["last"] - if value == 0: return None + if value == 0: return coin coin.time = datetime.now() coin.value = value @@ -102,17 +100,17 @@ def reader(self, config, line, date, is_live_mode): return coin except ValueError: # Do nothing, possible error in json decoding - return None + return coin # Example Line Format: #code date high low mid last bid ask volume #BTCUSD 2024-10-08 63248.0 61940.0 62246.5 62245.0 62246.0 62247.0 5.929230648356 - if not (line.strip() and line[7].isdigit()): return None + if not (line.strip() and line[7].isdigit()): return coin try: data = line.split(',') coin.time = datetime.strptime(data[1], "%Y-%m-%d") - coin.end_time = coin.time + timedelta(days=1) + coin.end_time = coin.time + timedelta(1) coin.value = float(data[5]) coin["High"] = float(data[2]) coin["Low"] = float(data[3]) @@ -125,4 +123,4 @@ def reader(self, config, line, date, is_live_mode): except ValueError: # Do nothing, possible error in json decoding - return None + return coin diff --git a/Algorithm.Python/CustomDataRegressionAlgorithm.py b/Algorithm.Python/CustomDataRegressionAlgorithm.py index b8f45af7b857..0515915211ed 100644 --- a/Algorithm.Python/CustomDataRegressionAlgorithm.py +++ b/Algorithm.Python/CustomDataRegressionAlgorithm.py @@ -11,6 +11,7 @@ # See the License for the specific language governing permissions and # limitations under the License. +import json from AlgorithmImports import * ### @@ -23,8 +24,7 @@ ### class CustomDataRegressionAlgorithm(QCAlgorithm): - def initialize(self): - + def initialize(self) -> None: self.set_start_date(2020,1,5) # Set Start Date self.set_end_date(2020,1,10) # Set End Date self.set_cash(100000) # Set Strategy Cash @@ -36,12 +36,12 @@ def initialize(self): self.set_security_initializer(lambda x: seeder.seed_security(x)) self._warmed_up_checked = False - def on_data(self, data): + def on_data(self, data: Slice) -> None: if not self.portfolio.invested: if data['BTC'].close != 0 : self.order('BTC', self.portfolio.margin_remaining/abs(data['BTC'].close + 1)) - def on_securities_changed(self, changes): + def on_securities_changed(self, changes: SecurityChanges) -> None: changes.filter_custom_securities = False for added_security in changes.added_securities: if added_security.symbol.value == "BTC": @@ -49,25 +49,24 @@ def on_securities_changed(self, changes): if not added_security.has_data: raise ValueError(f"Security {added_security.symbol} was not warmed up!") - def on_end_of_algorithm(self): + def on_end_of_algorithm(self) -> None: if not self._warmed_up_checked: raise ValueError("Security was not warmed up!") class Bitcoin(PythonData): '''Custom Data Type: Bitcoin data from Quandl - https://data.nasdaq.com/databases/BCHAIN''' - def get_source(self, config, date, is_live_mode): + def get_source(self, config: SubscriptionDataConfig, date: datetime, is_live_mode: bool) -> SubscriptionDataSource: if is_live_mode: return SubscriptionDataSource("https://www.bitstamp.net/api/ticker/", SubscriptionTransportMedium.REST) #return "http://my-ftp-server.com/futures-data-" + date.to_string("Ymd") + ".zip" # OR simply return a fixed small data file. Large files will slow down your backtest subscription = SubscriptionDataSource("https://www.quantconnect.com/api/v2/proxy/nasdaq/api/v3/datatables/QDL/BITFINEX.csv?code=BTCUSD&api_key=WyAazVXnq7ATy_fefTqm") - subscription.Sort = True + subscription.sort = True return subscription - - def reader(self, config, line, date, is_live_mode): + def reader(self, config: SubscriptionDataConfig, line: str, date: datetime, is_live_mode: bool) -> DynamicData: coin = Bitcoin() coin.symbol = config.symbol @@ -77,9 +76,10 @@ def reader(self, config, line, date, is_live_mode): try: live_btc = json.loads(line) - # If value is zero, return None + # If value is zero, return coin value = live_btc["last"] - if value == 0: return None + if value == 0: + return coin coin.time = datetime.now() coin.value = value @@ -94,12 +94,12 @@ def reader(self, config, line, date, is_live_mode): return coin except ValueError: # Do nothing, possible error in json decoding - return None + return coin # Example Line Format: # code date high low mid last bid ask volume # BTCUSD 2024-10-08 63248.0 61940.0 62246.5 62245.0 62246.0 62247.0 477.91102114 - if not (line.strip() and line[7].isdigit()): return None + if not (line.strip() and line[7].isdigit()): return coin try: data = line.split(',') @@ -117,4 +117,4 @@ def reader(self, config, line, date, is_live_mode): except ValueError: # Do nothing, possible error in json decoding - return None + return coin diff --git a/Algorithm.Python/CustomIndicatorAlgorithm.py b/Algorithm.Python/CustomIndicatorAlgorithm.py index d8d648fc2d5d..112b00e32067 100644 --- a/Algorithm.Python/CustomIndicatorAlgorithm.py +++ b/Algorithm.Python/CustomIndicatorAlgorithm.py @@ -21,54 +21,54 @@ ### ### class CustomIndicatorAlgorithm(QCAlgorithm): - def initialize(self): + def initialize(self) -> None: self.set_start_date(2013,10,7) self.set_end_date(2013,10,11) self.add_equity("SPY", Resolution.SECOND) # Create a QuantConnect indicator and a python custom indicator for comparison self._sma = self.sma("SPY", 60, Resolution.MINUTE) - self.custom = CustomSimpleMovingAverage('custom', 60) + self._custom = CustomSimpleMovingAverage('custom', 60) # The python custom class must inherit from PythonIndicator to enable Updated event handler - self.custom.updated += self.custom_updated + self._custom.updated += self._custom_updated - self.custom_window = RollingWindow[IndicatorDataPoint](5) - self.register_indicator("SPY", self.custom, Resolution.MINUTE) - self.plot_indicator('CSMA', self.custom) + self._custom_window = RollingWindow[IndicatorDataPoint](5) + self.register_indicator("SPY", self._custom, Resolution.MINUTE) + self.plot_indicator('CSMA', self._custom) - def custom_updated(self, sender, updated): - self.custom_window.add(updated) + def custom_updated(self, sender: object, updated: IndicatorDataPoint) -> None: + self._custom_window.add(updated) - def on_data(self, data): + def on_data(self, data: Slice) -> None: if not self.portfolio.invested: self.set_holdings("SPY", 1) if self.time.second == 0: self.log(f" sma -> IsReady: {self._sma.is_ready}. Value: {self._sma.current.value}") - self.log(f"custom -> IsReady: {self.custom.is_ready}. Value: {self.custom.value}") + self.log(f"custom -> IsReady: {self._custom.is_ready}. Value: {self._custom.value}") # Regression test: test fails with an early quit - diff = abs(self.custom.value - self._sma.current.value) + diff = abs(self._custom.value - self._sma.current.value) if diff > 1e-10: self.quit(f"Quit: indicators difference is {diff}") - def on_end_of_algorithm(self): - for item in self.custom_window: + def on_end_of_algorithm(self) -> None: + for item in self._custom_window: self.log(f'{item}') # Python implementation of SimpleMovingAverage. # Represents the traditional simple moving average indicator (SMA). class CustomSimpleMovingAverage(PythonIndicator): - def __init__(self, name, period): + def __init__(self, name: str, period: int) -> None: super().__init__() self.name = name self.value = 0 - self.queue = deque(maxlen=period) + self._queue = deque(maxlen=period) # Update method is mandatory - def update(self, input): - self.queue.appendleft(input.value) - count = len(self.queue) - self.value = np.sum(self.queue) / count - return count == self.queue.maxlen + def update(self, input: IndicatorDataPoint) -> bool: + self._queue.appendleft(input.value) + count = len(self._queue) + self.value = np.sum(self._queue) / count + return count == self._queue.maxlen diff --git a/Algorithm.Python/CustomIndicatorWithExtensionAlgorithm.py b/Algorithm.Python/CustomIndicatorWithExtensionAlgorithm.py index fa0a107c9b11..a29f3ae1921d 100644 --- a/Algorithm.Python/CustomIndicatorWithExtensionAlgorithm.py +++ b/Algorithm.Python/CustomIndicatorWithExtensionAlgorithm.py @@ -17,72 +17,71 @@ from math import isclose class CustomIndicatorWithExtensionAlgorithm(QCAlgorithm): - def initialize(self): + def initialize(self) -> None: self.set_start_date(2013, 10, 9) self.set_end_date(2013, 10, 9) - self.spy = self.add_equity("SPY", Resolution.MINUTE).symbol + self._spy = self.add_equity("SPY", Resolution.MINUTE).symbol - self.sma_values = [] - self.period = 10 + self._sma_values = [] + self._period = 10 - self.sma = self.sma(self.spy, self.period, Resolution.MINUTE) - self.sma.updated += self.on_sma_updated + self._sma = self.sma(self._spy, self._period, Resolution.MINUTE) + self._sma.updated += self.on_sma_updated - self.custom_sma = CustomSimpleMovingAverage("My SMA", self.period) - self.ext = IndicatorExtensions.of(self.custom_sma, self.sma) - self.ext.updated += self.on_indicator_extension_updated + self._custom_sma = CustomSimpleMovingAverage("My SMA", self._period) + self._ext = IndicatorExtensions.of(self._custom_sma, self._sma) + self._ext.updated += self.on_indicator_extension_updated - self.sma_minus_custom = IndicatorExtensions.minus(self.sma, self.custom_sma) - self.sma_minus_custom.updated += self.on_minus_updated + self._sma_minus_custom = IndicatorExtensions.minus(self._sma, self._custom_sma) + self._sma_minus_custom.updated += self.on_minus_updated - self.sma_was_updated = False - self.custom_sma_was_updated = False - self.sma_minus_custom_was_updated = False + self._sma_was_updated = False + self._custom_sma_was_updated = False + self._sma_minus_custom_was_updated = False - def on_sma_updated(self, sender, updated): - self.sma_was_updated = True + def on_sma_updated(self, sender: object, updated: IndicatorDataPoint) -> None: + self._sma_was_updated = True - if self.sma.is_ready: - self.sma_values.append(self.sma.current.value) + if self._sma.is_ready: + self._sma_values.append(self._sma.current.value) - def on_indicator_extension_updated(self, sender, updated): - self.custom_sma_was_updated = True + def on_indicator_extension_updated(self, sender: object, updated: IndicatorDataPoint) -> None: + self._custom_sma_was_updated = True - sma_last_values = self.sma_values[-self.period:] + sma_last_values = self._sma_values[-self._period:] expected = sum(sma_last_values) / len(sma_last_values) - if not isclose(expected, self.custom_sma.value): - raise AssertionError(f"Expected the custom SMA to calculate the moving average of the last {self.period} values of the SMA. " - f"Current expected: {expected}. Actual {self.custom_sma.value}.") + if not isclose(expected, self._custom_sma.value): + raise AssertionError(f"Expected the custom SMA to calculate the moving average of the last {self._period} values of the SMA. " + f"Current expected: {expected}. Actual {self._custom_sma.value}.") - self.debug(f"{self.sma.current.value} :: {self.custom_sma.value} :: {updated}") + self.debug(f"{self._sma.current.value} :: {self._custom_sma.value} :: {updated}") - def on_minus_updated(self, sender, updated): - self.sma_minus_custom_was_updated = True + def on_minus_updated(self, sender: object, updated: IndicatorDataPoint) -> None: + self._sma_minus_custom_was_updated = True - expected = self.sma.current.value - self.custom_sma.value + expected = self._sma.current.value - self._custom_sma.value - if not isclose(expected, self.sma_minus_custom.current.value): + if not isclose(expected, self._sma_minus_custom.current.value): raise AssertionError(f"Expected the composite minus indicator to calculate the difference between the SMA and custom SMA indicators. " - f"Expected: {expected}. Actual {self.sma_minus_custom.current.value}.") + f"Expected: {expected}. Actual {self._sma_minus_custom.current.value}.") - def on_end_of_algorithm(self): - if not (self.sma_was_updated and self.custom_sma_was_updated and self.sma_minus_custom_was_updated): + def on_end_of_algorithm(self) -> None: + if not (self._sma_was_updated and self._custom_sma_was_updated and self._sma_minus_custom_was_updated): raise AssertionError("Expected all indicators to have been updated.") # Custom indicator class CustomSimpleMovingAverage(PythonIndicator): - def __init__(self, name, period): + def __init__(self, name: str, period: int) -> None: self.name = name self.value = 0 self.warm_up_period = period - self.queue = deque(maxlen=period) + self._queue = deque(maxlen=period) def update(self, input: BaseData) -> bool: - self.queue.appendleft(input.value) - count = len(self.queue) - self.value = sum(self.queue) / count - - return count == self.queue.maxlen + self._queue.appendleft(input.value) + count = len(self._queue) + self.value = sum(self._queue) / count + return count == self._queue.maxlen diff --git a/Algorithm.Python/CustomShortableProviderRegressionAlgorithm.py b/Algorithm.Python/CustomShortableProviderRegressionAlgorithm.py index d8454dc5b9b0..c5fe6be89748 100644 --- a/Algorithm.Python/CustomShortableProviderRegressionAlgorithm.py +++ b/Algorithm.Python/CustomShortableProviderRegressionAlgorithm.py @@ -18,41 +18,40 @@ ### class CustomShortableProviderRegressionAlgorithm(QCAlgorithm): - def initialize(self): + def initialize(self) -> None: self.set_cash(10000000) self.set_start_date(2013,10,4) self.set_end_date(2013,10,6) - self.spy = self.add_security(SecurityType.EQUITY, "SPY", Resolution.DAILY) - self.spy.set_shortable_provider(CustomShortableProvider()) + self._spy = self.add_security(SecurityType.EQUITY, "SPY", Resolution.DAILY) + self._spy.set_shortable_provider(CustomShortableProvider()) - def on_data(self, data): - spy_shortable_quantity = self.spy.shortable_provider.shortable_quantity(self.spy.symbol, self.time) - if spy_shortable_quantity > 1000: - self.order_id = self.sell("SPY", int(spy_shortable_quantity)) + def on_data(self, data: Slice) -> None: + spy_shortable_quantity = self._spy.shortable_provider.shortable_quantity(self._spy.symbol, self.time) + if spy_shortable_quantity and spy_shortable_quantity > 1000: + self._order_id = self.sell("SPY", int(spy_shortable_quantity)).order_id - def on_end_of_algorithm(self): + def on_end_of_algorithm(self) -> None: transactions = self.transactions.orders_count if transactions != 1: raise AssertionError("Algorithm should have just 1 order, but was " + str(transactions)) - order_quantity = self.transactions.get_order_by_id(self.order_id).quantity + order_quantity = self.transactions.get_order_by_id(self._order_id).quantity if order_quantity != -1001: - raise AssertionError("Quantity of order " + str(_order_id) + " should be " + str(-1001)+", but was {order_quantity}") + raise AssertionError(f"Quantity of order {self._order_id} should be -1001 but was {order_quantity}") - fee_rate = self.spy.shortable_provider.fee_rate(self.spy.symbol, self.time) + fee_rate = self._spy.shortable_provider.fee_rate(self._spy.symbol, self.time) if fee_rate != 0.0025: raise AssertionError(f"Fee rate should be 0.0025, but was {fee_rate}") - rebate_rate = self.spy.shortable_provider.rebate_rate(self.spy.symbol, self.time) + rebate_rate = self._spy.shortable_provider.rebate_rate(self._spy.symbol, self.time) if rebate_rate != 0.0507: raise AssertionError(f"Rebate rate should be 0.0507, but was {rebate_rate}") class CustomShortableProvider(NullShortableProvider): - def fee_rate(self, symbol: Symbol, local_time: DateTime): + def fee_rate(self, symbol: Symbol, local_time: datetime) -> float: return 0.0025 - def rebate_rate(self, symbol: Symbol, local_time: DateTime): + def rebate_rate(self, symbol: Symbol, local_time: datetime) -> float: return 0.0507 - def shortable_quantity(self, symbol: Symbol, local_time: DateTime): + def shortable_quantity(self, symbol: Symbol, local_time: datetime) -> int: if local_time < datetime(2013,10,4,16,0,0): return 10 - else: - return 1001 + return 1001 diff --git a/Algorithm.Python/CustomWarmUpPeriodIndicatorAlgorithm.py b/Algorithm.Python/CustomWarmUpPeriodIndicatorAlgorithm.py index af80e2ba5c36..d5aa465562d4 100644 --- a/Algorithm.Python/CustomWarmUpPeriodIndicatorAlgorithm.py +++ b/Algorithm.Python/CustomWarmUpPeriodIndicatorAlgorithm.py @@ -23,9 +23,9 @@ ### ### class CustomWarmUpPeriodIndicatorAlgorithm(QCAlgorithm): - def initialize(self): - self.set_start_date(2013,10,7) - self.set_end_date(2013,10,11) + def initialize(self) -> None: + self.set_start_date(2013, 10, 7) + self.set_end_date(2013, 10, 11) self.add_equity("SPY", Resolution.SECOND) # Create three python indicators @@ -33,106 +33,106 @@ def initialize(self): # - custom_warm_up defines WarmUpPeriod parameter # - custom_not_inherit defines WarmUpPeriod parameter but does not inherit from PythonIndicator class # - csharp_indicator defines WarmUpPeriod parameter and represents the traditional LEAN C# indicator - self.custom_not_warm_up = CSMANotWarmUp('custom_not_warm_up', 60) - self.custom_warm_up = CSMAWithWarmUp('custom_warm_up', 60) - self.custom_not_inherit = CustomSMA('custom_not_inherit', 60) - self.csharp_indicator = SimpleMovingAverage('csharp_indicator', 60) + self._custom_not_warm_up = CSMANotWarmUp('custom_not_warm_up', 60) + self._custom_warm_up = CSMAWithWarmUp('custom_warm_up', 60) + self._custom_not_inherit = CustomSMA('custom_not_inherit', 60) + self._csharp_indicator = SimpleMovingAverage('csharp_indicator', 60) # Register the daily data of "SPY" to automatically update the indicators - self.register_indicator("SPY", self.custom_warm_up, Resolution.MINUTE) - self.register_indicator("SPY", self.custom_not_warm_up, Resolution.MINUTE) - self.register_indicator("SPY", self.custom_not_inherit, Resolution.MINUTE) - self.register_indicator("SPY", self.csharp_indicator, Resolution.MINUTE) + self.register_indicator("SPY", self._custom_warm_up, Resolution.MINUTE) + self.register_indicator("SPY", self._custom_not_warm_up, Resolution.MINUTE) + self.register_indicator("SPY", self._custom_not_inherit, Resolution.MINUTE) + self.register_indicator("SPY", self._csharp_indicator, Resolution.MINUTE) # Warm up custom_warm_up indicator - self.warm_up_indicator("SPY", self.custom_warm_up, Resolution.MINUTE) + self.warm_up_indicator("SPY", self._custom_warm_up, Resolution.MINUTE) # Check custom_warm_up indicator has already been warmed up with the requested data - assert(self.custom_warm_up.is_ready), "custom_warm_up indicator was expected to be ready" - assert(self.custom_warm_up.samples == 60), "custom_warm_up indicator was expected to have processed 60 datapoints already" + assert(self._custom_warm_up.is_ready), "custom_warm_up indicator was expected to be ready" + assert(self._custom_warm_up.samples == 60), "custom_warm_up indicator was expected to have processed 60 datapoints already" # Try to warm up custom_not_warm_up indicator. It's expected from LEAN to skip the warm up process # because this indicator doesn't define WarmUpPeriod parameter - self.warm_up_indicator("SPY", self.custom_not_warm_up, Resolution.MINUTE) + self.warm_up_indicator("SPY", self._custom_not_warm_up, Resolution.MINUTE) # Check custom_not_warm_up indicator is not ready and is using the default WarmUpPeriod value - assert(not self.custom_not_warm_up.is_ready), "custom_not_warm_up indicator wasn't expected to be warmed up" - assert(self.custom_not_warm_up.warm_up_period == 0), "custom_not_warm_up indicator WarmUpPeriod parameter was expected to be 0" + assert(not self._custom_not_warm_up.is_ready), "custom_not_warm_up indicator wasn't expected to be warmed up" + assert(self._custom_not_warm_up.warm_up_period == 0), "custom_not_warm_up indicator WarmUpPeriod parameter was expected to be 0" # Warm up custom_not_inherit indicator. Though it does not inherit from PythonIndicator class, # it defines WarmUpPeriod parameter so it's expected to be warmed up from LEAN - self.warm_up_indicator("SPY", self.custom_not_inherit, Resolution.MINUTE) + self.warm_up_indicator("SPY", self._custom_not_inherit, Resolution.MINUTE) # Check custom_not_inherit indicator has already been warmed up with the requested data - assert(self.custom_not_inherit.is_ready), "custom_not_inherit indicator was expected to be ready" - assert(self.custom_not_inherit.samples == 60), "custom_not_inherit indicator was expected to have processed 60 datapoints already" + assert(self._custom_not_inherit.is_ready), "custom_not_inherit indicator was expected to be ready" + assert(self._custom_not_inherit.samples == 60), "custom_not_inherit indicator was expected to have processed 60 datapoints already" # Warm up csharp_indicator - self.warm_up_indicator("SPY", self.csharp_indicator, Resolution.MINUTE) + self.warm_up_indicator("SPY", self._csharp_indicator, Resolution.MINUTE) # Check csharp_indicator indicator has already been warmed up with the requested data - assert(self.csharp_indicator.is_ready), "csharp_indicator indicator was expected to be ready" - assert(self.csharp_indicator.samples == 60), "csharp_indicator indicator was expected to have processed 60 datapoints already" + assert(self._csharp_indicator.is_ready), "csharp_indicator indicator was expected to be ready" + assert(self._csharp_indicator.samples == 60), "csharp_indicator indicator was expected to have processed 60 datapoints already" - def on_data(self, data): + def on_data(self, data: Slice) -> None: if not self.portfolio.invested: self.set_holdings("SPY", 1) if self.time.second == 0: # Compute the difference between indicators values - diff = abs(self.custom_not_warm_up.current.value - self.custom_warm_up.current.value) - diff += abs(self.custom_not_inherit.value - self.custom_not_warm_up.current.value) - diff += abs(self.custom_not_inherit.value - self.custom_warm_up.current.value) - diff += abs(self.csharp_indicator.current.value - self.custom_warm_up.current.value) - diff += abs(self.csharp_indicator.current.value - self.custom_not_warm_up.current.value) - diff += abs(self.csharp_indicator.current.value - self.custom_not_inherit.value) + diff = abs(self._custom_not_warm_up.current.value - self._custom_warm_up.current.value) + diff += abs(self._custom_not_inherit.value - self._custom_not_warm_up.current.value) + diff += abs(self._custom_not_inherit.value - self._custom_warm_up.current.value) + diff += abs(self._csharp_indicator.current.value - self._custom_warm_up.current.value) + diff += abs(self._csharp_indicator.current.value - self._custom_not_warm_up.current.value) + diff += abs(self._csharp_indicator.current.value - self._custom_not_inherit.value) # Check custom_not_warm_up indicator is ready when the number of samples is bigger than its WarmUpPeriod parameter - assert(self.custom_not_warm_up.is_ready == (self.custom_not_warm_up.samples >= 60)), "custom_not_warm_up indicator was expected to be ready when the number of samples were bigger that its WarmUpPeriod parameter" + assert(self._custom_not_warm_up.is_ready == (self._custom_not_warm_up.samples >= 60)), "custom_not_warm_up indicator was expected to be ready when the number of samples were bigger that its WarmUpPeriod parameter" # Check their values are the same. We only need to check if custom_not_warm_up indicator is ready because the other ones has already been asserted to be ready - assert(diff <= 1e-10 or (not self.custom_not_warm_up.is_ready)), f"The values of the indicators are not the same. Indicators difference is {diff}" + assert(diff <= 1e-10 or (not self._custom_not_warm_up.is_ready)), f"The values of the indicators are not the same. Indicators difference is {diff}" # Python implementation of SimpleMovingAverage. # Represents the traditional simple moving average indicator (SMA) without Warm Up Period parameter defined class CSMANotWarmUp(PythonIndicator): - def __init__(self, name, period): + def __init__(self, name: str, period: int) -> None: super().__init__() self.name = name self.value = 0 - self.queue = deque(maxlen=period) + self._queue = deque(maxlen=period) # Update method is mandatory - def update(self, input): - self.queue.appendleft(input.value) - count = len(self.queue) - self.value = np.sum(self.queue) / count - return count == self.queue.maxlen + def update(self, input: IndicatorDataPoint) -> bool: + self._queue.appendleft(input.value) + count = len(self._queue) + self.value = np.sum(self._queue) / count + return count == self._queue.maxlen # Python implementation of SimpleMovingAverage. # Represents the traditional simple moving average indicator (SMA) With Warm Up Period parameter defined class CSMAWithWarmUp(CSMANotWarmUp): - def __init__(self, name, period): + def __init__(self, name: str, period: int) -> None: super().__init__(name, period) self.warm_up_period = period # Custom python implementation of SimpleMovingAverage. # Represents the traditional simple moving average indicator (SMA) class CustomSMA(): - def __init__(self, name, period): + def __init__(self, name: str, period: int) -> None: self.name = name self.value = 0 - self.queue = deque(maxlen=period) + self._queue = deque(maxlen=period) self.warm_up_period = period self.is_ready = False self.samples = 0 # Update method is mandatory - def update(self, input): + def update(self, input: IndicatorDataPoint) -> bool: self.samples += 1 - self.queue.appendleft(input.value) - count = len(self.queue) - self.value = np.sum(self.queue) / count - if count == self.queue.maxlen: + self._queue.appendleft(input.value) + count = len(self._queue) + self.value = np.sum(self._queue) / count + if count == self._queue.maxlen: self.is_ready = True return self.is_ready diff --git a/Algorithm.Python/DailyAlgorithm.py b/Algorithm.Python/DailyAlgorithm.py index 5c9045fce8da..63a386b8fb0a 100644 --- a/Algorithm.Python/DailyAlgorithm.py +++ b/Algorithm.Python/DailyAlgorithm.py @@ -29,11 +29,10 @@ def initialize(self): self.set_cash(100000) #Set Strategy Cash # Find more symbols here: http://quantconnect.com/data self.add_equity("SPY", Resolution.DAILY) - self.add_equity("IBM", Resolution.HOUR).set_leverage(1.0) - self.macd = self.macd("SPY", 12, 26, 9, MovingAverageType.WILDERS, Resolution.DAILY, Field.CLOSE) - self.ema = self.ema("IBM", 15 * 6, Resolution.HOUR, Field.SEVEN_BAR) - self.last_action = None - + self.add_equity("IBM", Resolution.HOUR, leverage=1) + self._macd = self.macd("SPY", 12, 26, 9, MovingAverageType.WILDERS, Resolution.DAILY, Field.CLOSE) + self._ema = self.ema("IBM", 15 * 6, Resolution.HOUR, Field.SEVEN_BAR) + self._last_action = self.start_date def on_data(self, data): '''OnData event is the primary entry point for your algorithm. Each new data point will be pumped in here. @@ -41,17 +40,15 @@ def on_data(self, data): Arguments: data: Slice object keyed by symbol containing the stock data ''' - if not self.macd.is_ready: return - if not data.contains_key("IBM"): return - if data["IBM"] is None: - self.log("Price Missing Time: %s"%str(self.time)) - return - if self.last_action is not None and self.last_action.date() == self.time.date(): return - - self.last_action = self.time + if not self._macd.is_ready: return + bar = data.bars.get("IBM") + if not bar: return + if self._last_action.date() == self.time.date(): return + + self._last_action = self.time quantity = self.portfolio["SPY"].quantity - if quantity <= 0 and self.macd.current.value > self.macd.signal.current.value and data["IBM"].price > self.ema.current.value: + if quantity <= 0 and self._macd.current.value > self._macd.signal.current.value and bar.price > self._ema.current.value: self.set_holdings("IBM", 0.25) - elif quantity >= 0 and self.macd.current.value < self.macd.signal.current.value and data["IBM"].price < self.ema.current.value: + if quantity >= 0 and self._macd.current.value < self._macd.signal.current.value and bar.price < self._ema.current.value: self.set_holdings("IBM", -0.25) diff --git a/Algorithm.Python/DescendingCustomDataObjectStoreRegressionAlgorithm.py b/Algorithm.Python/DescendingCustomDataObjectStoreRegressionAlgorithm.py index 87a054611a0a..e5c0189922bb 100644 --- a/Algorithm.Python/DescendingCustomDataObjectStoreRegressionAlgorithm.py +++ b/Algorithm.Python/DescendingCustomDataObjectStoreRegressionAlgorithm.py @@ -40,7 +40,7 @@ class DescendingCustomDataObjectStoreRegressionAlgorithm(QCAlgorithm): "2024-09-09 12:00:00,176.0,178.0,175.0,177.0,116275729,4641.97" ] - def initialize(self): + def initialize(self) -> None: self.set_start_date(2024, 9, 9) self.set_end_date(2024, 10, 3) self.set_cash(100000) @@ -49,7 +49,7 @@ def initialize(self): SortCustomData.custom_data_key = self.get_custom_data_key() - self.custom_symbol = self.add_data(SortCustomData, "SortCustomData", Resolution.DAILY).symbol + self._custom_symbol = self.add_data(SortCustomData, "SortCustomData", Resolution.DAILY).symbol # Saving data here for demonstration and regression testing purposes. # In real scenarios, data has to be saved to the object store before the algorithm starts. @@ -57,20 +57,20 @@ def initialize(self): self.received_data = [] - def on_data(self, slice: Slice): - if slice.contains_key(self.custom_symbol): - custom_data = slice.get(SortCustomData, self.custom_symbol) + def on_data(self, slice: Slice) -> None: + if slice.contains_key(self._custom_symbol): + custom_data = slice.get(SortCustomData, self._custom_symbol) if custom_data.open == 0 or custom_data.high == 0 or custom_data.low == 0 or custom_data.close == 0 or custom_data.price == 0: raise AssertionError("One or more custom data fields (open, high, low, close, price) are zero.") self.received_data.append(custom_data) - def on_end_of_algorithm(self): + def on_end_of_algorithm(self) -> None: if not self.received_data: raise AssertionError("Custom data was not fetched") # Make sure history requests work as expected - history = self.history(SortCustomData, self.custom_symbol, self.start_date, self.end_date, Resolution.DAILY) + history = self.history(SortCustomData, self._custom_symbol, self.start_date, self.end_date, Resolution.DAILY) if history.shape[0] != len(self.received_data): raise AssertionError("History request returned more or less data than expected") @@ -82,21 +82,21 @@ def on_end_of_algorithm(self): raise AssertionError( f"Order failure: {history.index[i][1]} > {history.index[i + 1][1]} at index {i}.") - def get_custom_data_key(self): + def get_custom_data_key(self) -> str: return "CustomData/SortCustomData" class SortCustomData(PythonData): custom_data_key = "" - def get_source(self, config, date, is_live): + def get_source(self, config: SubscriptionDataConfig, date: datetime, is_live_mode: bool) -> SubscriptionDataSource: subscription = SubscriptionDataSource(self.custom_data_key, SubscriptionTransportMedium.OBJECT_STORE, FileFormat.CSV) # Indicate that the data from the subscription will be returned in descending order. - subscription.Sort = True + subscription.sort = True return subscription - def reader(self, config, line, date, is_live): + def reader(self, config: SubscriptionDataConfig, line: str, date: datetime, is_live_mode: bool) -> DynamicData: data = line.split(',') obj_data = SortCustomData() obj_data.symbol = config.symbol diff --git a/Algorithm.Python/DisplacedMovingAverageRibbon.py b/Algorithm.Python/DisplacedMovingAverageRibbon.py index 0b97ac56fc25..5423da2c4303 100644 --- a/Algorithm.Python/DisplacedMovingAverageRibbon.py +++ b/Algorithm.Python/DisplacedMovingAverageRibbon.py @@ -30,51 +30,50 @@ class DisplacedMovingAverageRibbon(QCAlgorithm): def initialize(self): self.set_start_date(2009, 1, 1) #Set Start Date self.set_end_date(2015, 1, 1) #Set End Date - self.spy = self.add_equity("SPY", Resolution.DAILY).symbol + self._spy = self.add_equity("SPY", Resolution.DAILY).symbol count = 6 offset = 5 period = 15 - self.ribbon = [] + self._ribbon = [] # define our sma as the base of the ribbon - self.sma = SimpleMovingAverage(period) + self._sma = SimpleMovingAverage(period) for x in range(count): # define our offset to the zero sma, these various offsets will create our 'displaced' ribbon delay = Delay(offset*(x+1)) # define an indicator that takes the output of the sma and pipes it into our delay indicator - delayed_sma = IndicatorExtensions.of(delay, self.sma) + delayed_sma = IndicatorExtensions.of(delay, self._sma) # register our new 'delayed_sma' for automatic updates on a daily resolution - self.register_indicator(self.spy, delayed_sma, Resolution.DAILY) - self.ribbon.append(delayed_sma) - self.previous = datetime.min - # plot indicators each time they update using the PlotIndicator function - for i in self.ribbon: - self.plot_indicator("Ribbon", i) + self.register_indicator(self._spy, delayed_sma, Resolution.DAILY) + # plot indicators each time they update using the plot_indicator function + self.plot_indicator("Ribbon", delayed_sma) + self._ribbon.append(delayed_sma) + self._previous = datetime.min # on_data event is the primary entry point for your algorithm. Each new data point will be pumped in here. def on_data(self, data): - if data[self.spy] is None: return + if not data[self._spy]: return # wait for our entire ribbon to be ready - if not all(x.is_ready for x in self.ribbon): return + if not all(x.is_ready for x in self._ribbon): return # only once per day - if self.previous.date() == self.time.date(): return - self.plot("Ribbon", "Price", data[self.spy].price) + if self._previous.date() == self.time.date(): return + self.plot("Ribbon", "Price", data[self._spy].price) # check for a buy signal - values = [x.current.value for x in self.ribbon] - holding = self.portfolio[self.spy] + values = [x.current.value for x in self._ribbon] + holding = self.portfolio[self._spy] if (holding.quantity <= 0 and self.is_ascending(values)): - self.set_holdings(self.spy, 1.0) + self.set_holdings(self._spy, 1.0) elif (holding.quantity > 0 and self.is_descending(values)): - self.liquidate(self.spy) - self.previous = self.time + self.liquidate(self._spy) + self._previous = self.time # Returns true if the specified values are in ascending order def is_ascending(self, values): last = None for val in values: - if last is None: + if not last: last = val continue if last < val: @@ -86,7 +85,7 @@ def is_ascending(self, values): def is_descending(self, values): last = None for val in values: - if last is None: + if not last: last = val continue if last > val: diff --git a/Algorithm.Python/DropboxBaseDataUniverseSelectionAlgorithm.py b/Algorithm.Python/DropboxBaseDataUniverseSelectionAlgorithm.py index 64bcb57b385e..51208f3e53f5 100644 --- a/Algorithm.Python/DropboxBaseDataUniverseSelectionAlgorithm.py +++ b/Algorithm.Python/DropboxBaseDataUniverseSelectionAlgorithm.py @@ -12,7 +12,6 @@ # limitations under the License. from AlgorithmImports import * -from System.Collections.Generic import List ### ### In this algorithm we show how you can easily use the universe selection feature to fetch symbols @@ -24,8 +23,7 @@ ### class DropboxBaseDataUniverseSelectionAlgorithm(QCAlgorithm): - def initialize(self): - + def initialize(self) -> None: self.universe_settings.resolution = Resolution.DAILY # Order margin value has to have a minimum of 0.5% of Portfolio value, allows filtering out small trades and reduce fees. @@ -45,17 +43,20 @@ def initialize(self): if len(universe_data) != 5: raise ValueError(f"Unexpected universe data receieved") - def stock_data_source(self, data): + self._changes = None + + def stock_data_source(self, data: list[DynamicData]) -> list[Symbol]: list = [] for item in data: for symbol in item["Symbols"]: list.append(symbol) return list - def on_data(self, slice): - - if slice.bars.count == 0: return - if self._changes is None: return + def on_data(self, slice: Slice) -> None: + if slice.bars.count == 0: + return + if not self._changes: + return # start fresh self.liquidate() @@ -67,19 +68,19 @@ def on_data(self, slice): # reset changes self._changes = None - def on_securities_changed(self, changes): + def on_securities_changed(self, changes: SecurityChanges) -> None: self._changes = changes class StockDataSource(PythonData): - def get_source(self, config, date, is_live_mode): + def get_source(self, config: SubscriptionDataConfig, date: datetime, is_live_mode: bool) -> SubscriptionDataSource: url = "https://www.dropbox.com/s/2l73mu97gcehmh7/daily-stock-picker-live.csv?dl=1" if is_live_mode else \ "https://www.dropbox.com/s/ae1couew5ir3z9y/daily-stock-picker-backtest.csv?dl=1" - return SubscriptionDataSource(url, SubscriptionTransportMedium.REMOTE_FILE) - def reader(self, config, line, date, is_live_mode): - if not (line.strip() and line[0].isdigit()): return None + def reader(self, config: SubscriptionDataConfig, line: str, date: datetime, is_live_mode: bool) -> DynamicData: + if not (line.strip() and line[0].isdigit()): + return None stocks = StockDataSource() stocks.symbol = config.symbol diff --git a/Algorithm.Python/DropboxUniverseSelectionAlgorithm.py b/Algorithm.Python/DropboxUniverseSelectionAlgorithm.py index ad8a1371203a..4f4660d73bb8 100644 --- a/Algorithm.Python/DropboxUniverseSelectionAlgorithm.py +++ b/Algorithm.Python/DropboxUniverseSelectionAlgorithm.py @@ -24,12 +24,12 @@ ### class DropboxUniverseSelectionAlgorithm(QCAlgorithm): - def initialize(self): + def initialize(self) -> None: self.set_start_date(2017, 7, 4) self.set_end_date(2018, 7, 4) - self.backtest_symbols_per_day = {} - self.current_universe = [] + self._backtest_symbols_per_day = {} + self._current_universe = [] self.universe_settings.resolution = Resolution.DAILY @@ -39,17 +39,17 @@ def initialize(self): self.add_universe("my-dropbox-universe", self.selector) - def selector(self, date): + def selector(self, date: datetime) -> list[str]: # handle live mode file format if self.live_mode: # fetch the file from dropbox str = self.download("https://www.dropbox.com/s/2l73mu97gcehmh7/daily-stock-picker-live.csv?dl=1") # if we have a file for today, return symbols, else leave universe unchanged - self.current_universe = str.split(',') if len(str) > 0 else self.current_universe - return self.current_universe + self._current_universe = str.split(',') if len(str) > 0 else self._current_universe + return self._current_universe # backtest - first cache the entire file - if len(self.backtest_symbols_per_day) == 0: + if len(self._backtest_symbols_per_day) == 0: # No need for headers for authorization with dropbox, these two lines are for example purposes byte_key = base64.b64encode("UserName:Password".encode('ASCII')) @@ -59,17 +59,18 @@ def selector(self, date): str = self.download("https://www.dropbox.com/s/ae1couew5ir3z9y/daily-stock-picker-backtest.csv?dl=1", headers) for line in str.splitlines(): data = line.split(',') - self.backtest_symbols_per_day[data[0]] = data[1:] + self._backtest_symbols_per_day[data[0]] = data[1:] index = date.strftime("%Y%m%d") - self.current_universe = self.backtest_symbols_per_day.get(index, self.current_universe) + self._current_universe = self._backtest_symbols_per_day.get(index, self._current_universe) - return self.current_universe + return self._current_universe - def on_data(self, slice): - - if slice.bars.count == 0: return - if self.changes is None: return + def on_data(self, slice: Slice) -> None: + if slice.bars.count == 0: + return + if not self._changes: + return # start fresh self.liquidate() @@ -79,7 +80,7 @@ def on_data(self, slice): self.set_holdings(trade_bar.symbol, percentage) # reset changes - self.changes = None + self._changes = None - def on_securities_changed(self, changes): - self.changes = changes + def on_securities_changed(self, changes: SecurityChanges) -> None: + self._changes = changes diff --git a/Algorithm.Python/DynamicSecurityDataRegressionAlgorithm.py b/Algorithm.Python/DynamicSecurityDataRegressionAlgorithm.py index cacc891940c8..5f7a08e1a3ab 100644 --- a/Algorithm.Python/DynamicSecurityDataRegressionAlgorithm.py +++ b/Algorithm.Python/DynamicSecurityDataRegressionAlgorithm.py @@ -24,10 +24,10 @@ def initialize(self): self.set_start_date(2015, 10, 22) self.set_end_date(2015, 10, 30) - self.ticker = "GOOGL" - self.equity = self.add_equity(self.ticker, Resolution.DAILY) + ticker = "GOOGL" + self._equity = self.add_equity(ticker, Resolution.DAILY) - custom_linked_equity = self.add_data(LinkedData, self.ticker, Resolution.DAILY) + custom_linked_equity = self.add_data(LinkedData, ticker, Resolution.DAILY) first_linked_data = LinkedData() first_linked_data.count = 100 @@ -45,7 +45,7 @@ def initialize(self): custom_linked_data = List[LinkedData]() custom_linked_data.add(first_linked_data) custom_linked_data.add(second_linked_data) - self.equity.cache.add_data_list(custom_linked_data, custom_linked_equity_type, False) + self._equity.cache.add_data_list(custom_linked_data, custom_linked_equity_type, False) def on_data(self, data): # The Security object's Data property provides convenient access @@ -55,13 +55,13 @@ def on_data(self, data): # 1. Get the most recent data point of a particular type: # 1.a Using the generic method, Get(T): => T - custom_linked_data = self.equity.data.get(LinkedData) - self.log("{}: LinkedData: {}".format(self.time, str(custom_linked_data))) + custom_linked_data = self._equity.data.get(LinkedData) + self.log(f"{self.time}: LinkedData: {custom_linked_data}") # 2. Get the list of data points of a particular type for the most recent time step: # 2.a Using the generic method, GetAll(T): => IReadOnlyList - custom_linked_data_list = self.equity.data.get_all(LinkedData) - self.log("{}: LinkedData: {}".format(self.time, len(custom_linked_data_list))) + custom_linked_data_list = self._equity.data.get_all(LinkedData) + self.log(f"{self.time}: LinkedData: {len(custom_linked_data_list)}") if not self.portfolio.invested: - self.buy(self.equity.symbol, 10) + self.buy(self._equity.symbol, 10) diff --git a/Algorithm.Python/ETFConstituentUniverseFrameworkRegressionAlgorithm.py b/Algorithm.Python/ETFConstituentUniverseFrameworkRegressionAlgorithm.py index b14c6808fbe3..d67ab4e9c1e9 100644 --- a/Algorithm.Python/ETFConstituentUniverseFrameworkRegressionAlgorithm.py +++ b/Algorithm.Python/ETFConstituentUniverseFrameworkRegressionAlgorithm.py @@ -11,7 +11,6 @@ # See the License for the specific language governing permissions and # limitations under the License. -from typing import List from AlgorithmImports import * constituent_data = [] @@ -21,14 +20,15 @@ ### of the ETF constituent ### class ETFConstituentAlphaModel(AlphaModel): - def on_securities_changed(self, algorithm, changes): + def on_securities_changed(self, algorithm: QCAlgorithm, changes: SecurityChanges) -> None: pass ### ### Creates new insights based on constituent data and their weighting ### in their respective ETF ### - def update(self, algorithm: QCAlgorithm, data: Slice): + def update(self, algorithm: QCAlgorithm, data: Slice) -> list[Insight]: + global constituent_data insights = [] for constituent in constituent_data: @@ -57,14 +57,14 @@ def update(self, algorithm: QCAlgorithm, data: Slice): ### of the constituent in their respective ETF ### class ETFConstituentPortfolioModel(PortfolioConstructionModel): - def __init__(self): + def __init__(self) -> None: self.has_added = False ### ### Securities changed, detects if we've got new additions to the universe ### so that we don't try to trade every loop ### - def on_securities_changed(self, algorithm: QCAlgorithm, changes: SecurityChanges): + def on_securities_changed(self, algorithm: QCAlgorithm, changes: SecurityChanges) -> None: self.has_added = len(changes.added_securities) != 0 ### @@ -72,7 +72,7 @@ def on_securities_changed(self, algorithm: QCAlgorithm, changes: SecurityChanges ### Emits portfolio targets setting the quantity to the weight of the constituent ### in its respective ETF. ### - def create_targets(self, algorithm: QCAlgorithm, insights: List[Insight]): + def create_targets(self, algorithm: QCAlgorithm, insights: list[Insight]) -> list[PortfolioTarget]: if not self.has_added: return [] @@ -90,7 +90,7 @@ class ETFConstituentExecutionModel(ExecutionModel): ### ### Liquidates if constituents have been removed from the universe ### - def on_securities_changed(self, algorithm: QCAlgorithm, changes: SecurityChanges): + def on_securities_changed(self, algorithm: QCAlgorithm, changes: SecurityChanges) -> None: for change in changes.removed_securities: algorithm.liquidate(change.symbol) @@ -100,7 +100,7 @@ def on_securities_changed(self, algorithm: QCAlgorithm, changes: SecurityChanges ### resulting algorithm portfolio weight might not be equal ### to the leverage of the ETF (1x, 2x, 3x, etc.) ### - def execute(self, algorithm: QCAlgorithm, targets: List[IPortfolioTarget]): + def execute(self, algorithm: QCAlgorithm, targets: list[IPortfolioTarget]) -> None: for target in targets: algorithm.set_holdings(target.symbol, target.quantity) @@ -111,7 +111,7 @@ class ETFConstituentUniverseFrameworkRegressionAlgorithm(QCAlgorithm): ### ### Initializes the algorithm, setting up the framework classes and ETF constituent universe settings ### - def initialize(self): + def initialize(self) -> None: self.set_start_date(2020, 12, 1) self.set_end_date(2021, 1, 31) self.set_cash(100000) @@ -120,30 +120,22 @@ def initialize(self): self.set_portfolio_construction(ETFConstituentPortfolioModel()) self.set_execution(ETFConstituentExecutionModel()) - spy = Symbol.create("SPY", SecurityType.EQUITY, Market.USA) - self.universe_settings.resolution = Resolution.HOUR - universe = self.add_universe(self.universe.etf(spy, self.universe_settings, self.filter_etf_constituents)) + universe = self.add_universe(self.universe.etf("SPY", self.universe_settings, self.filter_etf_constituents)) historical_data = self.history(universe, 1, flatten=True) if len(historical_data) < 200: - raise ValueError(f"Unexpected universe DataCollection count {len(historical_data)}! Expected > 200") + raise ValueError(f"Unexpected universe DataCollection count {len(historical_data)}! Expected > 200") ### ### Filters ETF constituents ### ### ETF constituents ### ETF constituent Symbols that we want to include in the algorithm - def filter_etf_constituents(self, constituents): + def filter_etf_constituents(self, constituents: list[ETFConstituentUniverse]) -> list[Symbol]: global constituent_data - constituent_data_local = [i for i in constituents if i is not None and i.weight >= 0.001] + constituent_data_local = [i for i in constituents if i.weight and i.weight >= 0.001] constituent_data = list(constituent_data_local) return [i.symbol for i in constituent_data_local] - - ### - ### no-op for performance - ### - def on_data(self, data): - pass diff --git a/Algorithm.Python/ExpiryHelperAlphaModelFrameworkAlgorithm.py b/Algorithm.Python/ExpiryHelperAlphaModelFrameworkAlgorithm.py index 501d0c0aa132..81f85ef5a7d3 100644 --- a/Algorithm.Python/ExpiryHelperAlphaModelFrameworkAlgorithm.py +++ b/Algorithm.Python/ExpiryHelperAlphaModelFrameworkAlgorithm.py @@ -19,7 +19,7 @@ class ExpiryHelperAlphaModelFrameworkAlgorithm(QCAlgorithm): '''Expiry Helper framework algorithm uses Expiry helper class in an Alpha Model''' - def initialize(self): + def initialize(self) -> None: ''' Initialise the data and resolution required, as well as the cash and start-end dates for your algorithm. All algorithms must initialized.''' # Set requested data resolution @@ -40,23 +40,22 @@ def initialize(self): self.insights_generated += self.on_insights_generated - def on_insights_generated(self, s, e): + def on_insights_generated(self, s: IAlgorithm, e: GeneratedInsightsCollection) -> None: for insight in e.insights: self.log(f"{e.date_time_utc.isoweekday()}: Close Time {insight.close_time_utc} {insight.close_time_utc.isoweekday()}") class ExpiryHelperAlphaModel(AlphaModel): - next_update = None - direction = InsightDirection.UP + _next_update = None + _direction = InsightDirection.UP - def update(self, algorithm, data): - - if self.next_update is not None and self.next_update > algorithm.time: + def update(self, algorithm: QCAlgorithm, data: Slice) -> list[Insight]: + if self._next_update and self._next_update > algorithm.time: return [] expiry = Expiry.END_OF_DAY # Use the Expiry helper to calculate a date/time in the future - self.next_update = expiry(algorithm.time) + self._next_update = expiry(algorithm.time) weekday = algorithm.time.isoweekday() @@ -64,15 +63,15 @@ def update(self, algorithm, data): for symbol in data.bars.keys(): # Expected CloseTime: next month on the same day and time if weekday == 1: - insights.append(Insight.price(symbol, Expiry.ONE_MONTH, self.direction)) + insights.append(Insight.price(symbol, Expiry.ONE_MONTH, self._direction)) # Expected CloseTime: next month on the 1st at market open time elif weekday == 2: - insights.append(Insight.price(symbol, Expiry.END_OF_MONTH, self.direction)) + insights.append(Insight.price(symbol, Expiry.END_OF_MONTH, self._direction)) # Expected CloseTime: next Monday at market open time elif weekday == 3: - insights.append(Insight.price(symbol, Expiry.END_OF_WEEK, self.direction)) + insights.append(Insight.price(symbol, Expiry.END_OF_WEEK, self._direction)) # Expected CloseTime: next day (Friday) at market open time elif weekday == 4: - insights.append(Insight.price(symbol, Expiry.END_OF_DAY, self.direction)) + insights.append(Insight.price(symbol, Expiry.END_OF_DAY, self._direction)) return insights diff --git a/Algorithm.Python/FilteredIdentityAlgorithm.py b/Algorithm.Python/FilteredIdentityAlgorithm.py index a68cea2ae8bb..1f2ba211c274 100644 --- a/Algorithm.Python/FilteredIdentityAlgorithm.py +++ b/Algorithm.Python/FilteredIdentityAlgorithm.py @@ -32,7 +32,7 @@ def initialize(self): security = self.add_forex("EURUSD", Resolution.TICK) self._symbol = security.symbol - self.identity = self.filtered_identity(self._symbol, None, self.filter) + self._identity = self.filtered_identity(self._symbol, filter=self.filter) def filter(self, data): '''Filter function: True if data is not an instance of Tick. If it is, true if TickType is Trade @@ -44,7 +44,7 @@ def filter(self, data): def on_data(self, data): # Since we are only accepting TickType.TRADE, # this indicator will never be ready - if not self.identity.is_ready: return + if not self._identity.is_ready: return if not self.portfolio.invested: self.set_holdings(self._symbol, 1) self.debug("Purchased Stock") diff --git a/Algorithm.Python/FutureStopMarketOrderOnExtendedHoursRegressionAlgorithm.py b/Algorithm.Python/FutureStopMarketOrderOnExtendedHoursRegressionAlgorithm.py index 6cf6ace3e7ea..63389bfa0a47 100644 --- a/Algorithm.Python/FutureStopMarketOrderOnExtendedHoursRegressionAlgorithm.py +++ b/Algorithm.Python/FutureStopMarketOrderOnExtendedHoursRegressionAlgorithm.py @@ -12,7 +12,6 @@ # limitations under the License. from AlgorithmImports import * -from QuantConnect import Orders # # This example demonstrates how to create future 'stop_market_order' in extended Market Hours time @@ -20,54 +19,50 @@ class FutureStopMarketOrderOnExtendedHoursRegressionAlgorithm(QCAlgorithm): # Keep new created instance of stop_market_order - stop_market_ticket = None - sp_500_e_mini = None - + _stop_market_ticket = None + # Initialize the Algorithm and Prepare Required Data - def initialize(self): + def initialize(self) -> None: self.set_start_date(2013, 10, 6) self.set_end_date(2013, 10, 12) # Add mini SP500 future with extended Market hours flag - self.sp_500_e_mini = self.add_future(Futures.Indices.SP_500_E_MINI, Resolution.MINUTE, extended_market_hours=True) + self._sp_500_e_mini = self.add_future(Futures.Indices.SP_500_E_MINI, Resolution.MINUTE, extended_market_hours=True) # Init new schedule event with params: every_day, 19:00:00 PM, what should to do self.schedule.on(self.date_rules.every_day(),self.time_rules.at(19, 0),self.make_market_and_stop_market_order) # This method is opened 2 new orders by scheduler - def make_market_and_stop_market_order(self): + def make_market_and_stop_market_order(self) -> None: # Don't place orders at the end of the last date, the market-on-stop order won't have time to fill - if self.time.date() == self.end_date.date() - timedelta(days=1): + if self.time.date() == self.end_date.date() - timedelta(1) or not self._sp_500_e_mini.mapped: return - self.market_order(self.sp_500_e_mini.mapped, 1) - self.stop_market_ticket = self.stop_market_order(self.sp_500_e_mini.mapped, -1, self.sp_500_e_mini.price * 1.1) + self.market_order(self._sp_500_e_mini.mapped, 1) + self._stop_market_ticket = self.stop_market_order(self._sp_500_e_mini.mapped, -1, self._sp_500_e_mini.price * 1.1) # New Data Event handler receiving all subscription data in a single event - def on_data(self, slice): - if (self.stop_market_ticket == None or self.stop_market_ticket.status != OrderStatus.SUBMITTED): + def on_data(self, slice: Slice) -> None: + if (self._stop_market_ticket == None or self._stop_market_ticket.status != OrderStatus.SUBMITTED): return None - self.stop_price = self.stop_market_ticket.get(OrderField.STOP_PRICE) - self.bar = self.securities[self.stop_market_ticket.symbol].cache.get_data() + self.stop_price = self._stop_market_ticket.get(OrderField.STOP_PRICE) + self.bar = self.securities[self._stop_market_ticket.symbol].cache.get_data() # An order fill update the resulting information is passed to this method. - def on_order_event(self, order_event): - if order_event is None: - return None - + def on_order_event(self, order_event: OrderEvent) -> None: if self.transactions.get_order_by_id(order_event.order_id).type is not OrderType.STOP_MARKET: return None if order_event.status == OrderStatus.FILLED: # Get Exchange Hours for specific security - exchange_hours = self.market_hours_database.get_exchange_hours(self.sp_500_e_mini.subscription_data_config) + exchange_hours = self.market_hours_database.get_exchange_hours(self._sp_500_e_mini.subscription_data_config) # Validate, Exchange is opened explicitly - if (not exchange_hours.is_open(order_event.utc_time, self.sp_500_e_mini.is_extended_market_hours)): + if (not exchange_hours.is_open(order_event.utc_time, self._sp_500_e_mini.is_extended_market_hours)): raise AssertionError("The Exchange hours was closed, verify 'extended_market_hours' flag in Initialize() when added new security(ies)") - def on_end_of_algorithm(self): + def on_end_of_algorithm(self) -> None: self.stop_market_orders = self.transactions.get_orders(lambda o: o.type is OrderType.STOP_MARKET) for o in self.stop_market_orders: diff --git a/Algorithm.Python/IndicatorWithRenkoBarsRegressionAlgorithm.py b/Algorithm.Python/IndicatorWithRenkoBarsRegressionAlgorithm.py index 9ac5d7d878f6..f29237f03a70 100644 --- a/Algorithm.Python/IndicatorWithRenkoBarsRegressionAlgorithm.py +++ b/Algorithm.Python/IndicatorWithRenkoBarsRegressionAlgorithm.py @@ -22,7 +22,7 @@ ### class IndicatorWithRenkoBarsRegressionAlgorithm(QCAlgorithm): - def initialize(self): + def initialize(self) -> None: self.set_start_date(2013, 10, 7) self.set_end_date(2013, 10, 9) @@ -30,10 +30,10 @@ def initialize(self): self.add_equity("AIG") spy_renko_consolidator = RenkoConsolidator(0.1) - spy_renko_consolidator.data_consolidated += self.on_s_p_y_data_consolidated + spy_renko_consolidator.data_consolidated += self.on_spy_data_consolidated aig_renko_consolidator = RenkoConsolidator(0.05) - aig_renko_consolidator.data_consolidated += self.on_a_i_g_data_consolidated + aig_renko_consolidator.data_consolidated += self.on_aig_data_consolidated self.subscription_manager.add_consolidator("SPY", spy_renko_consolidator) self.subscription_manager.add_consolidator("AIG", aig_renko_consolidator) @@ -44,16 +44,16 @@ def initialize(self): self._b = Beta("Beta", 3, "AIG", "SPY") self._indicators = [self._mi, self._wasi, self._wsi, self._b] - def on_s_p_y_data_consolidated(self, sender, renko_bar): + def on_spy_data_consolidated(self, sender: object, renko_bar: RenkoBar) -> None: self._mi.update(renko_bar) self._wasi.update(renko_bar) self._wsi.update(renko_bar) self._b.update(renko_bar) - def on_a_i_g_data_consolidated(self, sender, renko_bar): + def on_aig_data_consolidated(self, sender: object, renko_bar: RenkoBar) -> None: self._b.update(renko_bar) - def on_end_of_algorithm(self): + def on_end_of_algorithm(self) -> None: for indicator in self._indicators: if not indicator.is_ready: raise AssertionError(f"{indicator.name} indicator should be ready") diff --git a/Algorithm.Python/IronCondorStrategyAlgorithm.py b/Algorithm.Python/IronCondorStrategyAlgorithm.py index 0859c4643bf0..a41e7ea34700 100644 --- a/Algorithm.Python/IronCondorStrategyAlgorithm.py +++ b/Algorithm.Python/IronCondorStrategyAlgorithm.py @@ -26,18 +26,21 @@ class IronCondorStrategyAlgorithm(OptionStrategyFactoryMethodsBaseAlgorithm): def expected_orders_count(self) -> int: return 8 - def trade_strategy(self, chain: OptionChain, option_symbol: Symbol): + def trade_strategy(self, chain: OptionChain, option_symbol: Symbol) -> None: for expiry, group in itertools.groupby(chain, lambda x: x.expiry): contracts = sorted(group, key=lambda x: x.strike) - if len(contracts) < 4:continue + if len(contracts) < 4: + continue put_contracts = [x for x in contracts if x.right == OptionRight.PUT] - if len(put_contracts) < 2: continue + if len(put_contracts) < 2: + continue long_put_strike = put_contracts[0].strike short_put_strike = put_contracts[1].strike call_contracts = [x for x in contracts if x.right == OptionRight.CALL and x.strike > short_put_strike] - if len(call_contracts) < 2: continue + if len(call_contracts) < 2: + continue short_call_strike = call_contracts[0].strike long_call_strike = call_contracts[1].strike @@ -45,7 +48,7 @@ def trade_strategy(self, chain: OptionChain, option_symbol: Symbol): self.buy(self._iron_condor, 2) return - def assert_strategy_position_group(self, position_group: IPositionGroup, option_symbol: Symbol): + def assert_strategy_position_group(self, position_group: IPositionGroup, option_symbol: Symbol) -> None: positions = list(position_group.positions) if len(positions) != 4: raise AssertionError(f"Expected position group to have 4 positions. Actual: {len(positions)}") @@ -56,30 +59,30 @@ def assert_strategy_position_group(self, position_group: IPositionGroup, option_ long_put_position = next((x for x in position_group.positions if x.symbol.id.option_right == OptionRight.PUT and x.symbol.id.strike_price == long_put_strike), None) - if long_put_position is None or long_put_position.quantity != 2: + if not long_put_position or long_put_position.quantity != 2: raise AssertionError(f"Expected long put position quantity to be 2. Actual: {long_put_position.quantity}") short_put_strike = ordered_strikes[1] short_put_position = next((x for x in position_group.positions if x.symbol.id.option_right == OptionRight.PUT and x.symbol.id.strike_price == short_put_strike), None) - if short_put_position is None or short_put_position.quantity != -2: + if not short_put_position or short_put_position.quantity != -2: raise AssertionError(f"Expected short put position quantity to be -2. Actual: {short_put_position.quantity}") short_call_strike = ordered_strikes[2] short_call_position = next((x for x in position_group.positions if x.symbol.id.option_right == OptionRight.CALL and x.symbol.id.strike_price == short_call_strike), None) - if short_call_position is None or short_call_position.quantity != -2: + if not short_call_position or short_call_position.quantity != -2: raise AssertionError(f"Expected short call position quantity to be -2. Actual: {short_call_position.quantity}") long_call_strike = ordered_strikes[3] long_call_position = next((x for x in position_group.positions if x.symbol.id.option_right == OptionRight.CALL and x.symbol.id.strike_price == long_call_strike), None) - if long_call_position is None or long_call_position.quantity != 2: + if not long_call_position or long_call_position.quantity != 2: raise AssertionError(f"Expected long call position quantity to be 2. Actual: {long_call_position.quantity}") - def liquidate_strategy(self): + def liquidate_strategy(self) -> None: # We should be able to close the position by selling the strategy self.sell(self._iron_condor, 2) diff --git a/Algorithm.Python/KerasNeuralNetworkAlgorithm.py b/Algorithm.Python/KerasNeuralNetworkAlgorithm.py index eea55fe33e2a..ab837819fef4 100644 --- a/Algorithm.Python/KerasNeuralNetworkAlgorithm.py +++ b/Algorithm.Python/KerasNeuralNetworkAlgorithm.py @@ -15,17 +15,18 @@ from keras.models import * from tensorflow import keras +from keras import Sequential from keras.layers import Dense, Activation from keras.optimizers import SGD class KerasNeuralNetworkAlgorithm(QCAlgorithm): - def initialize(self): + def initialize(self) -> None: self.set_start_date(2019, 1, 1) # Set Start Date self.set_end_date(2020, 4, 1) # Set End Date self.set_cash(100000) # Set Strategy Cash - self.model_by_symbol = {} + self._model_by_symbol = {} for ticker in ["SPY", "QQQ", "TLT"]: symbol = self.add_equity(ticker).symbol @@ -33,14 +34,14 @@ def initialize(self): # Read the model saved in the ObjectStore for kvp in self.object_store: key = f'{symbol}_model' - if not key == kvp.key or kvp.value is None: + if not (key == kvp.key and kvp.value): continue file_path = self.object_store.get_file_path(kvp.key) - self.model_by_symbol[symbol] = keras.models.load_model(file_path) - self.debug(f'Model for {symbol} sucessfully retrieved. File {file_path}. Size {kvp.value.length}. Weights {self.model_by_symbol[symbol].get_weights()}') + self._model_by_symbol[symbol] = keras.models.load_model(file_path) + self.debug(f'Model for {symbol} sucessfully retrieved. File {file_path}. Size {len(kvp.value)}. Weights {self._model_by_symbol[symbol].get_weights()}') # Look-back period for training set - self.lookback = 30 + self._lookback = 30 # Train Neural Network every monday self.train( @@ -54,23 +55,21 @@ def initialize(self): self.time_rules.after_market_open("SPY", 30), self.trade) - - def on_end_of_algorithm(self): + def on_end_of_algorithm(self) -> None: ''' Save the data and the mode using the ObjectStore ''' - for symbol, model in self.model_by_symbol.items(): - key = f'{symbol}_model' + for symbol, model in self._model_by_symbol.items(): + key = f'{symbol}_model.keras' file = self.object_store.get_file_path(key) model.save(file) self.object_store.save(key) self.debug(f'Model for {symbol} sucessfully saved in the ObjectStore') - - def neural_network_training(self): + def neural_network_training(self) -> None: '''Train the Neural Network and save the model in the ObjectStore''' symbols = self.securities.keys() # Daily historical data is used to train the machine learning model - history = self.history(symbols, self.lookback + 1, Resolution.DAILY) + history = self.history(symbols, self._lookback + 1, Resolution.DAILY) history = history.open.unstack(0) for symbol in symbols: @@ -97,9 +96,9 @@ def neural_network_training(self): # training the model cost = model.train_on_batch(predictor, predictand) - self.model_by_symbol[symbol] = model + self._model_by_symbol[symbol] = model - def trade(self): + def trade(self) -> None: ''' Predict the price using the trained model and out-of-sample data Enter or exit positions based on relationship of the open price of the current bar and the prices defined by the machine learning model. @@ -107,10 +106,12 @@ def trade(self): ''' target = 1 / len(self.securities) - for symbol, model in self.model_by_symbol.items(): - + for symbol, model in self._model_by_symbol.items(): + if symbol not in self.current_slice.bars: + continue + # Get the out-of-sample history - history = self.history(symbol, self.lookback, Resolution.DAILY) + history = self.history(symbol, self._lookback, Resolution.DAILY) history = history.open.unstack(0)[symbol] # Get the final predicted price @@ -118,7 +119,7 @@ def trade(self): history_std = np.std(history) holding = self.portfolio[symbol] - open_price = self.current_slice[symbol].open + open_price = self.current_slice.bars[symbol].open # Follow the trend if holding.invested: diff --git a/Algorithm.Python/LongAndShortButterflyCallStrategiesAlgorithm.py b/Algorithm.Python/LongAndShortButterflyCallStrategiesAlgorithm.py index bc97b798a8d2..ff029a52815d 100644 --- a/Algorithm.Python/LongAndShortButterflyCallStrategiesAlgorithm.py +++ b/Algorithm.Python/LongAndShortButterflyCallStrategiesAlgorithm.py @@ -57,7 +57,7 @@ def assert_strategy_position_group(self, position_group: IPositionGroup, option_ if position.symbol.id.option_right == OptionRight.CALL and position.symbol.id.strike_price == higher_strike), None) - if higher_strike_position.quantity != 2: + if higher_strike_position and higher_strike_position.quantity != 2: raise AssertionError(f"Expected higher strike position quantity to be 2. Actual: {higher_strike_position.quantity}") lower_strike = min(leg.strike for leg in self._butterfly_call.option_legs) @@ -65,7 +65,7 @@ def assert_strategy_position_group(self, position_group: IPositionGroup, option_ if position.symbol.id.option_right == OptionRight.CALL and position.symbol.id.strike_price == lower_strike), None) - if lower_strike_position.quantity != 2: + if lower_strike_position and lower_strike_position.quantity != 2: raise AssertionError(f"Expected lower strike position quantity to be 2. Actual: {lower_strike_position.quantity}") middle_strike = [leg.strike for leg in self._butterfly_call.option_legs if leg.strike < higher_strike and leg.strike > lower_strike][0] @@ -73,7 +73,7 @@ def assert_strategy_position_group(self, position_group: IPositionGroup, option_ if position.symbol.id.option_right == OptionRight.CALL and position.symbol.id.strike_price == middle_strike), None) - if middle_strike_position.quantity != -4: + if middle_strike_position and middle_strike_position.quantity != -4: raise AssertionError(f"Expected middle strike position quantity to be -4. Actual: {middle_strike_position.quantity}") def liquidate_strategy(self): diff --git a/Algorithm.Python/LongAndShortButterflyPutStrategiesAlgorithm.py b/Algorithm.Python/LongAndShortButterflyPutStrategiesAlgorithm.py index b05c6756175d..3acd7806fa4a 100644 --- a/Algorithm.Python/LongAndShortButterflyPutStrategiesAlgorithm.py +++ b/Algorithm.Python/LongAndShortButterflyPutStrategiesAlgorithm.py @@ -26,7 +26,7 @@ class LongAndShortButterflyPutStrategiesAlgorithm(OptionStrategyFactoryMethodsBa def expected_orders_count(self) -> int: return 6 - def trade_strategy(self, chain: OptionChain, option_symbol: Symbol): + def trade_strategy(self, chain: OptionChain, option_symbol: Symbol) -> None: put_contracts = (contract for contract in chain if contract.right == OptionRight.PUT) for expiry, group in itertools.groupby(put_contracts, lambda x: x.expiry): @@ -47,7 +47,7 @@ def trade_strategy(self, chain: OptionChain, option_symbol: Symbol): self.buy(self._butterfly_put, 2) return - def assert_strategy_position_group(self, position_group: IPositionGroup, option_symbol: Symbol): + def assert_strategy_position_group(self, position_group: IPositionGroup, option_symbol: Symbol) -> None: positions = list(position_group.positions) if len(positions) != 3: raise AssertionError(f"Expected position group to have 3 positions. Actual: {len(positions)}") @@ -57,7 +57,7 @@ def assert_strategy_position_group(self, position_group: IPositionGroup, option_ if position.symbol.id.option_right == OptionRight.PUT and position.symbol.id.strike_price == higher_strike), None) - if higher_strike_position.quantity != 2: + if not higher_strike_position or higher_strike_position.quantity != 2: raise AssertionError(f"Expected higher strike position quantity to be 2. Actual: {higher_strike_position.quantity}") lower_strike = min(leg.strike for leg in self._butterfly_put.option_legs) @@ -65,7 +65,7 @@ def assert_strategy_position_group(self, position_group: IPositionGroup, option_ if position.symbol.id.option_right == OptionRight.PUT and position.symbol.id.strike_price == lower_strike), None) - if lower_strike_position.quantity != 2: + if not lower_strike_position or lower_strike_position.quantity != 2: raise AssertionError(f"Expected lower strike position quantity to be 2. Actual: {lower_strike_position.quantity}") middle_strike = [leg.strike for leg in self._butterfly_put.option_legs if leg.strike < higher_strike and leg.strike > lower_strike][0] @@ -73,9 +73,9 @@ def assert_strategy_position_group(self, position_group: IPositionGroup, option_ if position.symbol.id.option_right == OptionRight.PUT and position.symbol.id.strike_price == middle_strike), None) - if middle_strike_position.quantity != -4: + if not middle_strike_position or middle_strike_position.quantity != -4: raise AssertionError(f"Expected middle strike position quantity to be -4. Actual: {middle_strike_position.quantity}") - def liquidate_strategy(self): + def liquidate_strategy(self) -> None: # We should be able to close the position using the inverse strategy (a short butterfly put) self.buy(self._short_butterfly_put, 2); diff --git a/Algorithm.Python/LongAndShortCallCalendarSpreadStrategiesAlgorithm.py b/Algorithm.Python/LongAndShortCallCalendarSpreadStrategiesAlgorithm.py index c5743feeba9c..e2b962a67105 100644 --- a/Algorithm.Python/LongAndShortCallCalendarSpreadStrategiesAlgorithm.py +++ b/Algorithm.Python/LongAndShortCallCalendarSpreadStrategiesAlgorithm.py @@ -26,12 +26,13 @@ class LongAndShortCallCalendarSpreadStrategiesAlgorithm(OptionStrategyFactoryMet def expected_orders_count(self) -> int: return 4 - def trade_strategy(self, chain: OptionChain, option_symbol: Symbol): + def trade_strategy(self, chain: OptionChain, option_symbol: Symbol) -> None: call_contracts = sorted((contract for contract in chain if contract.right == OptionRight.CALL), key=lambda x: abs(x.strike - chain.underlying.value)) for strike, group in itertools.groupby(call_contracts, lambda x: x.strike): contracts = sorted(group, key=lambda x: x.expiry) - if len(contracts) < 2: continue + if len(contracts) < 2: + continue self._near_expiration = contracts[0].expiry self._far_expiration = contracts[1].expiry @@ -41,7 +42,7 @@ def trade_strategy(self, chain: OptionChain, option_symbol: Symbol): self.buy(self._call_calendar_spread, 2) return - def assert_strategy_position_group(self, position_group: IPositionGroup, option_symbol: Symbol): + def assert_strategy_position_group(self, position_group: IPositionGroup, option_symbol: Symbol) -> None: positions = list(position_group.positions) if len(positions) != 2: raise AssertionError(f"Expected position group to have 2 positions. Actual: {len(positions)}") @@ -49,15 +50,15 @@ def assert_strategy_position_group(self, position_group: IPositionGroup, option_ near_expiration_position = next((position for position in positions if position.symbol.id.option_right == OptionRight.CALL and position.symbol.id.date == self._near_expiration), None) - if near_expiration_position is None or near_expiration_position.quantity != -2: + if not near_expiration_position or near_expiration_position.quantity != -2: raise AssertionError(f"Expected near expiration position to be -2. Actual: {near_expiration_position.quantity}") far_expiration_position = next((position for position in positions if position.symbol.id.option_right == OptionRight.CALL and position.symbol.id.date == self._far_expiration), None) - if far_expiration_position is None or far_expiration_position.quantity != 2: + if not far_expiration_position or far_expiration_position.quantity != 2: raise AssertionError(f"Expected far expiration position to be 2. Actual: {far_expiration_position.quantity}") - def liquidate_strategy(self): + def liquidate_strategy(self) -> None: # We should be able to close the position using the inverse strategy (a short call calendar spread) self.buy(self._short_call_calendar_spread, 2) diff --git a/Algorithm.Python/LongAndShortPutCalendarSpreadStrategiesAlgorithm.py b/Algorithm.Python/LongAndShortPutCalendarSpreadStrategiesAlgorithm.py index fd2253d4fc4e..b41fe06805f6 100644 --- a/Algorithm.Python/LongAndShortPutCalendarSpreadStrategiesAlgorithm.py +++ b/Algorithm.Python/LongAndShortPutCalendarSpreadStrategiesAlgorithm.py @@ -26,12 +26,13 @@ class LongAndShortPutCalendarSpreadStrategiesAlgorithm(OptionStrategyFactoryMeth def expected_orders_count(self) -> int: return 4 - def trade_strategy(self, chain: OptionChain, option_symbol: Symbol): + def trade_strategy(self, chain: OptionChain, option_symbol: Symbol) -> None: put_contracts = sorted((contract for contract in chain if contract.right == OptionRight.PUT), key=lambda x: abs(x.strike - chain.underlying.value)) for strike, group in itertools.groupby(put_contracts, lambda x: x.strike): contracts = sorted(group, key=lambda x: x.expiry) - if len(contracts) < 2: continue + if len(contracts) < 2: + continue self._near_expiration = contracts[0].expiry self._far_expiration = contracts[1].expiry @@ -41,7 +42,7 @@ def trade_strategy(self, chain: OptionChain, option_symbol: Symbol): self.buy(self._put_calendar_spread, 2) return - def assert_strategy_position_group(self, position_group: IPositionGroup, option_symbol: Symbol): + def assert_strategy_position_group(self, position_group: IPositionGroup, option_symbol: Symbol) -> None: positions = list(position_group.positions) if len(positions) != 2: raise AssertionError(f"Expected position group to have 2 positions. Actual: {len(positions)}") @@ -49,15 +50,15 @@ def assert_strategy_position_group(self, position_group: IPositionGroup, option_ near_expiration_position = next((position for position in positions if position.symbol.id.option_right == OptionRight.PUT and position.symbol.id.date == self._near_expiration), None) - if near_expiration_position is None or near_expiration_position.quantity != -2: + if not near_expiration_position or near_expiration_position.quantity != -2: raise AssertionError(f"Expected near expiration position to be -2. Actual: {near_expiration_position.quantity}") far_expiration_position = next((position for position in positions if position.symbol.id.option_right == OptionRight.PUT and position.symbol.id.date == self._far_expiration), None) - if far_expiration_position is None or far_expiration_position.quantity != 2: + if not far_expiration_position or far_expiration_position.quantity != 2: raise AssertionError(f"Expected far expiration position to be 2. Actual: {far_expiration_position.quantity}") - def liquidate_strategy(self): + def liquidate_strategy(self) -> None: # We should be able to close the position using the inverse strategy (a short put calendar spread) self.buy(self._short_put_calendar_spread, 2) diff --git a/Algorithm.Python/MarketOnCloseOrderBufferExtendedMarketHoursRegressionAlgorithm.py b/Algorithm.Python/MarketOnCloseOrderBufferExtendedMarketHoursRegressionAlgorithm.py index c4a918a77b94..96f6293fe95c 100644 --- a/Algorithm.Python/MarketOnCloseOrderBufferExtendedMarketHoursRegressionAlgorithm.py +++ b/Algorithm.Python/MarketOnCloseOrderBufferExtendedMarketHoursRegressionAlgorithm.py @@ -16,50 +16,50 @@ class MarketOnCloseOrderBufferExtendedMarketHoursRegressionAlgorithm(QCAlgorithm): '''This regression test is a version of "MarketOnCloseOrderBufferRegressionAlgorithm" where we test market-on-close modeling with data from the post market.''' - valid_order_ticket = None - invalid_order_ticket = None - valid_order_ticket_extended_market_hours = None + _valid_order_ticket = None + _invalid_order_ticket = None + _valid_order_ticket_extended_market_hours = None - def initialize(self): + def initialize(self) -> None: self.set_start_date(2013,10,7) #Set Start Date self.set_end_date(2013,10,8) #Set End Date self.add_equity("SPY", Resolution.MINUTE, extended_market_hours = True) def moc_at_mid_night(): - self.valid_order_ticket_at_midnight = self.market_on_close_order("SPY", 2) + self._valid_order_ticket_at_midnight = self.market_on_close_order("SPY", 2) self.schedule.on(self.date_rules.tomorrow, self.time_rules.midnight, moc_at_mid_night) # Modify our submission buffer time to 10 minutes MarketOnCloseOrder.submission_time_buffer = timedelta(minutes=10) - def on_data(self, data): + def on_data(self, data: Slice) -> None: # Test our ability to submit MarketOnCloseOrders # Because we set our buffer to 10 minutes, any order placed # before 3:50PM should be accepted, any after marked invalid # Will not throw an order error and execute - if self.time.hour == 15 and self.time.minute == 49 and not self.valid_order_ticket: - self.valid_order_ticket = self.market_on_close_order("SPY", 2) + if self.time.hour == 15 and self.time.minute == 49 and not self._valid_order_ticket: + self._valid_order_ticket = self.market_on_close_order("SPY", 2) # Will throw an order error and be marked invalid - if self.time.hour == 15 and self.time.minute == 51 and not self.invalid_order_ticket: - self.invalid_order_ticket = self.market_on_close_order("SPY", 2) + if self.time.hour == 15 and self.time.minute == 51 and not self._invalid_order_ticket: + self._invalid_order_ticket = self.market_on_close_order("SPY", 2) # Will not throw an order error and execute - if self.time.hour == 16 and self.time.minute == 48 and not self.valid_order_ticket_extended_market_hours: - self.valid_order_ticket_extended_market_hours = self.market_on_close_order("SPY", 2) + if self.time.hour == 16 and self.time.minute == 48 and not self._valid_order_ticket_extended_market_hours: + self._valid_order_ticket_extended_market_hours = self.market_on_close_order("SPY", 2) - def on_end_of_algorithm(self): + def on_end_of_algorithm(self) -> None: # Set it back to default for other regressions MarketOnCloseOrder.submission_time_buffer = MarketOnCloseOrder.DEFAULT_SUBMISSION_TIME_BUFFER - if self.valid_order_ticket.status != OrderStatus.FILLED: + if self._valid_order_ticket.status != OrderStatus.FILLED: raise AssertionError("Valid order failed to fill") - if self.invalid_order_ticket.status != OrderStatus.INVALID: + if self._invalid_order_ticket.status != OrderStatus.INVALID: raise AssertionError("Invalid order was not rejected") - if self.valid_order_ticket_extended_market_hours.status != OrderStatus.FILLED: + if self._valid_order_ticket_extended_market_hours.status != OrderStatus.FILLED: raise AssertionError("Valid order during extended market hours failed to fill") diff --git a/Algorithm.Python/MarketOnCloseOrderBufferRegressionAlgorithm.py b/Algorithm.Python/MarketOnCloseOrderBufferRegressionAlgorithm.py index caca8eeda9f3..4189f718adde 100644 --- a/Algorithm.Python/MarketOnCloseOrderBufferRegressionAlgorithm.py +++ b/Algorithm.Python/MarketOnCloseOrderBufferRegressionAlgorithm.py @@ -14,45 +14,45 @@ from AlgorithmImports import * class MarketOnCloseOrderBufferRegressionAlgorithm(QCAlgorithm): - valid_order_ticket = None - invalid_order_ticket = None + _valid_order_ticket = None + _invalid_order_ticket = None - def initialize(self): + def initialize(self) -> None: self.set_start_date(2013,10,7) #Set Start Date self.set_end_date(2013,10,8) #Set End Date self.add_equity("SPY", Resolution.MINUTE) def moc_at_post_market(): - self.valid_order_ticket_extended_market_hours = self.market_on_close_order("SPY", 2) + self._valid_order_ticket_extended_market_hours = self.market_on_close_order("SPY", 2) self.schedule.on(self.date_rules.today, self.time_rules.at(17,0), moc_at_post_market) # Modify our submission buffer time to 10 minutes MarketOnCloseOrder.submission_time_buffer = timedelta(minutes=10) - def on_data(self, data): + def on_data(self, data: Slice) -> None: # Test our ability to submit MarketOnCloseOrders # Because we set our buffer to 10 minutes, any order placed # before 3:50PM should be accepted, any after marked invalid # Will not throw an order error and execute - if self.time.hour == 15 and self.time.minute == 49 and not self.valid_order_ticket: - self.valid_order_ticket = self.market_on_close_order("SPY", 2) + if self.time.hour == 15 and self.time.minute == 49 and not self._valid_order_ticket: + self._valid_order_ticket = self.market_on_close_order("SPY", 2) # Will throw an order error and be marked invalid - if self.time.hour == 15 and self.time.minute == 51 and not self.invalid_order_ticket: - self.invalid_order_ticket = self.market_on_close_order("SPY", 2) + if self.time.hour == 15 and self.time.minute == 51 and not self._invalid_order_ticket: + self._invalid_order_ticket = self.market_on_close_order("SPY", 2) - def on_end_of_algorithm(self): + def on_end_of_algorithm(self) -> None: # Set it back to default for other regressions MarketOnCloseOrder.submission_time_buffer = MarketOnCloseOrder.DEFAULT_SUBMISSION_TIME_BUFFER - if self.valid_order_ticket.status != OrderStatus.FILLED: + if self._valid_order_ticket.status != OrderStatus.FILLED: raise AssertionError("Valid order failed to fill") - if self.invalid_order_ticket.status != OrderStatus.INVALID: + if self._invalid_order_ticket.status != OrderStatus.INVALID: raise AssertionError("Invalid order was not rejected") - if self.valid_order_ticket_extended_market_hours.status != OrderStatus.FILLED: + if self._valid_order_ticket_extended_market_hours.status != OrderStatus.FILLED: raise AssertionError("Valid order during extended market hours failed to fill") diff --git a/Algorithm.Python/MultipleSymbolConsolidationAlgorithm.py b/Algorithm.Python/MultipleSymbolConsolidationAlgorithm.py index c683930f8352..9b021b2e3aa4 100644 --- a/Algorithm.Python/MultipleSymbolConsolidationAlgorithm.py +++ b/Algorithm.Python/MultipleSymbolConsolidationAlgorithm.py @@ -23,8 +23,7 @@ class MultipleSymbolConsolidationAlgorithm(QCAlgorithm): # Initialise the data and resolution required, as well as the cash and start-end dates for your algorithm. All algorithms must initialized. - def initialize(self): - + def initialize(self) -> None: # This is the period of bars we'll be creating bar_period = TimeSpan.from_minutes(10) # This is the period of our sma indicators @@ -32,7 +31,7 @@ def initialize(self): # This is the number of consolidated bars we'll hold in symbol data for reference rolling_window_size = 10 # Holds all of our data keyed by each symbol - self.data = {} + self._data = {} # Contains all of our equity symbols equity_symbols = ["AAPL","SPY","IBM"] # Contains all of our forex symbols @@ -44,15 +43,15 @@ def initialize(self): # initialize our equity data for symbol in equity_symbols: equity = self.add_equity(symbol) - self.data[symbol] = SymbolData(equity.symbol, bar_period, rolling_window_size) + self._data[symbol] = SymbolData(equity.symbol, bar_period, rolling_window_size) # initialize our forex data for symbol in forex_symbols: forex = self.add_forex(symbol) - self.data[symbol] = SymbolData(forex.symbol, bar_period, rolling_window_size) + self._data[symbol] = SymbolData(forex.symbol, bar_period, rolling_window_size) # loop through all our symbols and request data subscriptions and initialize indicator - for symbol, symbol_data in self.data.items(): + for symbol, symbol_data in self._data.items(): # define the indicator symbol_data.sma = SimpleMovingAverage(self.create_indicator_name(symbol, "sma" + str(sma_period), Resolution.MINUTE), sma_period) # define a consolidator to consolidate data for this symbol on the requested period @@ -62,18 +61,16 @@ def initialize(self): # we need to add this consolidator so it gets auto updates self.subscription_manager.add_consolidator(symbol_data.symbol, consolidator) - def on_data_consolidated(self, sender, bar): - - self.data[bar.symbol.value].sma.update(bar.time, bar.close) - self.data[bar.symbol.value].bars.add(bar) + def on_data_consolidated(self, sender: object, bar: TradeBar) -> None: + self._data[bar.symbol.value].sma.update(bar.time, bar.close) + self._data[bar.symbol.value].bars.add(bar) # OnData event is the primary entry point for your algorithm. Each new data point will be pumped in here. # Argument "data": Slice object, dictionary object with your stock data - def on_data(self,data): - + def on_data(self, data: Slice) -> None: # loop through each symbol in our structure - for symbol in self.data.keys(): - symbol_data = self.data[symbol] + for symbol in self._data.keys(): + symbol_data = self._data[symbol] # this check proves that this symbol was JUST updated prior to this OnData function being called if symbol_data.is_ready() and symbol_data.was_just_updated(self.time): if not self.portfolio[symbol].invested: @@ -81,11 +78,10 @@ def on_data(self,data): # End of a trading day event handler. This method is called at the end of the algorithm day (or multiple times if trading multiple assets). # Method is called 10 minutes before closing to allow user to close out position. - def on_end_of_day(self, symbol): - + def on_end_of_day(self, symbol: Symbol) -> None: i = 0 - for symbol in sorted(self.data.keys()): - symbol_data = self.data[symbol] + for symbol in sorted(self._data.keys()): + symbol_data = self._data[symbol] # we have too many symbols to plot them all, so plot every other i += 1 if symbol_data.is_ready() and i%2 == 0: @@ -94,7 +90,7 @@ def on_end_of_day(self, symbol): class SymbolData(object): - def __init__(self, symbol, bar_period, window_size): + def __init__(self, symbol: Symbol, bar_period: timedelta, window_size: int) -> None: self._symbol = symbol # The period used when population the Bars rolling window self.bar_period = bar_period @@ -106,10 +102,10 @@ def __init__(self, symbol, bar_period, window_size): self.sma = None # Returns true if all the data in this instance is ready (indicators, rolling windows, ect...) - def is_ready(self): + def is_ready(self) -> bool: return self.bars.is_ready and self.sma.is_ready # Returns true if the most recent trade bar time matches the current time minus the bar's period, this # indicates that update was just called on this instance - def was_just_updated(self, current): + def was_just_updated(self, current: datetime) -> bool: return self.bars.count > 0 and self.bars[0].time == current - self.bar_period diff --git a/Algorithm.Python/NumeraiSignalExportDemonstrationAlgorithm.py b/Algorithm.Python/NumeraiSignalExportDemonstrationAlgorithm.py index 7f95dd3b7d3e..396216b73be3 100644 --- a/Algorithm.Python/NumeraiSignalExportDemonstrationAlgorithm.py +++ b/Algorithm.Python/NumeraiSignalExportDemonstrationAlgorithm.py @@ -23,9 +23,9 @@ ### class NumeraiSignalExportDemonstrationAlgorithm(QCAlgorithm): - securities = [] + _securities = [] - def initialize(self): + def initialize(self) -> None: ''' Initialize the date and add all equity symbols present in list _symbols ''' self.set_start_date(2020, 10, 7) #Set Start Date @@ -63,9 +63,9 @@ def initialize(self): self.signal_export.add_signal_export_provider(NumeraiSignalExport(numerai_public_id, numerai_secret_id, numerai_model_id, numerai_filename)) - def submit_signals(self): + def submit_signals(self) -> None: # Select the subset of ETF constituents we can trade - symbols = sorted([security.symbol for security in self.securities if security.has_data]) + symbols = sorted([security.symbol for security in self._securities if security.has_data]) if len(symbols) == 0: return @@ -89,7 +89,7 @@ def submit_signals(self): def on_securities_changed(self, changes: SecurityChanges) -> None: for security in changes.removed_securities: - if security in self.securities: - self.securities.remove(security) + if security in self._securities: + self._securities.remove(security) - self.securities.extend([security for security in changes.added_securities if security.symbol != self.etf_symbol]) + self._securities.extend([security for security in changes.added_securities if security.symbol != self.etf_symbol]) diff --git a/Algorithm.Python/OptionIndicatorsMirrorContractsRegressionAlgorithm.py b/Algorithm.Python/OptionIndicatorsMirrorContractsRegressionAlgorithm.py index 7cec26ecf135..5665f3c11886 100644 --- a/Algorithm.Python/OptionIndicatorsMirrorContractsRegressionAlgorithm.py +++ b/Algorithm.Python/OptionIndicatorsMirrorContractsRegressionAlgorithm.py @@ -16,7 +16,7 @@ class OptionIndicatorsMirrorContractsRegressionAlgorithm(QCAlgorithm): - def initialize(self): + def initialize(self) -> None: self.set_start_date(2014, 6, 5) self.set_end_date(2014, 6, 9) self.set_cash(100000) @@ -28,40 +28,40 @@ def initialize(self): mirror_option = Symbol.create_option("AAPL", Market.USA, OptionStyle.AMERICAN, OptionRight.CALL, 650, datetime(2014, 6, 21)) self.add_option_contract(mirror_option, Resolution.DAILY) - self.delta = self.d(option, mirror_option, option_model = OptionPricingModelType.BINOMIAL_COX_ROSS_RUBINSTEIN, iv_model = OptionPricingModelType.BLACK_SCHOLES) - self.gamma = self.g(option, mirror_option, option_model = OptionPricingModelType.FORWARD_TREE, iv_model = OptionPricingModelType.BLACK_SCHOLES) - self.vega = self.v(option, mirror_option, option_model = OptionPricingModelType.FORWARD_TREE, iv_model = OptionPricingModelType.BLACK_SCHOLES) - self.theta = self.t(option, mirror_option, option_model = OptionPricingModelType.FORWARD_TREE, iv_model = OptionPricingModelType.BLACK_SCHOLES) - self.rho = self.r(option, mirror_option, option_model = OptionPricingModelType.FORWARD_TREE, iv_model = OptionPricingModelType.BLACK_SCHOLES) + self._delta = self.d(option, mirror_option, option_model = OptionPricingModelType.BINOMIAL_COX_ROSS_RUBINSTEIN, iv_model = OptionPricingModelType.BLACK_SCHOLES) + self._gamma = self.g(option, mirror_option, option_model = OptionPricingModelType.FORWARD_TREE, iv_model = OptionPricingModelType.BLACK_SCHOLES) + self._vega = self.v(option, mirror_option, option_model = OptionPricingModelType.FORWARD_TREE, iv_model = OptionPricingModelType.BLACK_SCHOLES) + self._theta = self.t(option, mirror_option, option_model = OptionPricingModelType.FORWARD_TREE, iv_model = OptionPricingModelType.BLACK_SCHOLES) + self._rho = self.r(option, mirror_option, option_model = OptionPricingModelType.FORWARD_TREE, iv_model = OptionPricingModelType.BLACK_SCHOLES) # A custom IV indicator with custom calculation of IV risk_free_rate_model = InterestRateProvider() dividend_yield_model = DividendYieldProvider(equity) - self.implied_volatility = CustomImpliedVolatility(option, mirror_option, risk_free_rate_model, dividend_yield_model) - self.register_indicator(option, self.implied_volatility, QuoteBarConsolidator(timedelta(1))) - self.register_indicator(mirror_option, self.implied_volatility, QuoteBarConsolidator(timedelta(1))) - self.register_indicator(equity, self.implied_volatility, TradeBarConsolidator(timedelta(1))) + self._implied_volatility = CustomImpliedVolatility(option, mirror_option, risk_free_rate_model, dividend_yield_model) + self.register_indicator(option, self._implied_volatility, QuoteBarConsolidator(timedelta(1))) + self.register_indicator(mirror_option, self._implied_volatility, QuoteBarConsolidator(timedelta(1))) + self.register_indicator(equity, self._implied_volatility, TradeBarConsolidator(timedelta(1))) # custom IV smoothing function: assume the lower IV is more "fair" smoothing_func = lambda iv, mirror_iv: min(iv, mirror_iv) # set the smoothing function - self.delta.implied_volatility.set_smoothing_function(smoothing_func) - self.gamma.implied_volatility.set_smoothing_function(smoothing_func) - self.vega.implied_volatility.set_smoothing_function(smoothing_func) - self.theta.implied_volatility.set_smoothing_function(smoothing_func) - self.rho.implied_volatility.set_smoothing_function(smoothing_func) + self._delta.implied_volatility.set_smoothing_function(smoothing_func) + self._gamma.implied_volatility.set_smoothing_function(smoothing_func) + self._vega.implied_volatility.set_smoothing_function(smoothing_func) + self._theta.implied_volatility.set_smoothing_function(smoothing_func) + self._rho.implied_volatility.set_smoothing_function(smoothing_func) - def on_end_of_algorithm(self): - if not self.implied_volatility.is_ready or not self.delta.is_ready or not self.gamma.is_ready \ - or not self.vega.is_ready or not self.theta.is_ready or not self.rho.is_ready: + def on_end_of_algorithm(self) -> None: + if not self._implied_volatility.is_ready or not self._delta.is_ready or not self._gamma.is_ready \ + or not self._vega.is_ready or not self._theta.is_ready or not self._rho.is_ready: raise AssertionError("Expected IV/greeks calculated") - self.debug(f"""Implied Volatility: {self.implied_volatility.current.value}, -Delta: {self.delta.current.value}, -Gamma: {self.gamma.current.value}, -Vega: {self.vega.current.value}, -Theta: {self.theta.current.value}, -Rho: {self.rho.current.value}""") + self.debug(f"""Implied Volatility: {self._implied_volatility.current.value}, +Delta: {self._delta.current.value}, +Gamma: {self._gamma.current.value}, +Vega: {self._vega.current.value}, +Theta: {self._theta.current.value}, +Rho: {self._rho.current.value}""") class CustomImpliedVolatility(ImpliedVolatility): def __init__(self, option, mirror_option, risk_free_rate_model, dividend_yield_model): @@ -78,7 +78,7 @@ def calculate_iv(self, time_till_expiry: float) -> float: # we demonstate put-call parity calculation here, but note that it is not suitable for American options def f(self, vol: float, time_till_expiry: float) -> float: call_black_price = OptionGreekIndicatorsHelper.black_theoretical_price( - vol, UnderlyingPrice.current.value, self.strike, time_till_expiry, RiskFreeRate.current.value, DividendYield.current.value, OptionRight.CALL) + vol, self.underlying_price.current.value, self.strike, time_till_expiry, self.risk_free_rate.current.value, self.dividend_yield.current.value, OptionRight.CALL) put_black_price = OptionGreekIndicatorsHelper.black_theoretical_price( - vol, UnderlyingPrice.current.value, self.strike, time_till_expiry, RiskFreeRate.current.value, DividendYield.current.value, OptionRight.PUT) - return Price.current.value + OppositePrice.current.value - call_black_price - put_black_price + vol, self.underlying_price.current.value, self.strike, time_till_expiry, self.risk_free_rate.current.value, self.dividend_yield.current.value, OptionRight.PUT) + return self.price.current.value + self.opposite_price.current.value - call_black_price - put_black_price diff --git a/Algorithm.Python/OptionPriceModelForOptionStylesBaseRegressionAlgorithm.py b/Algorithm.Python/OptionPriceModelForOptionStylesBaseRegressionAlgorithm.py index cd68548d9377..f5fb6639e007 100644 --- a/Algorithm.Python/OptionPriceModelForOptionStylesBaseRegressionAlgorithm.py +++ b/Algorithm.Python/OptionPriceModelForOptionStylesBaseRegressionAlgorithm.py @@ -18,36 +18,39 @@ ### or might not support them. Also, if the option style is supported, greeks are asserted to be accesible and have valid values. ### class OptionPriceModelForOptionStylesBaseRegressionAlgorithm(QCAlgorithm): - def __init__(self): + def __init__(self) -> None: super().__init__() self._option_style_is_supported = False self._check_greeks = True self._tried_greeks_calculation = False self._option = None - def on_data(self, slice): - if self.is_warming_up: return + def on_data(self, slice: Slice) -> None: + if self.is_warming_up: + return for kvp in slice.option_chains: - if self._option is None or kvp.key != self._option.symbol: continue + if not self._option or kvp.key != self._option.symbol: + continue self.check_greeks([contract for contract in kvp.value]) - def on_end_of_day(self, symbol): + def on_end_of_day(self, symbol: Symbol) -> None: self._check_greeks = True - def on_end_of_algorithm(self): + def on_end_of_algorithm(self) -> None: if not self._tried_greeks_calculation: raise AssertionError("Expected greeks to be accessed") - def init(self, option, option_style_is_supported): + def init(self, option: Option, option_style_is_supported: bool) -> None: self._option = option self._option_style_is_supported = option_style_is_supported self._check_greeks = True self._tried_greeks_calculation = False - def check_greeks(self, contracts): - if not self._check_greeks or len(contracts) == 0: return + def check_greeks(self, contracts: list[OptionContract]) -> None: + if not self._check_greeks or len(contracts) == 0 or not self._option: + return self._check_greeks = False self._tried_greeks_calculation = True @@ -70,7 +73,7 @@ def check_greeks(self, contracts): # Delta can be {-1, 0, 1} if the price is too wild, rho can be 0 if risk free rate is 0 # Vega can be 0 if the price is very off from theoretical price, Gamma = 0 if Delta belongs to {-1, 1} if (self._option_style_is_supported - and (greeks is None + and (not greeks or ((contract.right == OptionRight.CALL and (greeks.delta < 0.0 or greeks.delta > 1.0 or greeks.rho < 0.0)) or (contract.right == OptionRight.PUT and (greeks.delta < -1.0 or greeks.delta > 0.0 or greeks.rho > 0.0)) or greeks.theta == 0.0 or greeks.vega < 0.0 or greeks.gamma < 0.0))): diff --git a/Algorithm.Python/PytorchNeuralNetworkAlgorithm.py b/Algorithm.Python/PytorchNeuralNetworkAlgorithm.py index e7f11df96ab2..82481e1a36f6 100644 --- a/Algorithm.Python/PytorchNeuralNetworkAlgorithm.py +++ b/Algorithm.Python/PytorchNeuralNetworkAlgorithm.py @@ -17,7 +17,7 @@ class PytorchNeuralNetworkAlgorithm(QCAlgorithm): - def initialize(self): + def initialize(self) -> None: self.set_start_date(2013, 10, 7) # Set Start Date self.set_end_date(2013, 10, 8) # Set End Date @@ -27,14 +27,14 @@ def initialize(self): spy = self.add_equity("SPY", Resolution.MINUTE) self._symbols = [spy.symbol] # using a list can extend to condition for multiple symbols - self.lookback = 30 # days of historical data (look back) + self._lookback = 30 # days of historical data (look back) self.schedule.on(self.date_rules.every_day("SPY"), self.time_rules.after_market_open("SPY", 28), self.net_train) # train the NN self.schedule.on(self.date_rules.every_day("SPY"), self.time_rules.after_market_open("SPY", 30), self.trade) - def net_train(self): + def net_train(self) -> None: # Daily historical data is used to train the machine learning model - history = self.history(self._symbols, self.lookback + 1, Resolution.DAILY) + history = self.history(self._symbols, self._lookback + 1, Resolution.DAILY) # dicts that store prices for training self.prices_x = {} @@ -53,7 +53,6 @@ def net_train(self): for symbol in self._symbols: # if this symbol has historical data if symbol in self.prices_x: - net = Net(n_feature=1, n_hidden=10, n_output=1) # define the network optimizer = torch.optim.SGD(net.parameters(), lr=0.2) loss_func = torch.nn.MSELoss() # this is for regression mean squared loss @@ -79,28 +78,28 @@ def net_train(self): self.buy_prices[symbol] = net(y)[-1] + np.std(y.data.numpy()) self.sell_prices[symbol] = net(y)[-1] - np.std(y.data.numpy()) - def trade(self): + def trade(self) -> None: ''' Enter or exit positions based on relationship of the open price of the current bar and the prices defined by the machine learning model. Liquidate if the open price is below the sell price and buy if the open price is above the buy price ''' for holding in self.portfolio.values(): - if self.current_slice[holding.symbol].open < self.sell_prices[holding.symbol] and holding.invested: + bar = self.current_slice.bars.get(holding.symbol, None) + if bar and bar.open < self.sell_prices[holding.symbol] and holding.invested: self.liquidate(holding.symbol) - - if self.current_slice[holding.symbol].open > self.buy_prices[holding.symbol] and not holding.invested: + elif bar and bar.open > self.buy_prices[holding.symbol] and not holding.invested: self.set_holdings(holding.symbol, 1 / len(self._symbols)) # class for Pytorch NN model class Net(torch.nn.Module): - def __init__(self, n_feature, n_hidden, n_output): + def __init__(self, n_feature: int, n_hidden: int, n_output: int) -> None: super(Net, self).__init__() self.hidden = torch.nn.Linear(n_feature, n_hidden) # hidden layer self.predict = torch.nn.Linear(n_hidden, n_output) # output layer - def forward(self, x): + def forward(self, x: torch.Tensor) -> torch.Tensor: x = F.relu(self.hidden(x)) # activation function for hidden layer x = self.predict(x) # linear output return x diff --git a/Algorithm.Python/RangeConsolidatorAlgorithm.py b/Algorithm.Python/RangeConsolidatorAlgorithm.py index 13842286b4c1..0fbb41acc991 100644 --- a/Algorithm.Python/RangeConsolidatorAlgorithm.py +++ b/Algorithm.Python/RangeConsolidatorAlgorithm.py @@ -17,35 +17,35 @@ ### Example algorithm of how to use RangeConsolidator ### class RangeConsolidatorAlgorithm(QCAlgorithm): - def get_resolution(self): + def get_resolution(self) -> Resolution: return Resolution.DAILY - def get_range(self): + def get_range(self) -> int: return 100 - def initialize(self): + def initialize(self) -> None: self.set_start_and_end_dates(); self.add_equity("SPY", self.get_resolution()) range_consolidator = self.create_range_consolidator() range_consolidator.data_consolidated += self.on_data_consolidated - self.first_data_consolidated = None; + self._first_data_consolidated = None; self.subscription_manager.add_consolidator("SPY", range_consolidator) - def set_start_and_end_dates(self): + def set_start_and_end_dates(self) -> None: self.set_start_date(2013, 10, 7) self.set_end_date(2013, 10, 11) - def on_end_of_algorithm(self): - if self.first_data_consolidated == None: + def on_end_of_algorithm(self) -> None: + if not self._first_data_consolidated: raise AssertionError("The consolidator should have consolidated at least one RangeBar, but it did not consolidated any one") - def create_range_consolidator(self): + def create_range_consolidator(self) -> RangeConsolidator: return RangeConsolidator(self.get_range()) - def on_data_consolidated(self, sender, range_bar): - if (self.first_data_consolidated is None): - self.first_data_consolidated = range_bar + def on_data_consolidated(self, sender: object, range_bar: RangeBar) -> None: + if not self._first_data_consolidated: + self._first_data_consolidated = range_bar if round(range_bar.high - range_bar.low, 2) != self.get_range() * 0.01: # The minimum price change for SPY is 0.01, therefore the range size of each bar equals Range * 0.01 raise AssertionError(f"The difference between the High and Low for all RangeBar's should be {self.get_range() * 0.01}, but for this RangeBar was {round(range_bar.low - range_bar.high, 2)}") diff --git a/Algorithm.Python/RollingWindowAlgorithm.py b/Algorithm.Python/RollingWindowAlgorithm.py index e3f8770bab64..15635b783c26 100644 --- a/Algorithm.Python/RollingWindowAlgorithm.py +++ b/Algorithm.Python/RollingWindowAlgorithm.py @@ -34,35 +34,35 @@ def initialize(self): self.add_equity("SPY", Resolution.DAILY) # Creates a Rolling Window indicator to keep the 2 TradeBar - self.window = RollingWindow[TradeBar](2) # For other security types, use QuoteBar + self._window = RollingWindow[TradeBar](2) # For other security types, use QuoteBar # Creates an indicator and adds to a rolling window when it is updated - self.sma = self.SMA("SPY", 5) - self.sma.updated += self.sma_updated - self.sma_win = RollingWindow[IndicatorDataPoint](5) + self._sma = self.sma("SPY", 5) + self._sma.updated += self._sma_updated + self._sma_win = RollingWindow[IndicatorDataPoint](5) - def sma_updated(self, sender, updated): + def _sma_updated(self, sender, updated): '''Adds updated values to rolling window''' - self.sma_win.add(updated) + self._sma_win.add(updated) def on_data(self, data): '''OnData event is the primary entry point for your algorithm. Each new data point will be pumped in here.''' # Add SPY TradeBar in rollling window - self.window.add(data["SPY"]) + self._window.add(data["SPY"]) # Wait for windows to be ready. - if not (self.window.is_ready and self.sma_win.is_ready): return + if not (self._window.is_ready and self._sma_win.is_ready): return - curr_bar = self.window[0] # Current bar had index zero. - past_bar = self.window[1] # Past bar has index one. - self.log("Price: {0} -> {1} ... {2} -> {3}".format(past_bar.time, past_bar.close, curr_bar.time, curr_bar.close)) + curr_bar = self._window[0] # Current bar had index zero. + past_bar = self._window[1] # Past bar has index one. + self.log(f"Price: {past_bar.time} -> {past_bar.close} ... {curr_bar.time} -> {curr_bar.close}") - curr_sma = self.sma_win[0] # Current SMA had index zero. - past_sma = self.sma_win[self.sma_win.count-1] # Oldest SMA has index of window count minus 1. - self.log("SMA: {0} -> {1} ... {2} -> {3}".format(past_sma.time, past_sma.value, curr_sma.time, curr_sma.value)) + curr_sma = self._sma_win[0] # Current SMA had index zero. + past_sma = self._sma_win[self._sma_win.count-1] # Oldest SMA has index of window count minus 1. + self.log(f"SMA: {past_sma.time} -> {past_sma.value} ... {curr_sma.time} -> {curr_sma.value}") if not self.portfolio.invested and curr_sma.value > past_sma.value: self.set_holdings("SPY", 1) diff --git a/Algorithm.Python/ScheduledQueuingAlgorithm.py b/Algorithm.Python/ScheduledQueuingAlgorithm.py index d71d8fe7e9e4..d38436bbd3d0 100644 --- a/Algorithm.Python/ScheduledQueuingAlgorithm.py +++ b/Algorithm.Python/ScheduledQueuingAlgorithm.py @@ -16,7 +16,7 @@ class ScheduledQueuingAlgorithm(QCAlgorithm): - def initialize(self): + def initialize(self) -> None: self.set_start_date(2020, 9, 1) self.set_end_date(2020, 9, 2) self.set_cash(100000) @@ -29,33 +29,33 @@ def initialize(self): self.set_execution(ImmediateExecutionModel()) - self.queue = Queue() - self.dequeue_size = 100 + self._queue = Queue() + self._dequeue_size = 100 self.add_equity("SPY", Resolution.MINUTE) self.schedule.on(self.date_rules.every_day("SPY"), self.time_rules.at(0, 0), self.fill_queue) self.schedule.on(self.date_rules.every_day("SPY"), self.time_rules.every(timedelta(minutes=60)), self.take_from_queue) - def coarse_selection_function(self, coarse): + def coarse_selection_function(self, coarse: list[CoarseFundamental]) -> list[Symbol]: has_fundamentals = [security for security in coarse if security.has_fundamental_data] sorted_by_dollar_volume = sorted(has_fundamentals, key=lambda x: x.dollar_volume, reverse=True) return [ x.symbol for x in sorted_by_dollar_volume[:self.__number_of_symbols] ] - def fine_selection_function(self, fine): + def fine_selection_function(self, fine: list[FineFundamental]) -> list[Symbol]: sorted_by_pe_ratio = sorted(fine, key=lambda x: x.valuation_ratios.pe_ratio, reverse=True) return [ x.symbol for x in sorted_by_pe_ratio[:self.__number_of_symbols_fine] ] - def fill_queue(self): - securities = [security for security in self.active_securities.values() if security.fundamentals is not None] + def fill_queue(self) -> None: + securities = [security for security in self.active_securities.values() if security.fundamentals] # Fill queue with symbols sorted by PE ratio (decreasing order) - self.queue.queue.clear() + self._queue.queue.clear() sorted_by_pe_ratio = sorted(securities, key=lambda x: x.fundamentals.valuation_ratios.pe_ratio, reverse=True) for security in sorted_by_pe_ratio: - self.queue.put(security.symbol) + self._queue.put(security.symbol) - def take_from_queue(self): - symbols = [self.queue.get() for _ in range(min(self.dequeue_size, self.queue.qsize()))] + def take_from_queue(self) -> None: + symbols = [self._queue.get() for _ in range(min(self._dequeue_size, self._queue.qsize()))] self.history(symbols, 10, Resolution.DAILY) self.log(f"Symbols at {self.time}: {[str(symbol) for symbol in symbols]}") diff --git a/Algorithm.Python/SecurityDynamicPropertyPythonClassAlgorithm.py b/Algorithm.Python/SecurityDynamicPropertyPythonClassAlgorithm.py index e4327eb7d5ae..7170bbfd66da 100644 --- a/Algorithm.Python/SecurityDynamicPropertyPythonClassAlgorithm.py +++ b/Algorithm.Python/SecurityDynamicPropertyPythonClassAlgorithm.py @@ -14,45 +14,47 @@ from AlgorithmImports import * from collections import deque +import numpy as np + ### ### Algorithm asserting that security dynamic properties keep Python references to the Python class they are instances of, ### specifically when this class is a subclass of a C# class. ### class SecurityDynamicPropertyPythonClassAlgorithm(QCAlgorithm): - def initialize(self): + def initialize(self) -> None: self.set_start_date(2013, 10, 7) self.set_end_date(2013, 10, 7) - self.spy = self.add_equity("SPY", Resolution.MINUTE) + self._spy = self.add_equity("SPY", Resolution.MINUTE) custom_sma = CustomSimpleMovingAverage('custom', 60) - self.spy.custom_sma = custom_sma - custom_sma.security = self.spy + self._spy.custom_sma = custom_sma + custom_sma.security = self._spy - self.register_indicator(self.spy.symbol, self.spy.custom_sma, Resolution.MINUTE) + self.register_indicator(self._spy.symbol, self._spy.custom_sma, Resolution.MINUTE) def on_warmup_finished(self) -> None: - if type(self.spy.custom_sma) != CustomSimpleMovingAverage: + if type(self._spy.custom_sma) != CustomSimpleMovingAverage: raise AssertionError("spy.custom_sma is not an instance of CustomSimpleMovingAverage") - if self.spy.custom_sma.security is None: + if not self._spy.custom_sma.security: raise AssertionError("spy.custom_sma.security is None") else: - self.debug(f"spy.custom_sma.security.symbol: {self.spy.custom_sma.security.symbol}") + self.debug(f"spy.custom_sma.security.symbol: {self._spy.custom_sma.security.symbol}") def on_data(self, slice: Slice) -> None: - if self.spy.custom_sma.is_ready: - self.debug(f"CustomSMA: {self.spy.custom_sma.current.value}") + if self._spy.custom_sma.is_ready: + self.debug(f"CustomSMA: {self._spy.custom_sma.current.value}") class CustomSimpleMovingAverage(PythonIndicator): - def __init__(self, name, period): + def __init__(self, name: str, period: int) -> None: super().__init__() self.name = name self.value = 0 - self.queue = deque(maxlen=period) + self._queue = deque(maxlen=period) - def update(self, input): - self.queue.appendleft(input.value) - count = len(self.queue) - self.value = np.sum(self.queue) / count - return count == self.queue.maxlen + def update(self, input: IndicatorDataPoint) -> bool: + self._queue.appendleft(input.value) + count = len(self._queue) + self.value = np.sum(self._queue) / count + return count == self._queue.maxlen diff --git a/Algorithm.Python/SliceGetByTypeRegressionAlgorithm.py b/Algorithm.Python/SliceGetByTypeRegressionAlgorithm.py index dcdf203ce7a0..e1c86de615d3 100644 --- a/Algorithm.Python/SliceGetByTypeRegressionAlgorithm.py +++ b/Algorithm.Python/SliceGetByTypeRegressionAlgorithm.py @@ -19,7 +19,7 @@ ### Regression algorithm asserting slice.get() works for both the alpha and the algorithm ### class SliceGetByTypeRegressionAlgorithm(QCAlgorithm): - def initialize(self): + def initialize(self) -> None: '''Initialise the data and resolution required, as well as the cash and start-end dates for your algorithm. All algorithms must initialized.''' self.set_start_date(2013,10, 7) @@ -28,7 +28,7 @@ def initialize(self): self.add_equity("SPY", Resolution.MINUTE) self.set_alpha(TestAlphaModel()) - def on_data(self, data): + def on_data(self, data: Slice) -> None: if "SPY" in data: tb = data.get(TradeBar)["SPY"] global trade_flag @@ -36,7 +36,7 @@ def on_data(self, data): self.set_holdings("SPY", 1) class TestAlphaModel(AlphaModel): - def update(self, algorithm, data): + def update(self, algorithm: QCAlgorithm, data: Slice) -> list[Insight]: insights = [] if "SPY" in data: @@ -44,7 +44,4 @@ def update(self, algorithm, data): global trade_flag trade_flag = True - return insights - - def on_securities_changed(self, algorithm, changes): - return + return insights \ No newline at end of file diff --git a/Algorithm.Python/SmaCrossUniverseSelectionAlgorithm.py b/Algorithm.Python/SmaCrossUniverseSelectionAlgorithm.py index 58ae331d2471..5610a270f981 100644 --- a/Algorithm.Python/SmaCrossUniverseSelectionAlgorithm.py +++ b/Algorithm.Python/SmaCrossUniverseSelectionAlgorithm.py @@ -17,13 +17,12 @@ class SmaCrossUniverseSelectionAlgorithm(QCAlgorithm): '''Provides an example where WarmUpIndicator method is used to warm up indicators after their security is added and before (Universe Selection scenario)''' - count = 10 - tolerance = 0.01 - target_percent = 1 / count - averages = dict() - - def initialize(self): + _count = 10 + _tolerance = 0.01 + _target_percent = 1 / _count + _averages = dict() + def initialize(self) -> None: self.universe_settings.leverage = 2 self.universe_settings.resolution = Resolution.DAILY @@ -54,8 +53,7 @@ def initialize(self): # reporting that the algorithm manager is trying to add old information self.set_warm_up(10) - def coarse_sma_selector(self, coarse): - + def coarse_sma_selector(self, coarse: list[Fundamental]) -> list[Symbol]: score = dict() for cf in coarse: if not cf.has_fundamental_data: @@ -63,23 +61,23 @@ def coarse_sma_selector(self, coarse): symbol = cf.symbol price = cf.adjusted_price # grab the SMA instance for this symbol - avg = self.averages.setdefault(symbol, SimpleMovingAverage(100)) + avg = self._averages.setdefault(symbol, SimpleMovingAverage(100)) self.warm_up_indicator(symbol, avg, Resolution.DAILY) # Update returns true when the indicators are ready, so don't accept until they are if avg.update(cf.end_time, price): value = avg.current.value # only pick symbols who have their price over their 100 day sma - if value > price * self.tolerance: + if value > price * self._tolerance: score[symbol] = (value - price) / ((value + price) / 2) - # prefer symbols with a larger delta by percentage between the two averages + # prefer symbols with a larger delta by percentage between the two _averages sorted_score = sorted(score.items(), key=lambda kvp: kvp[1], reverse=True) - return [x[0] for x in sorted_score[:self.count]] + return [x[0] for x in sorted_score[:self._count]] - def on_securities_changed(self, changes): + def on_securities_changed(self, changes: SecurityChanges) -> None: for security in changes.removed_securities: if security.invested: self.liquidate(security.symbol) for security in changes.added_securities: - self.set_holdings(security.symbol, self.target_percent) + self.set_holdings(security.symbol, self._target_percent) diff --git a/Algorithm.Python/TensorFlowNeuralNetworkAlgorithm.py b/Algorithm.Python/TensorFlowNeuralNetworkAlgorithm.py index 2a929fa9508b..5be11e2c178a 100644 --- a/Algorithm.Python/TensorFlowNeuralNetworkAlgorithm.py +++ b/Algorithm.Python/TensorFlowNeuralNetworkAlgorithm.py @@ -16,7 +16,7 @@ class TensorFlowNeuralNetworkAlgorithm(QCAlgorithm): - def initialize(self): + def initialize(self) -> None: self.set_start_date(2013, 10, 7) # Set Start Date self.set_end_date(2013, 10, 8) # Set End Date @@ -29,7 +29,7 @@ def initialize(self): self.schedule.on(self.date_rules.every(DayOfWeek.MONDAY), self.time_rules.after_market_open("SPY", 28), self.net_train) # train the neural network 28 mins after market open self.schedule.on(self.date_rules.every(DayOfWeek.MONDAY), self.time_rules.after_market_open("SPY", 30), self.trade) # trade 30 mins after market open - def add_layer(self, inputs, in_size, out_size, activation_function=None): + def add_layer(self, inputs: tf.Tensor, in_size: int, out_size: int, activation_function: tf.keras.layers.Activation = None) -> tf.Tensor: # add one more layer and return the output of this layer # this is one NN with only one hidden layer weights = tf.Variable(tf.random_normal([in_size, out_size])) @@ -41,7 +41,7 @@ def add_layer(self, inputs, in_size, out_size, activation_function=None): outputs = activation_function(wx_plus_b) return outputs - def net_train(self): + def net_train(self) -> None: # Daily historical data is used to train the machine learning model history = self.history(self.symbols, self.lookback + 1, Resolution.DAILY) @@ -97,14 +97,17 @@ def net_train(self): self.sell_prices[symbol] = y_pred_final - np.std(y_data) self.buy_prices[symbol] = y_pred_final + np.std(y_data) - def trade(self): + def trade(self) -> None: ''' Enter or exit positions based on relationship of the open price of the current bar and the prices defined by the machine learning model. Liquidate if the open price is below the sell price and buy if the open price is above the buy price ''' for holding in self.portfolio.Values: - if self.current_slice[holding.symbol].open < self.sell_prices[holding.symbol] and holding.invested: + if holding.symbol not in self.current_slice.bars: + return + + if self.current_slice.bars[holding.symbol].open < self.sell_prices[holding.symbol] and holding.invested: self.liquidate(holding.symbol) - if self.current_slice[holding.symbol].open > self.buy_prices[holding.symbol] and not holding.invested: + if self.current_slice.bars[holding.symbol].open > self.buy_prices[holding.symbol] and not holding.invested: self.set_holdings(holding.symbol, 1 / len(self.symbols)) diff --git a/Algorithm.Python/TiingoPriceAlgorithm.py b/Algorithm.Python/TiingoPriceAlgorithm.py index 0739e232e238..358b6877472d 100644 --- a/Algorithm.Python/TiingoPriceAlgorithm.py +++ b/Algorithm.Python/TiingoPriceAlgorithm.py @@ -12,7 +12,7 @@ # limitations under the License. from AlgorithmImports import * -from QuantConnect.Data.Custom.Tiingo import * +from QuantConnect.Data.Custom.Tiingo import TiingoPrice ### ### This example algorithm shows how to import and use Tiingo daily prices data. @@ -32,29 +32,30 @@ def initialize(self): # Set your Tiingo API Token here Tiingo.set_auth_code("my-tiingo-api-token") - self.ticker = "AAPL" - self.equity = self.add_equity(self.ticker).symbol - self.aapl = self.add_data(TiingoPrice, self.ticker, Resolution.DAILY).symbol + self._equity = self.add_equity("AAPL").symbol + self._aapl = self.add_data(TiingoPrice, self._equity, Resolution.DAILY).symbol - self.ema_fast = self.ema(self.equity, 5) - self.ema_slow = self.ema(self.equity, 10) + self._ema_fast = self.ema(self._equity, 5) + self._ema_slow = self.ema(self._equity, 10) def on_data(self, slice): # OnData event is the primary entry point for your algorithm. Each new data point will be pumped in here. - if not slice.contains_key(self.ticker): return + if not slice.contains_key(self._equity): return # Extract Tiingo data from the slice - row = slice[self.ticker] + row = slice[self._equity] - if row is not None: - if self.ema_fast.is_ready and self.ema_slow.is_ready: - self.log(f"{self.time} - {row.symbol.value} - {row.close} {row.value} {row.price} - EmaFast:{self.ema_fast} - EmaSlow:{self.ema_slow}") + if not row: + return - # Simple EMA cross - if not self.portfolio.invested and self.ema_fast > self.ema_slow: - self.set_holdings(self.equity, 1) + if self._ema_fast.is_ready and self._ema_slow.is_ready: + self.log(f"{self.time} - {row.symbol.value} - {row.close} {row.value} {row.price} - EmaFast:{self._ema_fast} - EmaSlow:{self._ema_slow}") - elif self.portfolio.invested and self.ema_fast < self.ema_slow: - self.liquidate(self.equity) + # Simple EMA cross + if not self.portfolio.invested and self._ema_fast > self._ema_slow: + self.set_holdings(self._equity, 1) + + elif self.portfolio.invested and self._ema_fast < self._ema_slow: + self.liquidate(self._equity) diff --git a/Algorithm.Python/TimeInForceAlgorithm.py b/Algorithm.Python/TimeInForceAlgorithm.py index e7eb06d5ec10..69f034090895 100644 --- a/Algorithm.Python/TimeInForceAlgorithm.py +++ b/Algorithm.Python/TimeInForceAlgorithm.py @@ -33,61 +33,61 @@ def initialize(self): # We currently only support GTC and DAY. # self.default_order_properties.time_in_force = TimeInForce.day - self.symbol = self.add_equity("SPY", Resolution.MINUTE).symbol + self._symbol = self.add_equity("SPY", Resolution.MINUTE).symbol - self.gtc_order_ticket1 = None - self.gtc_order_ticket2 = None - self.day_order_ticket1 = None - self.day_order_ticket2 = None - self.gtd_order_ticket1 = None - self.gtd_order_ticket2 = None - self.expected_order_statuses = {} + self._gtc_order_ticket1 = None + self._gtc_order_ticket2 = None + self._day_order_ticket1 = None + self._day_order_ticket2 = None + self._gtd_order_ticket1 = None + self._gtd_order_ticket2 = None + self._expected_order_statuses = {} # OnData event is the primary entry point for your algorithm. Each new data point will be pumped in here. # Arguments: # data: Slice object keyed by symbol containing the stock data def on_data(self, data): - if self.gtc_order_ticket1 is None: + if not self._gtc_order_ticket1: # These GTC orders will never expire and will not be canceled automatically. self.default_order_properties.time_in_force = TimeInForce.GOOD_TIL_CANCELED # this order will not be filled before the end of the backtest - self.gtc_order_ticket1 = self.limit_order(self.symbol, 10, 100) - self.expected_order_statuses[self.gtc_order_ticket1.order_id] = OrderStatus.SUBMITTED + self._gtc_order_ticket1 = self.limit_order(self._symbol, 10, 100) + self._expected_order_statuses[self._gtc_order_ticket1.order_id] = OrderStatus.SUBMITTED # this order will be filled before the end of the backtest - self.gtc_order_ticket2 = self.limit_order(self.symbol, 10, 160) - self.expected_order_statuses[self.gtc_order_ticket2.order_id] = OrderStatus.FILLED + self._gtc_order_ticket2 = self.limit_order(self._symbol, 10, 160) + self._expected_order_statuses[self._gtc_order_ticket2.order_id] = OrderStatus.FILLED - if self.day_order_ticket1 is None: + if not self._day_order_ticket1: # These DAY orders will expire at market close, # if not filled by then they will be canceled automatically. self.default_order_properties.time_in_force = TimeInForce.DAY # this order will not be filled before market close and will be canceled - self.day_order_ticket1 = self.limit_order(self.symbol, 10, 140) - self.expected_order_statuses[self.day_order_ticket1.order_id] = OrderStatus.CANCELED + self._day_order_ticket1 = self.limit_order(self._symbol, 10, 140) + self._expected_order_statuses[self._day_order_ticket1.order_id] = OrderStatus.CANCELED # this order will be filled before market close - self.day_order_ticket2 = self.limit_order(self.symbol, 10, 180) - self.expected_order_statuses[self.day_order_ticket2.order_id] = OrderStatus.FILLED + self._day_order_ticket2 = self.limit_order(self._symbol, 10, 180) + self._expected_order_statuses[self._day_order_ticket2.order_id] = OrderStatus.FILLED - if self.gtd_order_ticket1 is None: + if not self._gtd_order_ticket1: # These GTD orders will expire on October 10th at market close, # if not filled by then they will be canceled automatically. - self.default_order_properties.time_in_force = TimeInForce.good_til_date(datetime(2013, 10, 10)) + self.default_order_properties.time_in_force = TimeInForce.GOOD_TIL_DATE(datetime(2013, 10, 10)) # this order will not be filled before expiry and will be canceled - self.gtd_order_ticket1 = self.limit_order(self.symbol, 10, 100) - self.expected_order_statuses[self.gtd_order_ticket1.order_id] = OrderStatus.CANCELED + self._gtd_order_ticket1 = self.limit_order(self._symbol, 10, 100) + self._expected_order_statuses[self._gtd_order_ticket1.order_id] = OrderStatus.CANCELED # this order will be filled before expiry - self.gtd_order_ticket2 = self.limit_order(self.symbol, 10, 160) - self.expected_order_statuses[self.gtd_order_ticket2.order_id] = OrderStatus.FILLED + self._gtd_order_ticket2 = self.limit_order(self._symbol, 10, 160) + self._expected_order_statuses[self._gtd_order_ticket2.order_id] = OrderStatus.FILLED # Order event handler. This handler will be called for all order events, including submissions, fills, cancellations. # This method can be called asynchronously, ensure you use proper locks on thread-unsafe objects @@ -96,7 +96,7 @@ def on_order_event(self, orderEvent): # End of algorithm run event handler. This method is called at the end of a backtest or live trading operation. def on_end_of_algorithm(self): - for orderId, expectedStatus in self.expected_order_statuses.items(): + for orderId, expectedStatus in self._expected_order_statuses.items(): order = self.transactions.get_order_by_id(orderId) if order.status != expectedStatus: raise AssertionError(f"Invalid status for order {orderId} - Expected: {expectedStatus}, actual: {order.status}") diff --git a/Algorithm.Python/UserDefinedUniverseAlgorithm.py b/Algorithm.Python/UserDefinedUniverseAlgorithm.py index 44216f58b714..2685c2de7b02 100644 --- a/Algorithm.Python/UserDefinedUniverseAlgorithm.py +++ b/Algorithm.Python/UserDefinedUniverseAlgorithm.py @@ -23,26 +23,23 @@ ### class UserDefinedUniverseAlgorithm(QCAlgorithm): - def initialize(self): + def initialize(self) -> None: self.set_cash(100000) - self.set_start_date(2015,1,1) - self.set_end_date(2015,12,1) - self.symbols = [ "SPY", "GOOG", "IBM", "AAPL", "MSFT", "CSCO", "ADBE", "WMT"] + self.set_start_date(2015, 1, 1) + self.set_end_date(2015, 12, 1) + self._symbols = ["SPY", "GOOG", "IBM", "AAPL", "MSFT", "CSCO", "ADBE", "WMT"] self.universe_settings.resolution = Resolution.HOUR self.add_universe('my_universe_name', Resolution.HOUR, self.selection) - def selection(self, time): - index = time.hour%len(self.symbols) - return [self.symbols[index]] + def selection(self, time: datetime) -> list[Union[str, Symbol]]: + index = time.hour % len(self._symbols) + return [self._symbols[index]] - def on_data(self, slice): - pass - - def on_securities_changed(self, changes): + def on_securities_changed(self, changes: SecurityChanges) -> None: for removed in changes.removed_securities: if removed.invested: self.liquidate(removed.symbol) for added in changes.added_securities: - self.set_holdings(added.symbol, 1/float(len(changes.added_securities))) + self.set_holdings(added.symbol, 1/len(changes.added_securities)) diff --git a/Algorithm.Python/VolumeRenkoConsolidatorAlgorithm.py b/Algorithm.Python/VolumeRenkoConsolidatorAlgorithm.py index 55eea91fec54..dc7df33801db 100644 --- a/Algorithm.Python/VolumeRenkoConsolidatorAlgorithm.py +++ b/Algorithm.Python/VolumeRenkoConsolidatorAlgorithm.py @@ -26,23 +26,23 @@ def initialize(self): self.set_end_date(2013, 10, 11) self.set_cash(100000) - self.sma = SimpleMovingAverage(10) - self.tick_consolidated = False + self._sma = SimpleMovingAverage(10) + self._tick_consolidated = False - self.spy = self.add_equity("SPY", Resolution.MINUTE).symbol - self.tradebar_volume_consolidator = VolumeRenkoConsolidator(1000000) - self.tradebar_volume_consolidator.data_consolidated += self.on_spy_data_consolidated + self._spy = self.add_equity("SPY", Resolution.MINUTE).symbol + self._tradebar_volume_consolidator = VolumeRenkoConsolidator(1000000) + self._tradebar_volume_consolidator.data_consolidated += self.on_spy_data_consolidated - self.ibm = self.add_equity("IBM", Resolution.TICK).symbol - self.tick_volume_consolidator = VolumeRenkoConsolidator(1000000) - self.tick_volume_consolidator.data_consolidated += self.on_ibm_data_consolidated + self._ibm = self.add_equity("IBM", Resolution.TICK).symbol + self._tick_volume_consolidator = VolumeRenkoConsolidator(1000000) + self._tick_volume_consolidator.data_consolidated += self.on_ibm_data_consolidated - history = self.history[TradeBar](self.spy, 1000, Resolution.MINUTE) + history = self.history[TradeBar](self._spy, 1000, Resolution.MINUTE) for bar in history: - self.tradebar_volume_consolidator.update(bar) + self._tradebar_volume_consolidator.update(bar) def on_spy_data_consolidated(self, sender, bar): - self.sma.update(bar.end_time, bar.value) + self._sma.update(bar.end_time, bar.value) self.debug(f"SPY {bar.time} to {bar.end_time} :: O:{bar.open} H:{bar.high} L:{bar.low} C:{bar.close} V:{bar.volume}") if bar.volume != 1000000: raise AssertionError("Volume of consolidated bar does not match set value!") @@ -51,23 +51,23 @@ def on_ibm_data_consolidated(self, sender, bar): self.debug(f"IBM {bar.time} to {bar.end_time} :: O:{bar.open} H:{bar.high} L:{bar.low} C:{bar.close} V:{bar.volume}") if bar.volume != 1000000: raise AssertionError("Volume of consolidated bar does not match set value!") - self.tick_consolidated = True + self._tick_consolidated = True def on_data(self, slice): # Update by TradeBar - if slice.bars.contains_key(self.spy): - self.tradebar_volume_consolidator.update(slice.bars[self.spy]) + if slice.bars.contains_key(self._spy): + self._tradebar_volume_consolidator.update(slice.bars[self._spy]) # Update by Tick - if slice.ticks.contains_key(self.ibm): - for tick in slice.ticks[self.ibm]: - self.tick_volume_consolidator.update(tick) + if slice.ticks.contains_key(self._ibm): + for tick in slice.ticks[self._ibm]: + self._tick_volume_consolidator.update(tick) - if self.sma.is_ready and self.sma.current.value < self.securities[self.spy].price: - self.set_holdings(self.spy, 1) + if self._sma.is_ready and self._sma.current.value < self.securities[self._spy].price: + self.set_holdings(self._spy, 1) else: - self.set_holdings(self.spy, 0) + self.set_holdings(self._spy, 0) def on_end_of_algorithm(self): - if not self.tick_consolidated: + if not self._tick_consolidated: raise AssertionError("Tick consolidator was never been called") diff --git a/Algorithm.Python/VolumeShareSlippageModelAlgorithm.py b/Algorithm.Python/VolumeShareSlippageModelAlgorithm.py index e585ec109c3e..0a1152ea1efb 100644 --- a/Algorithm.Python/VolumeShareSlippageModelAlgorithm.py +++ b/Algorithm.Python/VolumeShareSlippageModelAlgorithm.py @@ -18,8 +18,8 @@ ### Example algorithm implementing VolumeShareSlippageModel. ### class VolumeShareSlippageModelAlgorithm(QCAlgorithm): - longs = [] - shorts = [] + _longs = [] + _shorts = [] def initialize(self) -> None: self.set_start_date(2020, 11, 29) @@ -27,27 +27,24 @@ def initialize(self) -> None: # To set the slippage model to limit to fill only 30% volume of the historical volume, with 5% slippage impact. self.set_security_initializer(lambda security: security.set_slippage_model(VolumeShareSlippageModel(0.3, 0.05))) - # Create SPY symbol to explore its constituents. - spy = Symbol.create("SPY", SecurityType.EQUITY, Market.USA) - self.universe_settings.resolution = Resolution.DAILY # Add universe to trade on the most and least weighted stocks among SPY constituents. - self.add_universe(self.universe.etf(spy, universe_filter_func=self.selection)) + self.add_universe(self.universe.etf("SPY", universe_filter_func=self.selection)) - def selection(self, constituents: List[ETFConstituentUniverse]) -> List[Symbol]: + def selection(self, constituents: list[ETFConstituentUniverse]) -> list[Symbol]: sorted_by_weight = sorted(constituents, key=lambda c: c.weight) # Add the 10 most weighted stocks to the universe to long later. - self.longs = [c.symbol for c in sorted_by_weight[-10:]] + self._longs = [c.symbol for c in sorted_by_weight[-10:]] # Add the 10 least weighted stocks to the universe to short later. - self.shorts = [c.symbol for c in sorted_by_weight[:10]] + self._shorts = [c.symbol for c in sorted_by_weight[:10]] - return self.longs + self.shorts + return self._longs + self._shorts def on_data(self, slice: Slice) -> None: # Equally invest into the selected stocks to evenly dissipate capital risk. # Dollar neutral of long and short stocks to eliminate systematic risk, only capitalize the popularity gap. - targets = [PortfolioTarget(symbol, 0.05) for symbol in self.longs] - targets += [PortfolioTarget(symbol, -0.05) for symbol in self.shorts] + targets = [PortfolioTarget(symbol, 0.05) for symbol in self._longs] + targets += [PortfolioTarget(symbol, -0.05) for symbol in self._shorts] # Liquidate the ones not being the most and least popularity stocks to release fund for higher expected return trades. self.set_holdings(targets, liquidate_existing_holdings=True) diff --git a/Algorithm.Python/WeeklyUniverseSelectionRegressionAlgorithm.py b/Algorithm.Python/WeeklyUniverseSelectionRegressionAlgorithm.py index 8007101a086d..0d1afaaafd7b 100644 --- a/Algorithm.Python/WeeklyUniverseSelectionRegressionAlgorithm.py +++ b/Algorithm.Python/WeeklyUniverseSelectionRegressionAlgorithm.py @@ -19,7 +19,7 @@ ### class WeeklyUniverseSelectionRegressionAlgorithm(QCAlgorithm): - def initialize(self): + def initialize(self) -> None: self.set_cash(100000) self.set_start_date(2013,10,1) self.set_end_date(2013,10,31) @@ -29,22 +29,23 @@ def initialize(self): # select IBM once a week, empty universe the other days self.add_universe("my-custom-universe", lambda dt: ["IBM"] if dt.day % 7 == 0 else []) - def on_data(self, slice): - if self.changes is None: return + def on_data(self, slice: Slice) -> None: + if not self._changes: + return # liquidate removed securities - for security in self.changes.removed_securities: + for security in self._changes.removed_securities: if security.invested: self.log("{} Liquidate {}".format(self.time, security.symbol)) self.liquidate(security.symbol) # we'll simply go long each security we added to the universe - for security in self.changes.added_securities: + for security in self._changes.added_securities: if not security.invested: self.log("{} Buy {}".format(self.time, security.symbol)) self.set_holdings(security.symbol, 1) - self.changes = None + self._changes = None - def on_securities_changed(self, changes): - self.changes = changes + def on_securities_changed(self, changes: SecurityChanges) -> None: + self._changes = changes