diff --git a/single-page/Quantconnect-Cloud-Platform.html b/single-page/Quantconnect-Cloud-Platform.html index 5c493db26e..c771a78b22 100644 --- a/single-page/Quantconnect-Cloud-Platform.html +++ b/single-page/Quantconnect-Cloud-Platform.html @@ -2769,6 +2769,13 @@

Download Files

link that appears. +

+ If you can't download files from the Object Store, you can log data to the Object Store during a backtest and analyze it in the Research Environment. For a full walkthrough, see + + Example for Logging + + . +

@@ -9383,6 +9390,11 @@

Object Store

Example for Plotting . + For an example of logging order data to a file and analyzing it in the Research Environment, see + + Example for Logging + + .

@@ -23166,7 +23178,7 @@

Demo Algorithm

self.set_start_date(2024, 9, 1) self.set_end_date(2024, 12, 31) self.set_cash(100000) - self.set_brokerage_model(BrokerageName.QUANTCONNECT_BROKERAGE, AccountType.MARGIN) + self.set_brokerage_model(BrokerageName.QUANT_CONNECT_BROKERAGE, AccountType.MARGIN) symbol = self.add_equity("SPY", Resolution.DAILY).symbol self._fast = self.ema(symbol, 10, Resolution.DAILY) self._slow = self.ema(symbol, 50, Resolution.DAILY) @@ -43798,6 +43810,13 @@

Download Files

link that appears. +

+ If you can't download files from the Object Store, you can log data to the Object Store during a backtest and analyze it in the Research Environment. For a full walkthrough, see + + Example for Logging + + . +

diff --git a/single-page/Quantconnect-Lean-Cli.html b/single-page/Quantconnect-Lean-Cli.html index ecfd116d53..3e55ed5fc9 100644 --- a/single-page/Quantconnect-Lean-Cli.html +++ b/single-page/Quantconnect-Lean-Cli.html @@ -513,10 +513,10 @@

Cheat Sheet

The following cheat sheet summarizes the most common LEAN CLI commands, grouped by category.

- LEAN CLI API Cheat Sheet + LEAN CLI API Cheat Sheet

To download a copy of this cheat sheet, - + click here . For a full reference of all available commands, see the @@ -13645,6 +13645,224 @@

Mixed Workflow

+

Unit Testing

+ + +

+ You can't run traditional unit test frameworks with LEAN CLI, but you can use backtests to run unit tests. + To unit test your algorithm logic, structure your testable code into separate classes or functions and then run assertions during algorithm initialization. +

+

+ Example +

+

+ The following example defines a + + PortfolioAllocator + + class with testable methods and runs unit tests in the + + initialize + + method when backtesting. +

+
+
public class PortfolioAllocator
+{
+    private readonly string[] _targetSymbols;
+
+    public PortfolioAllocator(string[] targetSymbols)
+    {
+        _targetSymbols = targetSymbols;
+    }
+
+    public Dictionary<string, decimal> CalculateWeights()
+    {
+        // Calculate equal weights for all symbols.
+        var weight = 1m / _targetSymbols.Length;
+        return _targetSymbols.ToDictionary(s => s, s => weight);
+    }
+
+    public bool ShouldRebalance(SecurityPortfolioManager portfolio)
+    {
+        // Determine if the portfolio needs rebalancing.
+        return !portfolio.Invested;
+    }
+}
+
+public class UnitTestAlgorithm : QCAlgorithm
+{
+    private PortfolioAllocator _allocator;
+    private static readonly bool RunUnitTest = true;
+
+    public override void Initialize()
+    {
+        SetStartDate(2024, 9, 19);
+        SetEndDate(2024, 12, 31);
+        SetCash(100000);
+
+        // Run unit tests before setup (only in backtest mode).
+        if (RunUnitTest && !LiveMode)
+        {
+            RunUnitTests();
+        }
+
+        AddEquity("SPY", Resolution.Minute);
+        AddEquity("BND", Resolution.Minute);
+        AddEquity("AAPL", Resolution.Minute);
+
+        // Initialize testable allocator.
+        _allocator = new PortfolioAllocator(new[] { "SPY", "BND", "AAPL" });
+    }
+
+    private void RunUnitTests()
+    {
+        Debug("=== Running Unit Tests ===");
+
+        // Test 1: Equal weight calculation.
+        var allocator = new PortfolioAllocator(new[] { "SPY", "BND", "AAPL" });
+        var weights = allocator.CalculateWeights();
+
+        if (weights.Count != 3) throw new RegressionTestException("Should have 3 weights");
+        if (Math.Abs(weights["SPY"] - 0.3333m) > 0.001m) throw new RegressionTestException("SPY weight incorrect");
+        if (Math.Abs(weights["BND"] - 0.3333m) > 0.001m) throw new RegressionTestException("BND weight incorrect");
+        if (Math.Abs(weights["AAPL"] - 0.3333m) > 0.001m) throw new RegressionTestException("AAPL weight incorrect");
+        Debug("Test 1 passed: Equal weight calculation");
+
+        // Test 2: Weights sum to 1.0.
+        var totalWeight = weights.Values.Sum();
+        if (Math.Abs(totalWeight - 1.0m) > 0.001m) throw new RegressionTestException($"Weights sum to {totalWeight}, not 1.0");
+        Debug("Test 2 passed: Weights sum to 1.0");
+
+        // Test 3: Single symbol allocation.
+        var singleAllocator = new PortfolioAllocator(new[] { "SPY" });
+        var singleWeights = singleAllocator.CalculateWeights();
+        if (singleWeights["SPY"] != 1.0m) throw new RegressionTestException("Single symbol should have 100% weight");
+        Debug("Test 3 passed: Single symbol allocation");
+
+        // Test 4: Two symbol allocation.
+        var dualAllocator = new PortfolioAllocator(new[] { "SPY", "BND" });
+        var dualWeights = dualAllocator.CalculateWeights();
+        if (Math.Abs(dualWeights["SPY"] - 0.5m) > 0.001m) throw new RegressionTestException("SPY should be 50%");
+        if (Math.Abs(dualWeights["BND"] - 0.5m) > 0.001m) throw new RegressionTestException("BND should be 50%");
+        Debug("Test 4 passed: Two symbol allocation");
+
+        Debug("=== All Unit Tests Passed ===");
+    }
+
+    public override void OnData(Slice data)
+    {
+        if (_allocator.ShouldRebalance(Portfolio))
+        {
+            var weights = _allocator.CalculateWeights();
+            foreach (var kvp in weights)
+            {
+                SetHoldings(kvp.Key, kvp.Value);
+            }
+        }
+    }
+}
+
# region imports
+from AlgorithmImports import *
+# endregion
+
+RUN_UNIT_TEST = True
+
+class PortfolioAllocator:
+    """Testable business logic for portfolio allocation."""
+
+    def __init__(self, target_symbols):
+        self.target_symbols = target_symbols
+
+    def calculate_weights(self):
+        """Calculate equal weights for all symbols."""
+        weight = 1.0 / len(self.target_symbols)
+        return {symbol: weight for symbol in self.target_symbols}
+
+    def should_rebalance(self, portfolio):
+        """Determine if the portfolio needs rebalancing."""
+        return not portfolio.invested
+
+
+class UnitTestAlgorithm(QCAlgorithm):
+
+    def initialize(self):
+        self.set_start_date(2024, 9, 19)
+        self.set_end_date(2024, 12, 31)
+        self.set_cash(100000)
+
+        # Run unit tests before setup (only in backtest mode).
+        if RUN_UNIT_TEST and not self.live_mode:
+            self._run_unit_tests()
+
+        self.add_equity("SPY", Resolution.MINUTE)
+        self.add_equity("BND", Resolution.MINUTE)
+        self.add_equity("AAPL", Resolution.MINUTE)
+
+        # Initialize testable allocator.
+        self._allocator = PortfolioAllocator(["SPY", "BND", "AAPL"])
+
+    def _run_unit_tests(self):
+        """Run unit tests during algorithm initialization."""
+        self.debug("=== Running Unit Tests ===")
+
+        # Test 1: Equal weight calculation.
+        allocator = PortfolioAllocator(["SPY", "BND", "AAPL"])
+        weights = allocator.calculate_weights()
+
+        assert len(weights) == 3, "Should have 3 weights"
+        assert abs(weights["SPY"] - 0.3333) < 0.001, "SPY weight incorrect"
+        assert abs(weights["BND"] - 0.3333) < 0.001, "BND weight incorrect"
+        assert abs(weights["AAPL"] - 0.3333) < 0.001, "AAPL weight incorrect"
+        self.debug("Test 1 passed: Equal weight calculation")
+
+        # Test 2: Weights sum to 1.0.
+        total_weight = sum(weights.values())
+        assert abs(total_weight - 1.0) < 0.001, f"Weights sum to {total_weight}, not 1.0"
+        self.debug("Test 2 passed: Weights sum to 1.0")
+
+        # Test 3: Single symbol allocation.
+        single_allocator = PortfolioAllocator(["SPY"])
+        single_weights = single_allocator.calculate_weights()
+        assert single_weights["SPY"] == 1.0, "Single symbol should have 100% weight"
+        self.debug("Test 3 passed: Single symbol allocation")
+
+        # Test 4: Two symbol allocation.
+        dual_allocator = PortfolioAllocator(["SPY", "BND"])
+        dual_weights = dual_allocator.calculate_weights()
+        assert abs(dual_weights["SPY"] - 0.5) < 0.001, "SPY should be 50%"
+        assert abs(dual_weights["BND"] - 0.5) < 0.001, "BND should be 50%"
+        self.debug("Test 4 passed: Two symbol allocation")
+
+        # Test 5: Mock portfolio rebalance logic.
+        class MockPortfolio:
+            def __init__(self, invested):
+                self.invested = invested
+
+        assert allocator.should_rebalance(MockPortfolio(False)) == True, \
+            "Should rebalance when not invested"
+        assert allocator.should_rebalance(MockPortfolio(True)) == False, \
+            "Should not rebalance when invested"
+        self.debug("Test 5 passed: Rebalance logic")
+
+        self.debug("=== All Unit Tests Passed ===")
+
+    def on_data(self, data: Slice):
+        if self._allocator.should_rebalance(self.portfolio):
+            weights = self._allocator.calculate_weights()
+            for symbol, weight in weights.items():
+                self.set_holdings(symbol, weight)
+
+

+ To run the unit tests, call + + lean backtest "<projectName>" + + . If all tests pass, the debug log displays the results. If any assertion fails, the backtest stops with an error. +

+ + +

 

@@ -14050,7 +14268,40 @@

Python and PyCharm

Open a project directory, generated by the CLI, with PyCharm and wait for the project to load.
  • - Wait for PyCharm to index all packages and autocomplete starts working. + In PyCharm, go to + + File > Settings + + (or + + PyCharm > Preferences + + on macOS). +
  • +
  • + In the Settings/Preferences dialog, go to + + Python > Interpreter + + . +
  • +
  • + Click the + + + + + button to add a new package. +
  • +
  • + In the search bar, type + + quantconnect-stubs + + and click + + Install Package + + .
  • Update your project to @@ -17492,6 +17743,29 @@

    Python and PyCharm

    After making sure you are running the Professional edition, follow these steps to start local debugging for Python in PyCharm:

      +
    1. + Open a terminal in your + + organization workspace + + and run + + lean library add "<projectName>" pydevd-pycharm --version <pydevdVersion> + + to add the debugger package that matches your PyCharm version. +
      +
      $ lean library add "My Project" pydevd-pycharm --version 223.8836.43
      +Adding package pydevd-pycharm to project <workspace>\My Project
      +Checking compatibility of pydevd-pycharm 223.8836.43 with the Python version used in the Docker images
      +Adding pydevd-pycharm 223.8836.43 to 'My Project\requirements.txt'
      +Installing pydevd-pycharm 223.8836.43 in local Python environment to provide local autocomplete
      +
      + When PyCharm opens the project, it prompts you to install + + pydevd-pycharm + + into the environment. Accept the prompt to proceed. +
    2. Follow the diff --git a/single-page/Quantconnect-Research-Environment.html b/single-page/Quantconnect-Research-Environment.html index f136dbd5c6..8d40b9665c 100644 --- a/single-page/Quantconnect-Research-Environment.html +++ b/single-page/Quantconnect-Research-Environment.html @@ -44445,6 +44445,345 @@

      Example for Plotting

      +

      Example for Logging

      + + +

      + You can use the Object Store to log order data from your backtests and live algorithms, then analyze it in the Research Environment. The following example demonstrates how to log orders from an + + Exponential Moving Average + + (EMA) cross strategy to a text file in the Object Store. +

      +
        +
      1. + Create an algorithm, add a daily equity subscription, and create two EMA indicators. +
      2. +
        +
        public class ObjectStoreLoggingAlgorithm : QCAlgorithm
        +{
        +    private ExponentialMovingAverage _emaShort;
        +    private ExponentialMovingAverage _emaLong;
        +    private Symbol _symbol;
        +    private string _content;
        +
        +    public override void Initialize()
        +    {
        +        _symbol = AddEquity("SPY", Resolution.Daily).Symbol;
        +        _emaShort = EMA(_symbol, 10);
        +        _emaLong = EMA(_symbol, 30);
        +        // Add a header row.
        +        _content = "Time,Symbol,Price,Quantity,Tag\n";
        +    }
        +}
        +
        class ObjectStoreLoggingAlgorithm(QCAlgorithm):
        +    def initialize(self):
        +        self._symbol = self.add_equity("SPY", Resolution.DAILY).symbol
        +        self._ema_short = self.ema(self._symbol, 10)
        +        self._ema_long = self.ema(self._symbol, 30)
        +        # Add a header row.
        +        self._content = 'Time,Symbol,Price,Quantity,Tag\n'
        +
        +

        + The algorithm saves + + _content + + + self._content + + to the Object Store at the end of the backtest. +

        +
      3. + In the + + OnData + + method, place market orders when the EMAs cross. +
      4. +
        +
        public override void OnData(Slice data)
        +{
        +    if (!_emaShort.IsReady || !_emaLong.IsReady) return;
        +
        +    if (_emaShort > _emaLong && !Portfolio[_symbol].IsLong)
        +    {
        +        MarketOrder(_symbol, 100, tag: $"BUY: ema-short: {_emaShort:F4}  >  ema-long: {_emaLong:F4}");
        +    }
        +    else if (_emaShort < _emaLong && !Portfolio[_symbol].IsShort)
        +    {
        +        MarketOrder(_symbol, -100, tag: $"SELL: ema-short: {_emaShort:F4}  <  ema-long: {_emaLong:F4}");
        +    }
        +}
        +
        def on_data(self, data: Slice):
        +    if not self._ema_short.is_ready or not self._ema_long.is_ready:
        +        return
        +
        +    ema_short = self._ema_short.current.value
        +    ema_long = self._ema_long.current.value
        +    if ema_short > ema_long and not self.portfolio[self._symbol].is_long:
        +        self.market_order(self._symbol, 100, tag=f'BUY: ema-short: {ema_short:.4f}  >  ema-long: {ema_long:.4f}')
        +    elif ema_short < ema_long and not self.portfolio[self._symbol].is_short:
        +        self.market_order(self._symbol, -100, tag=f'SELL: ema-short: {ema_short:.4f}  <  ema-long: {ema_long:.4f}')
        +
        +
      5. + In the + + OnOrderEvent + + method, log each fill to the content string. +
      6. +
        +
        public override void OnOrderEvent(OrderEvent orderEvent)
        +{
        +    if (orderEvent.Status != OrderStatus.Filled) return;
        +
        +    _content += $"{orderEvent.UtcTime:yyyy-MM-dd},{orderEvent.Symbol},{orderEvent.FillPrice}," +
        +        $"{orderEvent.FillQuantity},{orderEvent.Ticket.Tag}\n";
        +}
        +
        def on_order_event(self, order_event: OrderEvent):
        +    if order_event.status != OrderStatus.FILLED:
        +        return
        +
        +    self._content += (
        +        f'{order_event.utc_time.strftime("%Y-%m-%d")},'
        +        f'{order_event.symbol},'
        +        f'{order_event.fill_price},'
        +        f'{order_event.fill_quantity},'
        +        f'{order_event.ticket.tag}\n'
        +    )
        +
        +
      7. + In the + + OnEndOfAlgorithm + + method, save the content to the Object Store. +
      8. +
        +
        public override void OnEndOfAlgorithm()
        +{
        +    var key = $"{ProjectId}-{AlgorithmId}.txt";
        +    ObjectStore.Save(key, _content);
        +    Log($"Saved order log to Object Store: {key}");
        +}
        +
        def on_end_of_algorithm(self):
        +    key = f'{self.project_id}-{self.algorithm_id}.txt'
        +    self.object_store.save(key, self._content)
        +    self.log(f'Saved order log to Object Store: {key}')
        +
        +
      9. + + Open the Research Environment + + and create a + + QuantBook + + . +
      10. +
        +
        // Execute the following command in first
        +#load "../Initialize.csx"
        +
        +// Create a QuantBook object
        +#load "../QuantConnect.csx"
        +using QuantConnect;
        +using QuantConnect.Research;
        +
        +var qb = new QuantBook();
        +
        qb = QuantBook()
        +
        +
      11. + Read the data from the Object Store. +
      12. +
        +
        // Replace the key with the one from your backtest log.
        +var content = qb.ObjectStore.Read("<project-id>-<algorithm-id>.txt");
        +
        # Replace the key with the one from your backtest log.
        +content = qb.object_store.read("<project-id>-<algorithm-id>.txt")
        +
        +

        + The key you provide must be the same key you used to save the object. Check the backtest log for the exact key. +

        +
      13. + Split the content into lines. +
      14. +
        +
        lines = content.strip().split('\n')
        +# Display the header.
        +print(lines[0])
        +
        +
      15. + Parse the content into a list of string arrays. +
      16. +
        +
        var lines = content.Split('\n')
        +    .Where(line => !string.IsNullOrWhiteSpace(line))
        +    .Select(line => line.Split(','))
        +    .ToList();
        +
        +// Display the header row.
        +var header = lines.First();
        +Console.WriteLine(string.Join(" | ", header));
        +
        +
      17. + Display the first few rows (head) and last few rows (tail). +
      18. +
        +
        # Show the first 5 data rows.
        +for line in lines[1:6]:
        +    print(line)
        +
        +# Show the last 5 data rows.
        +for line in lines[-5:]:
        +    print(line)
        +
        +
      19. + Display the first few rows (head) and last few rows (tail). +
      20. +
        +
        // Skip the header, then take the first 5 data rows.
        +var dataRows = lines.Skip(1).ToList();
        +Console.WriteLine("Head:");
        +foreach (var row in dataRows.Take(5))
        +{
        +    Console.WriteLine(string.Join(" | ", row));
        +}
        +
        +// Show the last 5 data rows.
        +Console.WriteLine("\nTail:");
        +foreach (var row in dataRows.TakeLast(5))
        +{
        +    Console.WriteLine(string.Join(" | ", row));
        +}
        +
        +
      21. + Search for all rows tagged as "BUY". +
      22. +
        +
        # Filter lines that contain "BUY".
        +buy_orders = [line for line in lines[1:] if 'BUY' in line]
        +for line in buy_orders:
        +    print(line)
        +
        +
      23. + Search for all rows tagged as "BUY". +
      24. +
        +
        // Filter rows where Tag contains "BUY".
        +var buyOrders = dataRows
        +    .Where(row => row.Length > 4 && row[4].Contains("BUY"))
        +    .ToList();
        +
        +Console.WriteLine("Buy Orders:");
        +foreach (var row in buyOrders)
        +{
        +    Console.WriteLine(string.Join(" | ", row));
        +}
        +
        +
      +
      +
      public class ObjectStoreLoggingAlgorithm : QCAlgorithm
      +{
      +    private ExponentialMovingAverage _emaShort;
      +    private ExponentialMovingAverage _emaLong;
      +    private Symbol _symbol;
      +    private string _content;
      +
      +    public override void Initialize()
      +    {
      +        SetStartDate(2024, 1, 1);
      +        SetEndDate(2024, 12, 31);
      +        SetCash(100000);
      +
      +        _symbol = AddEquity("SPY", Resolution.Daily).Symbol;
      +        // Create short and long EMA indicators.
      +        _emaShort = EMA(_symbol, 10);
      +        _emaLong = EMA(_symbol, 30);
      +        // Add a header row.
      +        _content = "Time,Symbol,Price,Quantity,Tag\n";
      +    }
      +
      +    public override void OnData(Slice data)
      +    {
      +        if (!_emaShort.IsReady || !_emaLong.IsReady) return;
      +
      +        // Place a market order when the EMAs cross.
      +        if (_emaShort > _emaLong && !Portfolio[_symbol].IsLong)
      +        {
      +            MarketOrder(_symbol, 100, tag: $"BUY: ema-short: {_emaShort:F4}  >  ema-long: {_emaLong:F4}");
      +        }
      +        else if (_emaShort < _emaLong && !Portfolio[_symbol].IsShort)
      +        {
      +            MarketOrder(_symbol, -100, tag: $"SELL: ema-short: {_emaShort:F4}  <  ema-long: {_emaLong:F4}");
      +        }
      +    }
      +
      +    public override void OnOrderEvent(OrderEvent orderEvent)
      +    {
      +        if (orderEvent.Status != OrderStatus.Filled) return;
      +
      +        // Log each fill as a row.
      +        _content += $"{orderEvent.UtcTime:yyyy-MM-dd},{orderEvent.Symbol},{orderEvent.FillPrice}," +
      +            $"{orderEvent.FillQuantity},{orderEvent.Ticket.Tag}\n";
      +    }
      +
      +    public override void OnEndOfAlgorithm()
      +    {
      +        // Save the order log to the Object Store.
      +        var key = $"{ProjectId}-{AlgorithmId}.txt";
      +        ObjectStore.Save(key, _content);
      +        Log($"Saved order log to Object Store: {key}");
      +    }
      +}
      +
      class ObjectStoreLoggingAlgorithm(QCAlgorithm):
      +    def initialize(self) -> None:
      +        self.set_start_date(2024, 1, 1)
      +        self.set_end_date(2024, 12, 31)
      +        self.set_cash(100000)
      +
      +        self._symbol = self.add_equity("SPY", Resolution.DAILY).symbol
      +        # Create short and long EMA indicators.
      +        self._ema_short = self.ema(self._symbol, 10)
      +        self._ema_long = self.ema(self._symbol, 30)
      +        # Add a header row.
      +        self._content = 'Time,Symbol,Price,Quantity,Tag\n'
      +
      +    def on_data(self, data: Slice) -> None:
      +        if not self._ema_short.is_ready or not self._ema_long.is_ready:
      +            return
      +
      +        # Place a market order when the EMAs cross.
      +        ema_short = self._ema_short.current.value
      +        ema_long = self._ema_long.current.value
      +        if ema_short > ema_long and not self.portfolio[self._symbol].is_long:
      +            self.market_order(self._symbol, 100, tag=f'BUY: ema-short: {ema_short:.4f}  >  ema-long: {ema_long:.4f}')
      +        elif ema_short < ema_long and not self.portfolio[self._symbol].is_short:
      +            self.market_order(self._symbol, -100, tag=f'SELL: ema-short: {ema_short:.4f}  <  ema-long: {ema_long:.4f}')
      +
      +    def on_order_event(self, order_event: OrderEvent) -> None:
      +        if order_event.status != OrderStatus.FILLED:
      +            return
      +
      +        # Log each fill as a row.
      +        self._content += (
      +            f'{order_event.utc_time.strftime("%Y-%m-%d")},'
      +            f'{order_event.symbol},'
      +            f'{order_event.fill_price},'
      +            f'{order_event.fill_quantity},'
      +            f'{order_event.ticket.tag}\n'
      +        )
      +
      +    def on_end_of_algorithm(self) -> None:
      +        # Save the order log to the Object Store.
      +        key = f'{self.project_id}-{self.algorithm_id}.txt'
      +        self.object_store.save(key, self._content)
      +        self.log(f'Saved order log to Object Store: {key}')
      +
      + + +

       

    3. + Obtain the close price and return direction series. +
    4. +
      +
      close = history['close']
      +returns = history['close'].pct_change().shift(-1)[lookback*2-1:-1].reset_index(drop=True)
      +labels = pd.Series([1 if y > 0 else 0 for y in returns])   # binary class
    5. Convert the lists of features and labels into @@ -44959,7 +45298,7 @@

      Prepare Data

      arrays.
    6. -
      X = np.array(features)
      +    
      X = np.array(X)
       y = np.array(labels)
    7. @@ -45028,12 +45367,12 @@

      Train Models

      predict = aesara.function(inputs=[x], outputs=prediction)
  • - Train the model with training dataset. + Train the model with the training dataset.
  • pred, err = train(D[0], D[1])
     
    -# We can also inspect the final outcome
    +# Inspect the final outcome
     print("Final model:")
     print(w.get_value())
     print(b.get_value())
    @@ -45093,7 +45432,7 @@ 

    Store Models

    You can save and load - aesera + aesara models using the Object Store.

    @@ -45180,11 +45519,14 @@

    before you proceed.

  • - Call - + Call the + GetFilePath - with the key. + + get_file_path + + method with the key.
  • file_name = qb.object_store.get_file_path(model_key)
    @@ -45193,11 +45535,11 @@

    This method returns the path where the model is stored.

  • - Call + Call the load - with the file path. + method with the file path.
  • loaded_model = joblib.load(file_name)
    @@ -45213,20 +45555,20 @@

    Examples

    - The following examples demonstrate some common practices for using the Aesera library. + The following examples demonstrate some common practices for using the aesara library.

    Example 1: Predict Return Direction

    - The following research notebook uses + The following research notebook uses an - Aesera + aesara machine learning model to predict the next day's return direction by the previous 5 days' close price differences.

    -
    # Import the Aesera library and others.
    +   
    # Import the aesara library and others.
     import aesara
     import aesara.tensor as at
     from sklearn.model_selection import train_test_split
    @@ -45239,25 +45581,25 @@ 

    symbol = qb.add_equity("SPY", Resolution.DAILY).symbol history = qb.history(symbol, datetime(2020, 1, 1), datetime(2022, 1, 1)).loc[symbol] -# We use the close price series to generate the features to be studied. -close = history['close'] -# Get the 1-day forward return direction as the labels for the machine to learn. -returns = data['close'].pct_change().shift(-1)[lookback*2-1:-1].reset_index(drop=True) -labels = pd.Series([1 if y > 0 else 0 for y in returns]) # binary class - -# Use 1- to 5-day differences as the input features for the machine to learn. +# Use 1- to 5-day differences as the input features. lookback = 5 lookback_series = [] for i in range(1, lookback + 1): - df = data['close'].shift(i)[lookback:-1] + df = history['close'].shift(i)[lookback:-1] df.name = f"close-{i}" lookback_series.append(df) X = pd.concat(lookback_series, axis=1) -# Normalize using the 5 day interval +# Normalize using the 5 day interval. X = MinMaxScaler().fit_transform(X.T).T[4:] -# Split the data as a training set and test set for validation. -X = np.array(features) +# Use the close price series to generate the labels. +close = history['close'] +# Get the 1-day forward return direction as the labels. +returns = history['close'].pct_change().shift(-1)[lookback*2-1:-1].reset_index(drop=True) +labels = pd.Series([1 if y > 0 else 0 for y in returns]) # binary class + +# Split the data into a training set and test set for validation. +X = np.array(X) y = np.array(labels) X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2) @@ -45273,7 +45615,7 @@

    rng = np.random.default_rng(100) w = aesara.shared(rng.standard_normal(X.shape[1]), name="w") -# initialize the bias term. +# Initialize the bias term. b = aesara.shared(0., name="b") # Construct an Aesara expression graph. @@ -45283,7 +45625,7 @@

    cost = xent.mean() + 0.01 * (w ** 2).sum() # The cost to minimize (MSE) gw, gb = at.grad(cost, [w, b]) # Compute the gradient of the cost -# Compile the model. In this example, we set the step size as 0.1 times gradients. +# Compile the model. In this example, set the step size as 0.1 times gradients. train = aesara.function( inputs=[x, y], outputs=[prediction, xent], @@ -45293,7 +45635,7 @@

    # Train the model with the training dataset. pred, err = train(D[0], D[1]) -# We can also inspect the final outcome +# Inspect the final outcome. print("Final model:") print(w.get_value()) print(b.get_value()) @@ -47665,11 +48007,11 @@

    Introduction

    - This page introduces how to use + This page explains how to build, train, test, and store - stable baselines + stable_baselines3 - library in Python for reinforcement machine learning (RL) model building, training, saving in the Object Store, and loading, through an example of a Proximal Policy Optimization (PPO) portfolio optimization trading bot. + models.

    @@ -47727,13 +48069,13 @@

    Prepare Data

    historical data - to prepare the data for the model. + to prepare the data for the model. If you have historical data, manipulate it to train and test the model. In this example, extract the close price series as the outcome and obtain the partial-differenced time-series of OHLCV values as the observation.

    history = df.unstack(0)
    -# we arbitrarily select weight 0.5 here, but ideally one should strike a balance between variance retained and stationarity.
    +# Select weight 0.5 here. Ideally, strike a balance between variance retained and stationarity.
     partial_diff = (history.diff() * 0.5 + history * 0.5).iloc[1:].fillna(0)
     history = history.close.iloc[1:]
    @@ -47756,7 +48098,7 @@

    Train Models

    1. - Split the data for training and testing to evaluate our model. + Split the data for training and testing to evaluate the model.
    2. X_train = partial_diff.iloc[:-100].values
      @@ -47787,7 +48129,7 @@ 

      Train Models

      self.portfolio_value = [] self.portfolio_weights = np.ones(num_stocks) / num_stocks - # Define your action and observation spaces + # Define the action and observation spaces self.action_space = gym.spaces.Box(low=-1.0, high=1.0, shape=(num_stocks, ), dtype=np.float32) self.observation_space = gym.spaces.Box(low=-np.inf, high=np.inf, shape=(5, data.shape[1])) @@ -47804,10 +48146,10 @@

      Train Models

      if sum_weights > 1: action /= sum_weights - # deduct transaction fee + # Deduct transaction fee value = self.prediction[self.current_step] fees = np.abs(self.portfolio_weights - action) @ value - + # Update portfolio weights based on the chosen action self.portfolio_weights = action @@ -47820,7 +48162,7 @@

      Train Models

      # Check if the episode is done (end of data) done = self.current_step >= len(self.data) - 1 - # Calculate the reward, in here, we use max drawdown + # Calculate the reward using max drawdown reward = self._neg_max_drawdown return self._get_observation(), reward, done, {} @@ -47923,13 +48265,16 @@

      Store Models

      You can save and load - stable baselines + stable_baselines3 models using the Object Store.

      Save Models

      +

      + Follow these steps to save models in the Object Store: +

      1. Set the key name of the model to be stored in the Object Store. @@ -48020,7 +48365,7 @@

        load - method with the file path, environment and policy. + method with the file path, environment, and policy.

      2. loaded_model = PPO.load(file_name, env=env, policy="MlpPolicy")
        @@ -48036,17 +48381,17 @@

        Examples

        - The following examples demonstrate some common practices for using the Stable Baselines library. + The following examples demonstrate some common practices for using the stable_baselines3 library.

        Example 1: Machine Trading

        - The following research notebook uses + The following research notebook uses a - Stable Baselines + stable_baselines3 - machine learning model to make trading decision, based on the previous 5 OHLCV partial differencing as observation. + machine learning model to make trading decisions, based on the previous 5 OHLCV partial differencing as observation.

        # Import the gym and stable_baselines3 library.
        @@ -48068,11 +48413,11 @@ 

        # Obtain the daily partial differencing to be the features and labels. history = df.unstack(0) -# we arbitrarily select weight 0.5 here, but ideally one should strike a balance between variance retained and stationarity. +# Select weight 0.5 here. Ideally, strike a balance between variance retained and stationarity. partial_diff = (history.diff() * 0.5 + history * 0.5).iloc[1:].fillna(0) history = history.close.iloc[1:] -# Split the data for training and testing to evaluate our model. +# Split the data for training and testing to evaluate the model. X_train = partial_diff.iloc[:-100].values X_test = partial_diff.iloc[-100:].values y_train = history.iloc[:-100].values @@ -48091,7 +48436,7 @@

        self.portfolio_value = [] self.portfolio_weights = np.ones(num_stocks) / num_stocks - # Define your action and observation spaces + # Define the action and observation spaces self.action_space = gym.spaces.Box(low=-1.0, high=1.0, shape=(num_stocks, ), dtype=np.float32) self.observation_space = gym.spaces.Box(low=-np.inf, high=np.inf, shape=(5, data.shape[1])) @@ -48108,10 +48453,10 @@

        if sum_weights > 1: action /= sum_weights - # deduct transaction fee + # Deduct transaction fee value = self.prediction[self.current_step] fees = np.abs(self.portfolio_weights - action) @ value - + # Update portfolio weights based on the chosen action self.portfolio_weights = action @@ -48124,7 +48469,7 @@

        # Check if the episode is done (end of data) done = self.current_step >= len(self.data) - 1 - # Calculate the reward, in here, we use max drawdown + # Calculate the reward using max drawdown reward = self._neg_max_drawdown return self._get_observation(), reward, done, {} @@ -48146,15 +48491,15 @@

        # Initialize the environment. env = PortfolioEnv(X_train, y_train, 5) -# Wrap the environment in a vectorized environment +# Wrap the environment in a vectorized environment. env = DummyVecEnv([lambda: env]) -# Normalize the observation space +# Normalize the observation space. env = VecNormalize(env, norm_obs=True, norm_reward=False) # Train the model. In this example, create a RL model and train with MLP-policy PPO algorithm. -# Define the PPO agent +# Define the PPO agent. model = PPO("MlpPolicy", env, verbose=0) -# Train the agent +# Train the agent. model.learn(total_timesteps=100000) # Initialize a return series to calculate performance and a list to store the equity value at each timestep. @@ -48198,7 +48543,7 @@

        Introduction

        This page explains how to build, train, test, and store - Tensorflow + tensorflow models.

        @@ -48213,7 +48558,7 @@

        Import Libraries

        tensorflow - , and + and sklearn @@ -48260,10 +48605,10 @@

        Prepare Data

        to prepare the data for the model. If you have historical data, manipulate it to train and test the model. In this example, use the following features and labels:

        - +
        - @@ -334566,10 +335660,33 @@

        Strategy Payoff

        \end{array} $$
        +

        + In this example, the underlying stock price is + + $1,078.92 + + at expiration. You sold an ITM call at a strike of + + $1,125.00 + + for + + $57.80 + + and bought an OTM call at a strike of + + $1,197.50 + + for + + $26.90 + + . +

        The following chart shows the payoff at expiration:

        - Strategy payoff decomposition and analysis of bear call spread + Bear Call Spread strategy payoff at expiration

        The maximum profit is the net credit you receive from opening the trade, $C^{ITM}_0 - C^{OTM}_0$. If the price declines, both calls expire worthless.

        @@ -335042,10 +336159,33 @@

        Strategy Payoff

        \end{array} $$
        +

        + In this example, the underlying stock price is + + $829.08 + + at expiration. You sold an OTM put at a strike of + + $767.50 + + for + + $4.60 + + and bought an ITM put at a strike of + + $835.00 + + for + + $40.00 + + . +

        The following chart shows the payoff at expiration:

        - Strategy payoff decomposition and analysis of bear put spread + Bear Put Spread strategy payoff at expiration

        The maximum profit is $K^{ITM} - K^{OTM} + P^{OTM}_0 - P^{ITM}_0$. If the underlying price is below than the strike prices of both put Option contracts, they are worth $(K - S_T)$ at expiration.

        @@ -335515,10 +336655,33 @@

        Strategy Payoff

        \end{array} $$
        +

        + In this example, the underlying stock price is + + $829.08 + + at expiration. You sold an OTM call at a strike of + + $835.00 + + for + + $3.00 + + and bought an ITM call at a strike of + + $767.50 + + for + + $41.00 + + . +

        The following chart shows the payoff at expiration:

        - Strategy payoff decomposition and analysis of bull call spread + Bull Call Spread strategy payoff at expiration

        The maximum profit is $K^{OTM} - K^{ITM} + C^{OTM}_0 - C^{ITM}_0$. If the underlying price increases to exceed both strikes at expiration, both calls are worth $(S_T - K)$ at expiration.

        @@ -335988,10 +337151,33 @@

        Strategy Payoff

        \end{array} $$
        +

        + In this example, the underlying stock price is + + $829.08 + + at expiration. You sold an ITM put at a strike of + + $835.00 + + for + + $35.50 + + and bought an OTM put at a strike of + + $767.50 + + for + + $5.70 + + . +

        The following chart shows the payoff at expiration:

        - Strategy payoff decomposition and analysis of bear call spread + Bull Put Spread strategy payoff at expiration

        The maximum profit is the net credit you received when opening the position, $P^{ITM}_0 - P^{OTM}_0$. If the underlying price is higher than the strike prices of both put contracts at expiration, both puts expire worthless.

        @@ -336469,10 +337655,41 @@

        Strategy Payoff

        \end{array} $$
        +

        + In this example, the underlying stock price at expiration is + + $829.08 + + . You bought an OTM call at a strike of + + $767.50 + + for + + $4.90 + + , sold 2 ATM calls at a strike of + + $800.00 + + for + + $15.00 + + each, and bought an ITM call at a strike of + + $832.50 + + for + + $41.00 + + . +

        The following chart shows the payoff at expiration:

        - Strategy payoff decomposition and analysis of long call butterfly + Long Call Butterfly strategy payoff at expiration

        The maximum profit is $K^{ATM} - K^{ITM} + 2\times C^{ATM}_0 - C^{ITM}_0 - C^{OTM}_0$. It occurs when the underlying price is the same price at expiration as it was when opening the position and the payouts of the bull and bear call spreads are at their maximum.

        @@ -336993,10 +338210,41 @@

        Strategy Payoff

        \end{array} $$
        +

        + In this example, the underlying stock price at expiration is + + $829.08 + + . You bought an OTM call at a strike of + + $767.50 + + for + + $4.90 + + , sold 2 ATM calls at a strike of + + $800.00 + + for + + $15.00 + + each, and bought an ITM call at a strike of + + $832.50 + + for + + $41.00 + + . +

        The following chart shows the payoff at expiration:

        - Strategy payoff decomposition and analysis of short call butterfly + Short Call Butterfly strategy payoff at expiration

        The maximum profit is the net credit received: $C^{ITM}_0 + C^{OTM}_0 - 2\times C^{ATM}_0$. It occurs when the underlying price is less than ITM strike or greater than OTM strike at expiration.

        @@ -337111,7 +338359,7 @@

        Example

        & = & 0\\ P_T & = & (-C^{OTM}_T - C^{ITM}_T + 2\times C^{ATM}_T - 2\times C^{ATM}_0 + C^{ITM}_0 + C^{OTM}_0)\times m - fee\\ & = & (-0-3.42+0\times2+4.90+41.00-15.00\times2)\times100-1.00\times4\\ -& = & -1252 +& = & 1244 \end{array} $$

        @@ -337507,10 +338755,41 @@

        Strategy Payoff

        \end{array} $$
        +

        + In this example, the underlying stock price at expiration is + + $829.08 + + . You bought an ITM put at a strike of + + $832.50 + + for + + $37.80 + + , sold 2 ATM puts at a strike of + + $800.00 + + for + + $14.70 + + each, and bought an OTM put at a strike of + + $767.50 + + for + + $5.70 + + . +

        The following chart shows the payoff at expiration:

        - Strategy payoff decomposition and analysis of long put butterfly + Long Put Butterfly strategy payoff at expiration

        The maximum profit is $K^{ATM} - K^{OTM} + 2\times P^{ATM}_0 - P^{ITM}_0 - P^{OTM}_0$. It occurs when the underlying price is the same at expiration as it was when you open the trade. In this case, the payout of the combined bull put and bear put spreads are at their maximum.

        @@ -338026,10 +339305,41 @@

        Strategy Payoff

        \end{array} $$
        +

        + In this example, the underlying stock price at expiration is + + $829.08 + + . You bought an ITM put at a strike of + + $832.50 + + for + + $37.80 + + , sold 2 ATM puts at a strike of + + $800.00 + + for + + $14.70 + + each, and bought an OTM put at a strike of + + $767.50 + + for + + $5.70 + + . +

        The following chart shows the payoff at expiration:

        - Strategy payoff decomposition and analysis of short put butterfly + Short Put Butterfly strategy payoff at expiration

        The maximum profit is the net credit received, $P^{ITM}_0 + P^{OTM}_0 - 2\times P^{ATM}_0$. It occurs when the underlying price is below the ITM strike or above the OTM strike at expiration.

        @@ -338540,10 +339850,25 @@

        Strategy Payoff

        \end{array} $$
        +

        + In this example, you bought a longer-term call at a strike of + + $800.00 + + for + + $20.00 + + and sold a shorter-term call at the same strike for + + $11.30 + + . +

        The following chart shows the payoff at expiration:

        - Strategy payoff decomposition and analysis of long call calendar spread + Long Call Calendar Spread strategy payoff at expiration

        The maximum profit is undetermined because it depends on the underlying volatility. It occurs when $S_T = S_0$ and the spread of the calls are at their maximum.

        @@ -339045,10 +340370,25 @@

        Strategy Payoff

        \end{array} $$
        +

        + In this example, you bought a longer-term call at a strike of + + $800.00 + + for + + $20.00 + + and sold a shorter-term call at the same strike for + + $11.30 + + . +

        The following chart shows the payoff at expiration:

        - Strategy payoff decomposition and analysis of short call calendar spread + Short Call Calendar Spread strategy payoff at expiration

        The maximum profit is the net credit received, $C^{\textrm{long-term}}_0 - C^{\textrm{short-term}}_0$. It occurs when the underlying price moves very deep ITM or OTM so the values of both calls are close to zero.

        @@ -339536,10 +340876,25 @@

        Strategy Payoff

        \end{array} $$
        +

        + In this example, you bought a longer-term put at a strike of + + $800.00 + + for + + $19.30 + + and sold a shorter-term put at the same strike for + + $11.30 + + . +

        The following chart shows the payoff at expiration:

        - Strategy payoff decomposition and analysis of long put calendar spread + Long Put Calendar Spread strategy payoff at expiration

        The maximum profit is undetermined because it depends on the underlying volatility.  It occurs when $S_T = S_0$ and the spread of the puts are at their maximum.

        @@ -340047,10 +341402,25 @@

        Strategy Payoff

        \end{array} $$
        +

        + In this example, you bought a longer-term put at a strike of + + $800.00 + + for + + $19.30 + + and sold a shorter-term put at the same strike for + + $11.30 + + . +

        The following chart shows the payoff at expiration:

        - Strategy payoff decomposition and analysis of short put calendar spread + Short Put Calendar Spread strategy payoff at expiration

        The maximum profit is the net credit received, $P^{\textrm{long-term}}_0 - P^{\textrm{short-term}}_0$. It occurs when the underlying price moves very deep ITM or OTM so the values of both puts are close to zero.

        @@ -340533,10 +341903,25 @@

        Strategy Payoff

        \end{array} $$
        +

        + In this example, the underlying stock traded at + + $187.07 + + at expiration. You held the underlying and sold a call at a strike of + + $185.00 + + for + + $3.35 + + . +

        The following chart shows the payoff at expiration:

        - covered call strategy payoff + Covered Call strategy payoff at expiration

        The maximum profit is $K - S_T + C^{K}_0$, which occurs when the underlying price is at or above the strike price of the call at expiration.

        @@ -340974,10 +342359,25 @@

        Strategy Payoff

        \end{array} $$
        +

        + In this example, the underlying stock traded at + + $186.94 + + at expiration. You held the underlying and sold a put at a strike of + + $185.00 + + for + + $1.37 + + . +

        The following chart shows the payoff at expiration:

        - Strategy payoff decomposition and analysis of covered put + Covered Put strategy payoff at expiration

        The maximum profit is $S_T - K + P^{K}_0$. It occurs when the underlying price is at or below the strike price of the put at expiration.

        @@ -341503,10 +342903,49 @@

        Strategy Payoff

        \end{array} $$
        +

        + In this example, the underlying stock price at expiration is + + $843.25 + + . You traded an OTM call at + + $855.00 + + for + + $1.35 + + , an OTM put at + + $810.00 + + for + + $1.50 + + , an ATM call at + + $832.50 + + for + + $10.30 + + , and an ATM put at + + $832.50 + + for + + $9.50 + + . +

        The following chart shows the payoff at expiration:

        - Strategy payoff decomposition and analysis of long iron butterfly + Long Iron Butterfly strategy payoff at expiration

        The maximum profit is the net credit received, $C^{ATM}_0 + P^{ATM}_0 - C^{OTM}_0 - P^{OTM}_0$. It occurs when the underlying price stays the same as when you opened the trade.

        @@ -342063,10 +343502,49 @@

        Strategy Payoff

        \end{array} $$
        +

        + In this example, the underlying stock price at expiration is + + $843.25 + + . You traded an OTM call at + + $855.00 + + for + + $2.35 + + , an OTM put at + + $810.00 + + for + + $2.75 + + , an ATM call at + + $832.50 + + for + + $8.10 + + , and an ATM put at + + $832.50 + + for + + $7.40 + + . +

        The following chart shows the payoff at expiration:

        - Strategy payoff decomposition and analysis of short iron butterfly + Short Iron Butterfly strategy payoff at expiration

        The maximum profit is $K^C_{OTM} - K^C_{ATM} - C^{ATM}_0 - P^{ATM}_0 + C^{OTM}_0 + P^{OTM}_0$. It occurs when the underlying price is below the OTM put strike price or above the OTM call strike price at expiration.

        @@ -342636,10 +344114,49 @@

        Strategy Payoff

        \end{array} $$
        +

        + In this example, the underlying stock price at expiration is + + $843.25 + + . You traded a far-OTM call at + + $857.50 + + for + + $1.85 + + , a near-OTM call at + + $852.50 + + for + + $1.65 + + , a far-OTM put at + + $815.00 + + for + + $3.80 + + , and a near-OTM put at + + $820.00 + + for + + $3.50 + + . +

        The following chart shows the payoff at expiration:

        - Strategy payoff decomposition and analysis of long iron condor + Long Iron Condor strategy payoff at expiration

        The maximum profit is the net credit received after commission when opening the trade, where $K^P_{OTM} < S_T < K^C_{OTM}$.

        @@ -343249,10 +344766,49 @@

        Strategy Payoff

        \end{array} $$
        +

        + In this example, the underlying stock price at expiration is + + $843.25 + + . You traded a far-OTM call at + + $857.50 + + for + + $1.05 + + , a near-OTM call at + + $852.50 + + for + + $2.75 + + , a far-OTM put at + + $815.00 + + for + + $2.15 + + , and a near-OTM put at + + $820.00 + + for + + $4.80 + + . +

        The following chart shows the payoff at expiration:

        - Strategy payoff decomposition and analysis of short iron condor + Short Iron Condor strategy payoff at expiration

        The maximum profit is $K^C_{far} - K^C_{near} - C^{near}_0 - P^{near}_0 + C^{far}_0 + P^{far}_0$, where $K^P_{OTM} > S_T$ or $S_T > K^C_{OTM}$.

        @@ -343716,10 +345272,21 @@

        Strategy Payoff

        \end{array} $$
        +

        + In this example, you sold a call at a strike of + + $185.00 + + for + + $3.35 + + . +

        The following chart shows the payoff at expiration:

        - Strategy payoff decomposition and analysis of naked call + Naked Call strategy payoff at expiration

        The maximum profit is $C^{K}_0$, which occurs when the underlying price is at or below the strike price of the call at expiration.

        @@ -343806,7 +345373,7 @@

        Example

        & = & 5.01\\ P_T & = & (C^{K}_0 - C^{K}_T)\times m - fee\\ & = & (3.35 - 5.01)\times m - fee\\ -& = & -1.66 \times 100 - 2\\ +& = & -1.66 \times 100 - 1\\ & = & -167 \end{array} $$ @@ -344123,10 +345690,21 @@

        Strategy Payoff

        \end{array} $$
        +

        + In this example, you sold a put at a strike of + + $185.00 + + for + + $1.37 + + . +

        The following chart shows the payoff at expiration:

        - Strategy payoff decomposition and analysis of naked put + Naked Put strategy payoff at expiration

        The maximum profit is $P^{K}_0$, which occurs when the underlying price is at or above the strike price of the put at expiration.

        @@ -344549,10 +346127,25 @@

        Strategy Payoff

        \end{array} $$
        +

        + In this example, the underlying stock traded at + + $186.94 + + at expiration. You held the underlying position and bought a call at a strike of + + $185.00 + + for + + $3.50 + + . +

        The following chart shows the payoff at expiration:

        - Strategy payoff decomposition and analysis of protective call + Protective Call strategy payoff at expiration

        The maximum profit is $S_0 - C^{K}_0$, which occurs when the underlying price is $0$.

        @@ -344982,10 +346575,25 @@

        Strategy Payoff

        \end{array} $$
        +

        + In this example, the underlying stock traded at + + $187.07 + + at expiration. You held the underlying position and bought a put at a strike of + + $185.00 + + for + + $1.53 + + . +

        The following chart shows the payoff at expiration:

        - Strategy payoff decomposition and analysis of protective put + Protective Put strategy payoff at expiration

        The maximum profit is $S_T - S_0 - P^{K}_0$, which occurs when the underlying price is above the $S_0 + P^{K}_0$.

        @@ -345436,10 +347044,33 @@

        Strategy Payoff

        \end{array} $$
        +

        + In this example, the underlying stock traded at + + $833.17 + + at expiration. You held the underlying, bought a put at a strike of + + $822.50 + + for + + $6.00 + + , and sold a call at a strike of + + $845.00 + + for + + $2.85 + + . +

        The following chart shows the payoff at expiration:

        - protective collar strategy payoff + Protective Collar strategy payoff at expiration

        The maximum profit is $K^{C} - S_T + C_0 - P_0$. It occurs when the underlying price is at or above the strike price of the call at expiration.

        @@ -345900,10 +347531,25 @@

        Strategy Payoff

        \end{array} $$
        +

        + In this example, you traded a call and a put, both at a strike of + + $835.00 + + , for + + $22.30 + + and + + $23.90 + + respectively. +

        The following chart shows the payoff at expiration:

        - Strategy payoff decomposition and analysis of long straddle + Long Straddle strategy payoff at expiration

        The maximum profit is unlimited if the underlying price rises to infinity or substantial, $K^{P} - C^{OTM}_0 - P^{OTM}_0$, if it drops to zero at expiration.

        @@ -345991,18 +347637,18 @@

        Example

        \begin{array}{rcll} C^{ATM}_T & = & (S_T - K^{C})^{+}\\ & = & (934.01-835.00)^{+}\\ -& = & 98.99\\ +& = & 99.01\\ P^{ATM}_T & = & (K^{P} - S_T)^{+}\\ & = & (835.00-934.01)^{+}\\ & = & 0\\ P_T & = & (C^{ATM}_T + P^{ATM}_T - C^{ATM}_0 - P^{ATM}_0)\times m - fee\\ -& = & (98.99+0-22.3-23.9)\times100-1.00\times2\\ -& = & 5277 +& = & (99.01+0-22.3-23.9)\times100-1.00\times2\\ +& = & 5279 \end{array} $$

        - So, the strategy gains $5,277. + So, the strategy gains $5,279.

        The following algorithm implements a long straddle Option strategy: @@ -346325,10 +347971,25 @@

        Strategy Payoff

        \end{array} $$
        +

        + In this example, you traded a call and a put, both at a strike of + + $835.00 + + , for + + $19.60 + + and + + $21.40 + + respectively. +

        The following chart shows the payoff at expiration:

        - Strategy payoff decomposition and analysis of short straddle + Short Straddle strategy payoff at expiration

        The maximum profit is $C^{ATM}_0 + P^{ATM}_0$. It occurs when the price of the underlying asset remains exactly at the strike price upon expiration.

        @@ -346445,7 +348106,7 @@

        Example

        $$

        - So, the strategy loses $5,277. The early assigment doesn't influence the payoff. + So, the strategy loses $5,803. The early assigment doesn't influence the payoff.

        The following algorithm implements a short straddle Option strategy: @@ -346798,10 +348459,29 @@

        Strategy Payoff

        \end{array} $$
        +

        + In this example, you traded a call at a strike of + + $835.00 + + for + + $8.80 + + and a put at a strike of + + $832.50 + + for + + $9.50 + + . +

        The following chart shows the payoff at expiration:

        - long strangle strategy payoff + Long Strangle strategy payoff at expiration

        The maximum profit is unlimited if the underlying price rises to infinity at expiration.

        @@ -346894,7 +348574,7 @@

        Example

        & = & (832.50-843.19)^{+}\\ & = & 0\\ P_T & = & (C^{OTM}_T + P^{OTM}_T - C^{OTM}_0 - P^{OTM}_0)\times m - fee\\ -& = & (8.19+0-8.80-9.50)\times100-2.00\times2\\ +& = & (8.19+0-8.80-9.50)\times100-1.00\times2\\ & = & -1013 \end{array} $$ @@ -347278,10 +348958,29 @@

        Strategy Payoff

        \end{array} $$
        +

        + In this example, you traded a call at a strike of + + $835.00 + + for + + $8.00 + + and a put at a strike of + + $832.50 + + for + + $7.40 + + . +

        The following chart shows the payoff at expiration:

        - Strategy payoff decomposition and analysis of short strangle + Short Strangle strategy payoff at expiration

        The maximum profit $C^{OTM}_0 + P^{OTM}_0$. It occurs when the underlying price at expiration remains within the range of the strike prices. In this case, both Options expire worthless.

        @@ -347381,7 +349080,7 @@

        Example

        & = & (832.50-843.19)^{+}\\ & = & 0\\ P_T & = & (-C^{OTM}_T - P^{OTM}_T + C^{OTM}_0 + P^{OTM}_0)\times m - fee\\ -& = & (-8.19-0+8.00+7.40)\times100-2.00\times2\\ +& = & (-8.19-0+8.00+7.40)\times100-1.00\times2\\ & = & 719 \end{array} $$ @@ -347750,10 +349449,29 @@

        Strategy Payoff

        \end{array} $$
        +

        + In this example, the underlying stock traded at + + $833.17 + + at expiration. You traded a call and a put at a strike of + + $832.50 + + for + + $8.10 + + and + + $9.50 + + respectively, and held the underlying. +

        The following chart shows the payoff at expiration:

        - conversion strategy payoff + Conversion strategy payoff at expiration

        The payoff is only dependent on the strike price and the initial asset prices.

        @@ -348206,10 +349924,29 @@

        Strategy Payoff

        \end{array} $$
        +

        + In this example, the underlying stock traded at + + $832.26 + + at expiration. You traded a call and a put at a strike of + + $832.50 + + for + + $10.30 + + and + + $7.40 + + respectively, and held the underlying. +

        The following chart shows the payoff at expiration:

        - reverse conversion strategy payoff + Reverse Conversion strategy payoff at expiration

        The payoff is only dependent on the strike price and the initial asset prices.

        @@ -348677,10 +350414,45 @@

        Strategy Payoff

        \end{array} $$
        +

        + In this example, you traded an ITM call at + + $810.00 + + for + + $27.30 + + , an OTM call at + + $857.50 + + for + + $1.05 + + , an ITM put at + + $857.50 + + for + + $28.00 + + , and an OTM put at + + $810.00 + + for + + $1.50 + + . +

        The following chart shows the payoff at expiration:

        - long box spread strategy payoff + Long Box Spread strategy payoff at expiration

        The payoff is only dependent on the strike price and the initial asset prices.

        @@ -349175,10 +350947,45 @@

        Strategy Payoff

        \end{array} $$
        +

        + In this example, you traded an ITM call at + + $810.00 + + for + + $23.00 + + , an OTM call at + + $857.50 + + for + + $1.85 + + , an ITM put at + + $857.50 + + for + + $23.80 + + , and an OTM put at + + $810.00 + + for + + $2.75 + + . +

        The following chart shows the payoff at expiration:

        - short box spread strategy payoff + Short Box Spread strategy payoff at expiration

        The payoff is only dependent on the strike price and the initial asset prices.

        @@ -349685,10 +351492,17 @@

        Strategy Payoff

        \end{array} $$
        +

        + In this example, you traded near-expiry and far-expiry calls and puts at a strike of + + $832.50 + + . +

        The following chart shows the payoff at expiration:

        - long jelly roll strategy payoff + Long Jelly Roll strategy payoff at expiration

        The payoff is dependent on the market prices of the Options, but in theory, if assuming call-put parity exists, the expected payoff would be

        @@ -350212,10 +352026,17 @@

        Strategy Payoff

        \end{array} $$
        +

        + In this example, you traded near-expiry and far-expiry calls and puts at a strike of + + $832.50 + + . +

        The following chart shows the payoff at expiration:

        - short jelly roll strategy payoff + Short Jelly Roll strategy payoff at expiration

        The payoff is dependent on the market prices of the options, but in theory, if assuming call-put parity exists, the expected payoff would be

        @@ -350709,10 +352530,41 @@

        Strategy Payoff

        \end{array} $$
        +

        + In this example, the underlying stock price at expiration is + + $843.25 + + . You traded a lower-strike call at + + $822.50 + + for + + $13.80 + + , a middle-strike call at + + $825.00 + + for + + $15.10 + + , and a higher-strike call at + + $827.50 + + for + + $13.10 + + . +

        The following chart shows the payoff at expiration:

        - Strategy payoff decomposition and analysis of bear call ladder + Bear Call Ladder strategy payoff at expiration

        The maximum profit is unlimited, which occurs when the underlying price increases indefinitely.

        @@ -351196,15 +353048,50 @@

        Strategy Payoff

        \end{array} $$
        +

        + In this example, the underlying stock price at expiration is + + $843.25 + + . You traded a lower-strike put at + + $822.50 + + for + + $3.80 + + , a middle-strike put at + + $825.00 + + for + + $4.70 + + , and a higher-strike put at + + $827.50 + + for + + $7.80 + + . +

        The following chart shows the payoff at expiration:

        - Strategy payoff decomposition and analysis of bear put ladder + Bear Put Ladder strategy payoff at expiration

        The maximum profit is $K^{high} - K^{mid} + P^{low}_0 + P^{mid}_0 - P^{high}_0$, which occurs when the underlying price is between the two lower strike prices.

        - The maximum loss is $K^{high} - K^{mid} - K^{low} + P^{low}_0 + P^{mid}_0 - P^{high}_0$, which occurs when the underlying price decreases to $0. + The maximum loss is $K^{high} - K^{mid} - K^{low} + P^{low}_0 + P^{mid}_0 - P^{high}_0$, which occurs when the underlying price decreases to + + $0 + + .

        If the Option is American Option, there is a risk of @@ -351683,10 +353570,41 @@

        Strategy Payoff

        \end{array} $$
        +

        + In this example, the underlying stock price at expiration is + + $843.25 + + . You traded a lower-strike call at + + $822.50 + + for + + $17.10 + + , a middle-strike call at + + $825.00 + + for + + $12.00 + + , and a higher-strike call at + + $827.50 + + for + + $10.90 + + . +

        The following chart shows the payoff at expiration:

        - Strategy payoff decomposition and analysis of bull call ladder + Bull Call Ladder strategy payoff at expiration

        The maximum profit is $K^{mid} - K^{low} - C^{low}_0 + C^{mid}_0 + C^{high}_0$, which occurs when the underlying price is between the two higher strike prices.

        @@ -352170,12 +354088,47 @@

        Strategy Payoff

        \end{array} $$
        +

        + In this example, the underlying stock price at expiration is + + $843.25 + + . You traded a lower-strike put at + + $822.50 + + for + + $6.00 + + , a middle-strike put at + + $825.00 + + for + + $4.70 + + , and a higher-strike put at + + $827.50 + + for + + $5.60 + + . +

        The following chart shows the payoff at expiration:

        - Strategy payoff decomposition and analysis of bull put ladder + Bull Put Ladder strategy payoff at expiration

        - The maximum profit is $K^{mid} + K^{low} - K^{high} - P^{low}_0 - P^{mid}_0 + P^{high}_0$, which occurs when the underlying price decreases to $0. + The maximum profit is $K^{mid} + K^{low} - K^{high} - P^{low}_0 - P^{mid}_0 + P^{high}_0$, which occurs when the underlying price decreases to + + $0 + + .

        The maximum loss is $K^{mid} - K^{high} - P^{low}_0 - P^{mid}_0 + P^{high}_0$, which occurs when the underlying price is between the two lower strike prices. @@ -352651,10 +354604,33 @@

        Strategy Payoff

        \end{array} $$
        +

        + In this example, the underlying stock price at expiration is + + $843.19 + + . You traded a lower-strike call at + + $825.00 + + for + + $12.00 + + and two higher-strike calls at + + $835.00 + + for + + $8.80 + + each. +

        The following chart shows the payoff at expiration:

        - Strategy payoff decomposition and analysis of long call backspread + Long Call Backspread strategy payoff at expiration

        The maximum profit is unlimited, which occurs when the underlying price increases indefinitely.

        @@ -353115,10 +355091,33 @@

        Strategy Payoff

        \end{array} $$
        +

        + In this example, the underlying stock price at expiration is + + $843.19 + + . You traded a lower-strike call at + + $825.00 + + for + + $15.10 + + and two higher-strike calls at + + $835.00 + + for + + $8.00 + + each. +

        The following chart shows the payoff at expiration:

        - Strategy payoff decomposition and analysis of short call backspread + Short Call Backspread strategy payoff at expiration

        The maximum profit is $K^{high} - K^{low} - C^{low}_0 + C^{high}_0 \times 2$, which occurs when the underlying price is exactly at the higher strike at expiry.

        @@ -353579,10 +355578,33 @@

        Strategy Payoff

        \end{array} $$
        +

        + In this example, the underlying stock price at expiration is + + $843.19 + + . You traded two lower-strike puts at + + $825.00 + + for + + $6.90 + + each and a higher-strike put at + + $835.00 + + for + + $8.50 + + . +

        The following chart shows the payoff at expiration:

        - Strategy payoff decomposition and analysis of long put backspread + Long Put Backspread strategy payoff at expiration

        The maximum profit is unlimited, which occurs when the underlying price increases indefinitely.

        @@ -354043,10 +356065,33 @@

        Strategy Payoff

        \end{array} $$
        +

        + In this example, the underlying stock price at expiration is + + $843.19 + + . You traded two lower-strike puts at + + $825.00 + + for + + $4.70 + + each and a higher-strike put at + + $835.00 + + for + + $10.90 + + . +

        The following chart shows the payoff at expiration:

        - Strategy payoff decomposition and analysis of short put backspread + Short Put Backspread strategy payoff at expiration

        The maximum profit is $K^{high} - K^{low} - P^{high}_0 + P^{low}_0 \times 2$, which occurs when the underlying price is exactly at the lower strike at expiry.

        @@ -357921,7 +359966,7 @@

        Introduction

        Reality models make backtests as realistic as possible to how the strategy would perform in live trading. These reality models model the behavior of things like the portfolio, brokerage, fills, slippage, options, and strategy - + capacity . Some reality models are set on a security basis and some are set at the portfolio level. The default models assume you trade highly liquid assets. If you trade high volumes or on illiquid assets, you should create custom reality models to be more realistic. @@ -378804,8 +380849,8 @@

        Introduction

        SetBrokerageModel(BrokerageName.QuantConnectBrokerage, AccountType.Cash);
         SetBrokerageModel(BrokerageName.QuantConnectBrokerage, AccountType.Margin); // Overrides the default account type
        -
        self.set_brokerage_model(BrokerageName.QUANTCONNECT_BROKERAGE) # Defaults to margin account
        -self.set_brokerage_model(BrokerageName.QUANTCONNECT_BROKERAGE, AccountType.MARGIN) # Overrides the default account type
        +
        self.set_brokerage_model(BrokerageName.QUANT_CONNECT_BROKERAGE) # Defaults to margin account
        +self.set_brokerage_model(BrokerageName.QUANT_CONNECT_BROKERAGE, AccountType.MARGIN) # Overrides the default account type

        For more information about this model, see the @@ -395506,7 +397551,7 @@

        Model Structure

        option.set_price_model(CustomOptionPriceModel()) # Define the custom price model -class CustomOptionPriceModel(OptionPriceModel): +class CustomOptionPriceModel(CurrentPriceOptionPriceModel): def evaluate(self, parameters: OptionPriceModelParameters) -> OptionPriceModelResult: contract = parameters.contract underlying = contract.underlying_last_price @@ -463889,6 +465934,345 @@

        Example for Plotting

        +

        Example for Logging

        + + +

        + You can use the Object Store to log order data from your backtests and live algorithms, then analyze it in the Research Environment. The following example demonstrates how to log orders from an + + Exponential Moving Average + + (EMA) cross strategy to a text file in the Object Store. +

        +
          +
        1. + Create an algorithm, add a daily equity subscription, and create two EMA indicators. +
        2. +
          +
          public class ObjectStoreLoggingAlgorithm : QCAlgorithm
          +{
          +    private ExponentialMovingAverage _emaShort;
          +    private ExponentialMovingAverage _emaLong;
          +    private Symbol _symbol;
          +    private string _content;
          +
          +    public override void Initialize()
          +    {
          +        _symbol = AddEquity("SPY", Resolution.Daily).Symbol;
          +        _emaShort = EMA(_symbol, 10);
          +        _emaLong = EMA(_symbol, 30);
          +        // Add a header row.
          +        _content = "Time,Symbol,Price,Quantity,Tag\n";
          +    }
          +}
          +
          class ObjectStoreLoggingAlgorithm(QCAlgorithm):
          +    def initialize(self):
          +        self._symbol = self.add_equity("SPY", Resolution.DAILY).symbol
          +        self._ema_short = self.ema(self._symbol, 10)
          +        self._ema_long = self.ema(self._symbol, 30)
          +        # Add a header row.
          +        self._content = 'Time,Symbol,Price,Quantity,Tag\n'
          +
          +

          + The algorithm saves + + _content + + + self._content + + to the Object Store at the end of the backtest. +

          +
        3. + In the + + OnData + + method, place market orders when the EMAs cross. +
        4. +
          +
          public override void OnData(Slice data)
          +{
          +    if (!_emaShort.IsReady || !_emaLong.IsReady) return;
          +
          +    if (_emaShort > _emaLong && !Portfolio[_symbol].IsLong)
          +    {
          +        MarketOrder(_symbol, 100, tag: $"BUY: ema-short: {_emaShort:F4}  >  ema-long: {_emaLong:F4}");
          +    }
          +    else if (_emaShort < _emaLong && !Portfolio[_symbol].IsShort)
          +    {
          +        MarketOrder(_symbol, -100, tag: $"SELL: ema-short: {_emaShort:F4}  <  ema-long: {_emaLong:F4}");
          +    }
          +}
          +
          def on_data(self, data: Slice):
          +    if not self._ema_short.is_ready or not self._ema_long.is_ready:
          +        return
          +
          +    ema_short = self._ema_short.current.value
          +    ema_long = self._ema_long.current.value
          +    if ema_short > ema_long and not self.portfolio[self._symbol].is_long:
          +        self.market_order(self._symbol, 100, tag=f'BUY: ema-short: {ema_short:.4f}  >  ema-long: {ema_long:.4f}')
          +    elif ema_short < ema_long and not self.portfolio[self._symbol].is_short:
          +        self.market_order(self._symbol, -100, tag=f'SELL: ema-short: {ema_short:.4f}  <  ema-long: {ema_long:.4f}')
          +
          +
        5. + In the + + OnOrderEvent + + method, log each fill to the content string. +
        6. +
          +
          public override void OnOrderEvent(OrderEvent orderEvent)
          +{
          +    if (orderEvent.Status != OrderStatus.Filled) return;
          +
          +    _content += $"{orderEvent.UtcTime:yyyy-MM-dd},{orderEvent.Symbol},{orderEvent.FillPrice}," +
          +        $"{orderEvent.FillQuantity},{orderEvent.Ticket.Tag}\n";
          +}
          +
          def on_order_event(self, order_event: OrderEvent):
          +    if order_event.status != OrderStatus.FILLED:
          +        return
          +
          +    self._content += (
          +        f'{order_event.utc_time.strftime("%Y-%m-%d")},'
          +        f'{order_event.symbol},'
          +        f'{order_event.fill_price},'
          +        f'{order_event.fill_quantity},'
          +        f'{order_event.ticket.tag}\n'
          +    )
          +
          +
        7. + In the + + OnEndOfAlgorithm + + method, save the content to the Object Store. +
        8. +
          +
          public override void OnEndOfAlgorithm()
          +{
          +    var key = $"{ProjectId}-{AlgorithmId}.txt";
          +    ObjectStore.Save(key, _content);
          +    Log($"Saved order log to Object Store: {key}");
          +}
          +
          def on_end_of_algorithm(self):
          +    key = f'{self.project_id}-{self.algorithm_id}.txt'
          +    self.object_store.save(key, self._content)
          +    self.log(f'Saved order log to Object Store: {key}')
          +
          +
        9. + + Open the Research Environment + + and create a + + QuantBook + + . +
        10. +
          +
          // Execute the following command in first
          +#load "../Initialize.csx"
          +
          +// Create a QuantBook object
          +#load "../QuantConnect.csx"
          +using QuantConnect;
          +using QuantConnect.Research;
          +
          +var qb = new QuantBook();
          +
          qb = QuantBook()
          +
          +
        11. + Read the data from the Object Store. +
        12. +
          +
          // Replace the key with the one from your backtest log.
          +var content = qb.ObjectStore.Read("<project-id>-<algorithm-id>.txt");
          +
          # Replace the key with the one from your backtest log.
          +content = qb.object_store.read("<project-id>-<algorithm-id>.txt")
          +
          +

          + The key you provide must be the same key you used to save the object. Check the backtest log for the exact key. +

          +
        13. + Split the content into lines. +
        14. +
          +
          lines = content.strip().split('\n')
          +# Display the header.
          +print(lines[0])
          +
          +
        15. + Parse the content into a list of string arrays. +
        16. +
          +
          var lines = content.Split('\n')
          +    .Where(line => !string.IsNullOrWhiteSpace(line))
          +    .Select(line => line.Split(','))
          +    .ToList();
          +
          +// Display the header row.
          +var header = lines.First();
          +Console.WriteLine(string.Join(" | ", header));
          +
          +
        17. + Display the first few rows (head) and last few rows (tail). +
        18. +
          +
          # Show the first 5 data rows.
          +for line in lines[1:6]:
          +    print(line)
          +
          +# Show the last 5 data rows.
          +for line in lines[-5:]:
          +    print(line)
          +
          +
        19. + Display the first few rows (head) and last few rows (tail). +
        20. +
          +
          // Skip the header, then take the first 5 data rows.
          +var dataRows = lines.Skip(1).ToList();
          +Console.WriteLine("Head:");
          +foreach (var row in dataRows.Take(5))
          +{
          +    Console.WriteLine(string.Join(" | ", row));
          +}
          +
          +// Show the last 5 data rows.
          +Console.WriteLine("\nTail:");
          +foreach (var row in dataRows.TakeLast(5))
          +{
          +    Console.WriteLine(string.Join(" | ", row));
          +}
          +
          +
        21. + Search for all rows tagged as "BUY". +
        22. +
          +
          # Filter lines that contain "BUY".
          +buy_orders = [line for line in lines[1:] if 'BUY' in line]
          +for line in buy_orders:
          +    print(line)
          +
          +
        23. + Search for all rows tagged as "BUY". +
        24. +
          +
          // Filter rows where Tag contains "BUY".
          +var buyOrders = dataRows
          +    .Where(row => row.Length > 4 && row[4].Contains("BUY"))
          +    .ToList();
          +
          +Console.WriteLine("Buy Orders:");
          +foreach (var row in buyOrders)
          +{
          +    Console.WriteLine(string.Join(" | ", row));
          +}
          +
          +
        +
        +
        public class ObjectStoreLoggingAlgorithm : QCAlgorithm
        +{
        +    private ExponentialMovingAverage _emaShort;
        +    private ExponentialMovingAverage _emaLong;
        +    private Symbol _symbol;
        +    private string _content;
        +
        +    public override void Initialize()
        +    {
        +        SetStartDate(2024, 1, 1);
        +        SetEndDate(2024, 12, 31);
        +        SetCash(100000);
        +
        +        _symbol = AddEquity("SPY", Resolution.Daily).Symbol;
        +        // Create short and long EMA indicators.
        +        _emaShort = EMA(_symbol, 10);
        +        _emaLong = EMA(_symbol, 30);
        +        // Add a header row.
        +        _content = "Time,Symbol,Price,Quantity,Tag\n";
        +    }
        +
        +    public override void OnData(Slice data)
        +    {
        +        if (!_emaShort.IsReady || !_emaLong.IsReady) return;
        +
        +        // Place a market order when the EMAs cross.
        +        if (_emaShort > _emaLong && !Portfolio[_symbol].IsLong)
        +        {
        +            MarketOrder(_symbol, 100, tag: $"BUY: ema-short: {_emaShort:F4}  >  ema-long: {_emaLong:F4}");
        +        }
        +        else if (_emaShort < _emaLong && !Portfolio[_symbol].IsShort)
        +        {
        +            MarketOrder(_symbol, -100, tag: $"SELL: ema-short: {_emaShort:F4}  <  ema-long: {_emaLong:F4}");
        +        }
        +    }
        +
        +    public override void OnOrderEvent(OrderEvent orderEvent)
        +    {
        +        if (orderEvent.Status != OrderStatus.Filled) return;
        +
        +        // Log each fill as a row.
        +        _content += $"{orderEvent.UtcTime:yyyy-MM-dd},{orderEvent.Symbol},{orderEvent.FillPrice}," +
        +            $"{orderEvent.FillQuantity},{orderEvent.Ticket.Tag}\n";
        +    }
        +
        +    public override void OnEndOfAlgorithm()
        +    {
        +        // Save the order log to the Object Store.
        +        var key = $"{ProjectId}-{AlgorithmId}.txt";
        +        ObjectStore.Save(key, _content);
        +        Log($"Saved order log to Object Store: {key}");
        +    }
        +}
        +
        class ObjectStoreLoggingAlgorithm(QCAlgorithm):
        +    def initialize(self) -> None:
        +        self.set_start_date(2024, 1, 1)
        +        self.set_end_date(2024, 12, 31)
        +        self.set_cash(100000)
        +
        +        self._symbol = self.add_equity("SPY", Resolution.DAILY).symbol
        +        # Create short and long EMA indicators.
        +        self._ema_short = self.ema(self._symbol, 10)
        +        self._ema_long = self.ema(self._symbol, 30)
        +        # Add a header row.
        +        self._content = 'Time,Symbol,Price,Quantity,Tag\n'
        +
        +    def on_data(self, data: Slice) -> None:
        +        if not self._ema_short.is_ready or not self._ema_long.is_ready:
        +            return
        +
        +        # Place a market order when the EMAs cross.
        +        ema_short = self._ema_short.current.value
        +        ema_long = self._ema_long.current.value
        +        if ema_short > ema_long and not self.portfolio[self._symbol].is_long:
        +            self.market_order(self._symbol, 100, tag=f'BUY: ema-short: {ema_short:.4f}  >  ema-long: {ema_long:.4f}')
        +        elif ema_short < ema_long and not self.portfolio[self._symbol].is_short:
        +            self.market_order(self._symbol, -100, tag=f'SELL: ema-short: {ema_short:.4f}  <  ema-long: {ema_long:.4f}')
        +
        +    def on_order_event(self, order_event: OrderEvent) -> None:
        +        if order_event.status != OrderStatus.FILLED:
        +            return
        +
        +        # Log each fill as a row.
        +        self._content += (
        +            f'{order_event.utc_time.strftime("%Y-%m-%d")},'
        +            f'{order_event.symbol},'
        +            f'{order_event.fill_price},'
        +            f'{order_event.fill_quantity},'
        +            f'{order_event.ticket.tag}\n'
        +        )
        +
        +    def on_end_of_algorithm(self) -> None:
        +        # Save the order log to the Object Store.
        +        key = f'{self.project_id}-{self.algorithm_id}.txt'
        +        self.object_store.save(key, self._content)
        +        self.log(f'Saved order log to Object Store: {key}')
        +
        + + +

        Example of Custom Data

        @@ -465602,6 +467986,21 @@

        Save Models

        event handler so that saving multiple times doesn't slow down your backtest.

        +

        + If you train models outside of QuantConnect (for example, on your local machine), you can upload the model files to the Object Store with the + + LEAN CLI + + or the + + Cloud API + + . For library-specific upload instructions, see the Upload Models section in each + + Popular Libraries + + tutorial. +

        To view examples of storing library-specific models, see @@ -465628,7 +468027,7 @@

        Load Models

        initialize method, check if the Object Store already contains the model. To avoid - + look-ahead bias in backtests, don't train your model on the same data you use to test the model. @@ -466682,6 +469081,58 @@

        Save Models

        +

        Upload Models

        + + +

        + If you train models locally or in another environment, you can upload the model files to the Object Store so your algorithms can use them. Use the model key that matches the key your algorithm expects when it calls + + object_store.get_file_path + + . +

        +

        + Follow one of these approaches to upload your model files: +

        +

        + LEAN CLI +

        +

        + Run the + + + lean cloud object-store set + + + command to upload a local file to the Object Store. +

        +
        +
        $ lean cloud object-store set <projectId>/model <pathTo>/model
        +
        +

        + Replace + + <projectId> + + with your project Id and + + <pathTo> + + with the path to the local model file. +

        +

        + Cloud API +

        +

        + Use the + + Upload Object Store Files + + endpoint to upload a model file through the API. +

        + + +

        Load Models

        @@ -467569,6 +470020,58 @@

        Save Models

        +

        Upload Models

        + + +

        + If you train models locally or in another environment, you can upload the model files to the Object Store so your algorithms can use them. Use the model key that matches the key your algorithm expects when it calls + + object_store.get_file_path + + . +

        +

        + Follow one of these approaches to upload your model files: +

        +

        + LEAN CLI +

        +

        + Run the + + + lean cloud object-store set + + + command to upload a local file to the Object Store. +

        +
        +
        $ lean cloud object-store set <projectId>/copula <pathTo>/copula
        +
        +

        + Replace + + <projectId> + + with your project Id and + + <pathTo> + + with the path to the local model file. +

        +

        + Cloud API +

        +

        + Use the + + Upload Object Store Files + + endpoint to upload a model file through the API. +

        + + +

        Load Models

        @@ -468412,6 +470915,59 @@

        Save Models

        +

        Upload Models

        + + +

        + If you train models locally or in another environment, you can upload the model files to the Object Store so your algorithms can use them. Use the model key that matches the key your algorithm expects when it calls + + object_store.get_file_path + + . +

        +

        + Follow one of these approaches to upload your model files: +

        +

        + LEAN CLI +

        +

        + Run the + + + lean cloud object-store set + + + command to upload a local file to the Object Store. +

        +
        +
        $ lean cloud object-store set <projectId>/transformer <pathTo>/transformer
        +
        $ lean cloud object-store set <projectId>/regressor <pathTo>/regressor
        +
        +

        + Replace + + <projectId> + + with your project Id and + + <pathTo> + + with the path to the local model file. +

        +

        + Cloud API +

        +

        + Use the + + Upload Object Store Files + + endpoint to upload a model file through the API. +

        + + +

        Load Models

        @@ -468914,6 +471470,58 @@

        Save Models

        +

        Upload Models

        + + +

        + If you train models locally or in another environment, you can upload the model files to the Object Store so your algorithms can use them. Use the model key that matches the key your algorithm expects when it calls + + object_store.get_file_path + + . +

        +

        + Follow one of these approaches to upload your model files: +

        +

        + LEAN CLI +

        +

        + Run the + + + lean cloud object-store set + + + command to upload a local file to the Object Store. +

        +
        +
        $ lean cloud object-store set <projectId>/model.hmm <pathTo>/model.hmm
        +
        +

        + Replace + + <projectId> + + with your project Id and + + <pathTo> + + with the path to the local model file. +

        +

        + Cloud API +

        +

        + Use the + + Upload Object Store Files + + endpoint to upload a model file through the API. +

        + + +

        Load Models

        @@ -469465,6 +472073,58 @@

        Save Models

        +

        Upload Models

        + + +

        + If you train models locally or in another environment, you can upload the model files to the Object Store so your algorithms can use them. Use the model key that matches the key your algorithm expects when it calls + + object_store.get_file_path + + . +

        +

        + Follow one of these approaches to upload your model files: +

        +

        + LEAN CLI +

        +

        + Run the + + + lean cloud object-store set + + + command to upload a local file to the Object Store. +

        +
        +
        $ lean cloud object-store set <projectId>/model.keras <pathTo>/model.keras
        +
        +

        + Replace + + <projectId> + + with your project Id and + + <pathTo> + + with the path to the local model file. +

        +

        + Cloud API +

        +

        + Use the + + Upload Object Store Files + + endpoint to upload a model file through the API. +

        + + +

        Load Models

        @@ -470045,6 +472705,58 @@

        Save Models

        +

        Upload Models

        + + +

        + If you train models locally or in another environment, you can upload the model files to the Object Store so your algorithms can use them. Use the model key that matches the key your algorithm expects when it calls + + object_store.get_file_path + + . +

        +

        + Follow one of these approaches to upload your model files: +

        +

        + LEAN CLI +

        +

        + Run the + + + lean cloud object-store set + + + command to upload a local file to the Object Store. +

        +
        +
        $ lean cloud object-store set <projectId>/model <pathTo>/model
        +
        +

        + Replace + + <projectId> + + with your project Id and + + <pathTo> + + with the path to the local model file. +

        +

        + Cloud API +

        +

        + Use the + + Upload Object Store Files + + endpoint to upload a model file through the API. +

        + + +

        Load Models

        @@ -470614,6 +473326,58 @@

        Save Models

        +

        Upload Models

        + + +

        + If you train models locally or in another environment, you can upload the model files to the Object Store so your algorithms can use them. Use the model key that matches the key your algorithm expects when it calls + + object_store.get_file_path + + . +

        +

        + Follow one of these approaches to upload your model files: +

        +

        + LEAN CLI +

        +

        + Run the + + + lean cloud object-store set + + + command to upload a local file to the Object Store. +

        +
        +
        $ lean cloud object-store set <projectId>/model <pathTo>/model
        +
        +

        + Replace + + <projectId> + + with your project Id and + + <pathTo> + + with the path to the local model file. +

        +

        + Cloud API +

        +

        + Use the + + Upload Object Store Files + + endpoint to upload a model file through the API. +

        + + +

        Load Models

        @@ -471156,6 +473920,58 @@

        Save Models

        +

        Upload Models

        + + +

        + If you train models locally or in another environment, you can upload the model files to the Object Store so your algorithms can use them. Use the model key that matches the key your algorithm expects when it calls + + object_store.get_file_path + + . +

        +

        + Follow one of these approaches to upload your model files: +

        +

        + LEAN CLI +

        +

        + Run the + + + lean cloud object-store set + + + command to upload a local file to the Object Store. +

        +
        +
        $ lean cloud object-store set <projectId>/model.keras <pathTo>/model.keras
        +
        +

        + Replace + + <projectId> + + with your project Id and + + <pathTo> + + with the path to the local model file. +

        +

        + Cloud API +

        +

        + Use the + + Upload Object Store Files + + endpoint to upload a model file through the API. +

        + + +

        Load Models

        @@ -471630,6 +474446,58 @@

        Save Models

        +

        Upload Models

        + + +

        + If you train models locally or in another environment, you can upload the model files to the Object Store so your algorithms can use them. Use the model key that matches the key your algorithm expects when it calls + + object_store.get_file_path + + . +

        +

        + Follow one of these approaches to upload your model files: +

        +

        + LEAN CLI +

        +

        + Run the + + + lean cloud object-store set + + + command to upload a local file to the Object Store. +

        +
        +
        $ lean cloud object-store set <projectId>/model.hdf5 <pathTo>/model.hdf5
        +
        +

        + Replace + + <projectId> + + with your project Id and + + <pathTo> + + with the path to the local model file. +

        +

        + Cloud API +

        +

        + Use the + + Upload Object Store Files + + endpoint to upload a model file through the API. +

        + + +

        Load Models

        @@ -472155,6 +475023,58 @@

        Save Models

        +

        Upload Models

        + + +

        + If you train models locally or in another environment, you can upload the model files to the Object Store so your algorithms can use them. Use the model key that matches the key your algorithm expects when it calls + + object_store.get_file_path + + . +

        +

        + Follow one of these approaches to upload your model files: +

        +

        + LEAN CLI +

        +

        + Run the + + + lean cloud object-store set + + + command to upload a local file to the Object Store. +

        +
        +
        $ lean cloud object-store set <projectId>/model <pathTo>/model
        +
        +

        + Replace + + <projectId> + + with your project Id and + + <pathTo> + + with the path to the local model file. +

        +

        + Cloud API +

        +

        + Use the + + Upload Object Store Files + + endpoint to upload a model file through the API. +

        + + +

        Load Models

        @@ -477649,7 +480569,7 @@

        Introduction

        Manual universes can be prone to - + look-ahead bias . For example, if you select a set of securities that have performed well during the backtest period, you are incorporating information from the future into the backtest and the algorithm may underperform in live mode. @@ -489077,7 +491997,7 @@

        Maximum Sharpe Ratio Optimizer

        MaximumSharpeRatioPortfolioOptimizer seeks to maximize the portfolio - + Sharpe Ratio . @@ -490596,7 +493516,7 @@

        Maximum Security Drawdown Model

        + Data Category @@ -48343,7 +48688,7 @@

        Prepare Data

        For example, to use the last third of data to test the model, run:

        -
        X_train, X_test, y_train, y_test = train_test_split(X, Y, test_size=0.33, shuffle=False)
        +
        X_train, X_test, y_train, y_test = train_test_split(X.values, Y.values, test_size=0.33, shuffle=False)
        @@ -48367,7 +48712,7 @@

        1. - Set the number of layers, their number of nodes, the number of epoch and the learning rate. + Set the number of layers, their number of nodes, the number of epochs, and the learning rate.
        2. num_factors = X_test.shape[1]
          @@ -48378,10 +48723,10 @@ 

          learning_rate = 0.0001

        3. - Create hidden layers with the set number of layer and their corresponding number of nodes. + Create hidden layers with the set number of layers and their corresponding number of nodes.
        4. - In this example, we're constructing the model with the in-built Keras API, with Relu activator for non-linear activation of each tensors. + This example uses the Keras API with ReLU activation for non-linear activation of each tensor.

          model = tf.keras.Sequential([
          @@ -48395,7 +48740,7 @@ 

          Select an optimizer.

          - We're using Adam optimizer in this example. You may also consider others like SGD. + This example uses the Adam optimizer. You can also use others like SGD.

          optimizer = tf.keras.optimizers.Adam(learning_rate=learning_rate)
          @@ -48404,7 +48749,7 @@

          Define the loss function.

          - In the context of numerical regression, we use MSE as our objective function. If you're doing classification, cross entropy would be more suitable. + For numerical regression, use MSE as the objective function. For classification, cross entropy is more suitable.

          def loss_mse(target_y, predicted_y):
          @@ -48415,10 +48760,14 @@ 

          Train the Model

          - Iteratively train the model by the set epoch number. The model will train adaptively by the gradient provided by the loss function with the selected optimizer. + Follow these steps to train the model:

          -
          -
          for i in range(epochs):
          +  
            +
          1. + Iteratively train the model over the set number of epochs. The model trains adaptively using the gradient from the loss function with the selected optimizer. +
          2. +
            +
            for i in range(epochs):
                 with tf.GradientTape() as t:
                     loss = loss_mse(y_train, model(X_train))
             
            @@ -48429,7 +48778,8 @@ 

            jac = t.gradient(loss, model.trainable_weights) optimizer.apply_gradients(zip(jac, model.trainable_weights))

            -
            +
          +

        @@ -48437,10 +48787,18 @@

        Test Models

        - To test the model, we'll setup a method to plot test set predictions ontop of the SPY price. + You need to + + build and train the model + + before you test its performance. If you have trained the model, test it on the out-of-sample data. Follow these steps to test the model:

        -
        -
        def test_model(actual, title, X):
        +  
          +
        1. + Create a helper function to plot the test set predictions on top of the SPY price. +
        2. +
          +
          def test_model(actual, title, X):
               prediction = model(X).numpy()
               prediction = prediction.reshape(-1, 1)
           
          @@ -48451,11 +48809,16 @@ 

          Test Models

          plt.xlabel("Time step") plt.ylabel("SPY Price") plt.legend() - plt.show() - -test_model(y_test, "Test Set Results from Original Model", X_test)
          -
          - Tensorflow model performance + plt.show()
        +
        +
      3. + Call the helper function with the testing dataset. +
      4. +
        +
        test_model(y_test, "Test Set Results from Original Model", X_test)
        +
        + + TensorFlow model performance @@ -48465,7 +48828,7 @@

        Store Models

        You can save and load - TensorFlow + tensorflow models using the Object Store.

        @@ -48479,16 +48842,16 @@

      5. Set the key name of the model to be stored in the Object Store.
      6. -
        -
        model_key = "model.keras"
        -

        - Note that the model has to have the suffix - + The key must end with a + .keras - - . + + extension for the native Keras format (recommended).

        +
        +
        model_key = "model.keras"
        +
      7. Call the @@ -48510,13 +48873,13 @@

        save - method with the model and file path. + method with the file path.

      8. model.save(file_name)
      9. - Save the model to the file path. + Save the model to the Object Store.
      10. qb.object_store.save(model_key)
        @@ -48530,21 +48893,34 @@

        1. - Get the file path from the Object Store. + Call the + + GetFilePath + + + get_file_path + + method with the key.
        2. file_name = qb.object_store.get_file_path(model_key)
          +

          + This method returns the path where the model is stored. +

        3. - Restore the + Call the - TensorFlow + load_model - model from the saved path. + method with the file path.
        4. model = tf.keras.models.load_model(file_name)
          +

          + This method returns the saved model. +

        @@ -48553,20 +48929,20 @@

        Examples

        - The following examples demonstrate some common practices for using the Tensorflow library. + The following examples demonstrate some common practices for using the tensorflow library.

        Example 1: Predict Next Return

        - The following research notebook uses + The following research notebook uses a - Tensorflow + tensorflow machine learning model to predict the next day's return by the previous 5 days' close price differencing.

        -
        # Import the Tensorflow library and others.
        +   
        # Import the tensorflow library and others.
         import tensorflow as tf
         from sklearn.model_selection import train_test_split
         
        @@ -48590,31 +48966,31 @@ 

        Y = data['close'].diff(-1) Y = Y[lookback:-1].reset_index(drop=True) -# Split the data into training and testing datasets. For example, to use the last third of data to test the model. -X_train, X_test, y_train, y_test = train_test_split(X, Y, test_size=0.33, shuffle=False) +# Split the data into training and testing datasets. For example, use the last third of data to test the model. +X_train, X_test, y_train, y_test = train_test_split(X.values, Y.values, test_size=0.33, shuffle=False) -# Set the number of layers, their number of nodes, the number of epoch and the learning rate. +# Set the number of layers, their number of nodes, the number of epochs, and the learning rate. num_factors = X_test.shape[1] num_neurons_1 = 10 num_neurons_2 = 10 num_neurons_3 = 5 epochs = 20 learning_rate = 0.0001 -# Create hidden layers with the set number of layer and their corresponding number of nodes. -# In this example, we're constructing the model with the in-built Keras API, with Relu activator for non-linear activation of each tensors. +# Create hidden layers with the set number of layers and their corresponding number of nodes. +# This example uses the Keras API with ReLU activation for non-linear activation of each tensor. model = tf.keras.Sequential([ tf.keras.layers.Dense(num_neurons_1, activation=tf.nn.relu, input_shape=(num_factors,)), # input shape required tf.keras.layers.Dense(num_neurons_2, activation=tf.nn.relu), tf.keras.layers.Dense(num_neurons_3, activation=tf.nn.relu), tf.keras.layers.Dense(1) ]) -# We're using Adam optimizer in this example. You may also consider others like SGD. +# This example uses the Adam optimizer. You can also use others like SGD. optimizer = tf.keras.optimizers.Adam(learning_rate=learning_rate) -# Define the loss function. In the context of numerical regression, we use MSE as our objective function. If you're doing classification, cross entropy would be more suitable. +# Define the loss function. For numerical regression, use MSE as the objective function. For classification, cross entropy is more suitable. def loss_mse(target_y, predicted_y): return tf.reduce_mean(tf.square(target_y - predicted_y)) -# Iteratively train the model by the set epoch number. The model will train adaptively by the gradient provided by the loss function with the selected optimizer. +# Iteratively train the model over the set number of epochs. The model trains adaptively using the gradient from the loss function with the selected optimizer. for i in range(epochs): with tf.GradientTape() as t: loss = loss_mse(y_train, model(X_train)) @@ -48627,7 +49003,7 @@

        jac = t.gradient(loss, model.trainable_weights) optimizer.apply_gradients(zip(jac, model.trainable_weights)) -# To test the model, we'll setup a method to plot test set predictions ontop of the SPY price. +# Create a helper function to plot the test set predictions on top of the SPY price. def test_model(actual, title, X): prediction = model(X).numpy() prediction = prediction.reshape(-1, 1) @@ -48813,25 +49189,36 @@

        Train Models

        - Instead of using real-time comparison, we could apply a technique call Dynamic Time Wrapping (DTW) with Barycenter Averaging (DBA). Intuitively, it is a technique of averaging a few time-series into a single one without losing much of their information. Since not all time-series would move efficiently like in ideal EMH assumption, this would allow similarity analysis of different time-series with sticky lags. Check the technical details from + Instead of using real-time comparison, apply Dynamic Time Warping (DTW) with Barycenter Averaging (DBA). This technique averages multiple time-series into a single one without losing much of their information. Since not all time-series move efficiently as in ideal EMH assumptions, this approach enables similarity analysis of different time-series with sticky lags. Check the technical details from the tslearn documentation page .

        - Dynamic time wraping barycenter averaging visualization + Dynamic time warping barycenter averaging visualization

        - We then can separate different clusters by KMean after DBA. + Follow these steps to train the model:

        -
        -
        # Set up the Time Series KMean model with soft DBA.
        -km = TimeSeriesKMeans(n_clusters=6,   # We have 6 main groups
        +  
          +
        1. + Set up the Time Series KMeans model with soft DBA. +
        2. +
          +
          km = TimeSeriesKMeans(n_clusters=6,   # 6 main groups
                                 metric="softdtw",  # soft for differentiable
          -                      random_state=0)
          -
          -# Fit the model.
          -km.fit(standard_close.T)
          -
          + random_state=0)
        +
        +
      11. + Call the + + fit + + method with the prepared data. +
      12. +
        +
        km.fit(standard_close.T)
        +
        + @@ -48839,25 +49226,33 @@

        Test Models

        - We visualize the clusters and their corresponding underlying series. + You need to + + build and train the model + + before you test its performance. If you have trained the model, visualize the clusters and their corresponding underlying series. Follow these steps to test the model:

        1. - Predict with the label of the data. + Call the + + predict + + method with the data to get the cluster labels.
        2. labels = km.predict(standard_close.T)
        3. - Create a class to aid plotting. + Create a helper function to plot the clusters.
        4. def plot_helper(ts):
          -    # plot all points of the data set
          +    # Plot all points of the data set
               for i in range(ts.shape[0]):
                   plt.plot(ts[i, :], "k-", alpha=.2)
          -        
          -    # plot the given barycenter of them
          +
          +    # Plot the given barycenter of them
               barycenter = softdtw_barycenter(ts, gamma=1.)
               plt.plot(barycenter, "r-", linewidth=2)
          @@ -48870,12 +49265,12 @@

          Test Models

          for i in set(labels): # Select the series in the i-th cluster. X = standard_close.iloc[:, [n for n, k in enumerate(labels) if k == i]].values - + # Plot the series and barycenter-averaged series. plt.subplot(len(set(labels)) // 3 + (1 if len(set(labels))%3 != 0 else 0), 3, j) plt.title(f"Cluster {i+1}") plot_helper(X.T) - + j += 1 plt.show()
        @@ -49028,7 +49423,7 @@

        Reference

      13. F. Petitjean, A. Ketterlin, P. Gancarski. (2010). A global averaging method for dynamic time warping, with applications to clustering. - Pattern Recognition. 44(2011). 678-693. Retreived from https://lig-membres.imag.fr/bisson/cours/M2INFO-AIW-ML/papers/PetitJean11.pdf + Pattern Recognition. 44(2011). 678-693. Retrieved from https://lig-membres.imag.fr/bisson/cours/M2INFO-AIW-ML/papers/PetitJean11.pdf
      14. @@ -49048,11 +49443,11 @@

        Example 1: DBA Clustering

        - The following research notebook uses + The following research notebook uses a tslearn - machine learning model to cluster a collection of stocks applying Dynamic Time Wrapping (DTW) with Barycenter Averaging (DBA). + machine learning model to cluster a collection of stocks applying Dynamic Time Warping (DTW) with Barycenter Averaging (DBA).

        # Import the tslearn library.
        @@ -49062,10 +49457,10 @@ 

        # Instantiate the QuantBook for researching. qb = QuantBook() # Request the daily history of the collection of stocks in the date range to be studied. -tickers = ["SPY", "QQQ", "DIA", - "AAPL", "MSFT", "TSLA", - "IEF", "TLT", "SHV", "SHY", - "GLD", "IAU", "SLV", +tickers = ["SPY", "QQQ", "DIA", + "AAPL", "MSFT", "TSLA", + "IEF", "TLT", "SHV", "SHY", + "GLD", "IAU", "SLV", "USO", "XLE", "XOM"] symbols = [qb.add_equity(ticker, Resolution.DAILY).symbol for ticker in tickers] history = qb.history(symbols, datetime(2020, 1, 1), datetime(2022, 2, 20)) @@ -49076,23 +49471,23 @@

        # Standardize the data for faster convergence. standard_close = (log_close - log_close.mean()) / log_close.std() -# Set up the Time Series KMean model with soft DBA. -km = TimeSeriesKMeans(n_clusters=6, # We have 6 main groups +# Set up the Time Series KMeans model with soft DBA. +km = TimeSeriesKMeans(n_clusters=6, # 6 main groups metric="softdtw", # soft for differentiable random_state=0) # Fit the model. km.fit(standard_close.T) -# Call the predict method with the testing dataset to get the prediction from the model. +# Call the predict method with the data to get the cluster labels. labels = km.predict(standard_close.T) -# Create a class to aid plotting. +# Create a helper function to plot the clusters. def plot_helper(ts): - # plot all points of the data set + # Plot all points of the data set for i in range(ts.shape[0]): plt.plot(ts[i, :], "k-", alpha=.2) - - # plot the given barycenter of them + + # Plot the given barycenter of them barycenter = softdtw_barycenter(ts, gamma=1.) plt.plot(barycenter, "r-", linewidth=2) # Plot the results. @@ -49101,12 +49496,12 @@

        for i in set(labels): # Select the series in the i-th cluster. X = standard_close.iloc[:, [n for n, k in enumerate(labels) if k == i]].values - + # Plot the series and barycenter-averaged series. plt.subplot(len(set(labels)) // 3 + (1 if len(set(labels))%3 != 0 else 0), 3, j) plt.title(f"Cluster {i+1}") plot_helper(X.T) - + j += 1 plt.show() @@ -49303,17 +49698,21 @@

        Train Models

        - We're about to train a gradient-boosted random forest for future price prediction. + You need to + + prepare the historical data + + for training before you train the model. If you have prepared the data, build and train the model. In this example, train a gradient-boosted random forest for future price prediction. Follow these steps to create the model:

        1. - Split the data for training and testing to evaluate our model. + Split the data for training and testing to evaluate the model.
        2. X_train, X_test, y_train, y_test = train_test_split(X, y)
        3. - Format training set into XGBoost matrix. + Format the training set into an XGBoost matrix.
        4. dtrain = xgb.DMatrix(X_train, label=y_train)
          @@ -49342,23 +49741,31 @@

          Test Models

          - We then make predictions on the testing data set. We compare our Predicted Values with the Expected Values by plotting both to see if our Model has predictive power. + You need to + + build and train the model + + before you test its performance. If you have trained the model, test it on the out-of-sample data. Follow these steps to test the model:

          1. - Format testing set into XGBoost matrix. + Format the testing set into an XGBoost matrix.
          2. dtest = xgb.DMatrix(X_test, label=y_test)
          3. - Predict with the testing set data. + Call the + + predict + + method with the testing dataset.
          4. y_predict = model.predict(dtest)
          5. - Plot the result. + Plot the predicted values against the actual values to assess the model's predictive power.
          6. df = pd.DataFrame({'Real': y_test.flatten(), 'Predicted': y_predict.flatten()})
            @@ -49373,15 +49780,18 @@ 

            Test Models

            Store Models

            -

            - Saving the Model -

            - We dump the model using the + You can save and load - joblib + xgboost - module and save it to Object Store file path. This way, the model doesn't need to be retrained, saving time and computational resources. + models using the Object Store. +

            +

            + Save Models +

            +

            + Follow these steps to save models in the Object Store:

            1. @@ -49391,31 +49801,44 @@

              model_key = "model"

          7. - Call - + Call the + GetFilePath - with the key's name to get the file path. + + get_file_path + + method with the key.
          8. file_name = qb.object_store.get_file_path(model_key)
            +

            + This method returns the file path where the model will be stored. +

          9. - Call dump with the model and file path to save the model to the file path. + Call the + + dump + + method with the model and file path.
          10. joblib.dump(model, file_name)
            +

            + If you dump the model using the + + joblib + + module before you save the model, you don't need to retrain the model. +

          - Loading the Model + Load Models

          - Let's retrieve the model from the Object Store file path and load by - - joblib - - . + You must save a model into the Object Store before you can load it from the Object Store. If you saved a model, follow these steps to load it:

          1. @@ -49447,28 +49870,37 @@

            before you proceed.

          2. - Call - + Call the + GetFilePath - with the key's name to get the file path. + + get_file_path + + method with the key.
          3. file_name = qb.object_store.get_file_path(model_key)
            +

            + This method returns the path where the model is stored. +

          4. - Call + Call the load - with the file path to fetch the saved model. + method with the file path.
          5. loaded_model = joblib.load(file_name)
            +

            + This method returns the saved model. +

          - To ensure loading the model was successfuly, let's test the model. + To verify loading the model was successful, test the model.

          y_pred = loaded_model.predict(dtest)
          @@ -49483,20 +49915,20 @@ 

          Examples

          - The following examples demonstrate some common practices for using the XGBoost library. + The following examples demonstrate some common practices for using the xgboost library.

          Example 1: Predict Next Price

          - The following research notebook uses + The following research notebook uses an - XGBoost + xgboost machine learning model to predict the next day's close price by the previous 5 days' daily closes.

          -
          # Import the XGBoost library and others.
          +   
          # Import the xgboost library and others.
           import xgboost as xgb
           from sklearn.model_selection import train_test_split
           import joblib
          @@ -49509,8 +49941,8 @@ 

          # Obtain the daily fractional differencing in close price to be the features and labels. df = (history['close'] * 0.5 + history['close'].diff() * 0.5)[1:] -# We use the previous 5 day returns as the features to be studied. -# Get the 1-day forward return as the labels for the machine to learn. +# Use the previous 5 day returns as the features. +# Get the 1-day forward return as the labels. n_steps = 5 features = [] labels = [] @@ -49524,10 +49956,10 @@

          X = (features - features.mean()) / features.std() y = (labels - labels.mean()) / labels.std() -# Split the data as a training set and test set for validation. +# Split the data into a training set and test set for validation. X_train, X_test, y_train, y_test = train_test_split(X, y) -# Format training set into XGBoost matrix. +# Format the training set into an XGBoost matrix. dtrain = xgb.DMatrix(X_train, label=y_train) # Train the model with parameters. params = { @@ -49542,11 +49974,11 @@

          } model = xgb.train(params, dtrain, num_boost_round=10) -# Format testing set into XGBoost matrix to test it. +# Format the testing set into an XGBoost matrix. dtest = xgb.DMatrix(X_test, label=y_test) -# Predict with the testing set data. +# Call the predict method with the testing dataset. y_predict = model.predict(dtest) -# Plot the result. +# Plot the predicted values against the actual values. df = pd.DataFrame({'Real': y_test.flatten(), 'Predicted': y_predict.flatten()}) df.plot(title='Model Performance: predicted vs actual closing price', figsize=(15, 10)) plt.show() diff --git a/single-page/Quantconnect-Writing-Algorithms.html b/single-page/Quantconnect-Writing-Algorithms.html index 8ad26ebe9b..e52c6213e0 100644 --- a/single-page/Quantconnect-Writing-Algorithms.html +++ b/single-page/Quantconnect-Writing-Algorithms.html @@ -51,10 +51,11 @@

          Table of Content

        5. 1.7 Python and LEAN
        6. 1.8 Duck Typing
        7. 1.9 Research Guide
        8. -
        9. 1.10 Globals and Statics
        10. -
        11. 1.11 Libraries
        12. -
        13. 1.12 Debugging Tools
        14. -
        15. 1.13 Glossary
        16. +
        17. 1.10 Behavioral Finance
        18. +
        19. 1.11 Globals and Statics
        20. +
        21. 1.12 Libraries
        22. +
        23. 1.13 Debugging Tools
        24. +
        25. 1.14 Glossary
        26. 2 Initialization
        27. 3 Securities
        28. 3.1 Key Concepts
        29. @@ -868,7 +869,7 @@

          Batch vs Stream Analysis

          Backtesting platforms come in two general varieties, batch processing and event streaming. Batch processing backtesting is much simpler. It loads all data into an array and passes it to your algorithm for analysis. Because your algorithm has access to future data points, it is easy to introduce - + look-ahead bias . Most home-grown analysis tools are batch systems. @@ -1265,11 +1266,11 @@

          Asset Portfolio

          portfolio manages the individual securities it contains. It tracks the cost of holding each security. It aggregates the performance of the individual securities in the portfolio to produce statistics like - + net profit and - + drawdown . The portfolio also holds information about each currency in its cashbook. @@ -5085,7 +5086,7 @@

          Look-ahead Bias

          Another culprit of look-ahead bias is adjusted price data. Splits and reverse splits can improve liquidity or attract certain investors, causing the performance of the stock to be different than without the split or reverse split. Wang et al (2014) build a portfolio using the 25 lowest priced stocks in the S&P index based on adjusted and non-adjusted prices. The portfolio based on adjusted prices greatly outperformed the one with raw prices in terms of return and - + Sharpe ratio . In this case, if you were to analyze the low price factor with adjusted prices, it would lead you to believe the factor is very informative, but it would not perform well out-of-sample with raw data. @@ -5518,9 +5519,1079 @@

           

          - +
          +

          Key Concepts

          +

          Behavioral Finance

          +
          +
          +

          Introduction

          + + +

          + Behavioral finance studies how cognitive biases and emotional responses cause investors to make irrational decisions. These systematic errors create persistent market inefficiencies that algorithmic trading strategies can exploit. Understanding the behavioral "why" behind a strategy helps you design better hypotheses, avoid overfitting to noise, and recognize when an edge might disappear. +

          +

          + Traditional finance assumes that market participants act rationally and that prices reflect all available information. In practice, investors consistently deviate from rationality in predictable ways. They follow the crowd, anchor to irrelevant reference points, overreact to recent news, and avoid realizing losses. These patterns repeat across markets and time periods because they stem from deep-seated cognitive tendencies. +

          +

          + Algorithmic trading is well suited to exploit behavioral biases because algorithms execute rules without emotion. However, the algo trader who designs the rules is still susceptible to the same biases. Knowing which biases drive your strategy and which biases threaten your decision-making process makes you a more effective quant. +

          +

          + This section covers four strategy families and the biases behind them: +

          +
            +
          • + + Momentum + + - Assets that have performed well recently tend to continue performing well. Exploits herd mentality, anchoring bias, and confirmation bias. +
          • +
          • + + Mean Reversion + + - Assets that deviate from historical averages tend to revert. Exploits overreaction, the disposition effect, and availability bias. +
          • +
          • + + Value Factor + + - Undervalued assets tend to appreciate as the market corrects its pessimism. Exploits loss aversion, recency bias, and status quo bias. +
          • +
          • + + Quality Factor + + - High-quality companies tend to outperform because investors chase exciting stories instead. Exploits narrative fallacy, overconfidence, and the endowment effect. +
          • +
          +

          + The section also includes a reference page covering the + + 12 most dangerous investment biases + + and + + example algorithms + + for each strategy type. +

          + + + +

          Momentum

          + + +

          + Momentum strategies buy assets that have performed well recently and sell assets that have performed poorly. The core observation is that recent winners tend to keep winning and recent losers tend to keep losing over intermediate time horizons (3 to 12 months). This effect has been documented across equities, bonds, commodities, and currencies. +

          +

          + Behavioral biases that create momentum +

          +

          + Several cognitive biases cause momentum to persist in financial markets. + + Herd mentality + + amplifies trends as investors follow the crowd into rising assets. + + Anchoring bias + + causes slow adjustment to new information, creating gradual price drifts that momentum captures. + + Confirmation bias + + reinforces trends as investors seek information that supports their existing positions and dismiss contradictory signals. +

          +

          + Algorithmic approach +

          +

          + A typical cross-sectional momentum algorithm follows these steps: +

          +
            +
          • + Define a universe of assets (for example, US equities with sufficient liquidity). +
          • +
          • + Rank each asset by its trailing return over a lookback period (commonly 12 months with a 1-month skip to avoid short-term reversal). +
          • +
          • + Go long the top decile of performers. Optionally, go short the bottom decile. +
          • +
          • + Rebalance the portfolio monthly or at a fixed interval. +
          • +
          +

          + The 1-month skip at the end of the lookback period is important because very short-term returns (1 month or less) tend to reverse rather than continue. This short-term reversal is a separate effect driven by liquidity and microstructure, not the behavioral biases that drive intermediate-term momentum. +

          +

          + Biases that threaten momentum traders +

          +

          + While momentum strategies exploit behavioral biases in others, the algo trader designing and managing the strategy is susceptible to several biases: +

          +
            +
          • + + Herd mentality + + - Piling into popular momentum strategies when they are already crowded increases exposure to momentum crashes. +
          • +
          • + + Anchoring + + - Anchoring to a recent period of strong momentum returns can cause you to over-allocate to the strategy. +
          • +
          • + + Overconfidence + + - Holding momentum positions too long because you believe the trend will continue beyond what the evidence supports. +
          • +
          + + + +

          Mean Reversion

          + + +

          + Mean reversion strategies bet that assets which have deviated significantly from their historical average will revert back. When a price moves far above or below its mean, the strategy takes a contrarian position and profits when the price returns to normal levels. Mean reversion operates on shorter time horizons than momentum, typically days to weeks. +

          +

          + Behavioral biases that create reversion opportunities +

          +

          + Several cognitive biases cause prices to overshoot and then revert. + + Recency bias + + causes investors to overweight recent events and extrapolate short-term trends, creating overreactions that correct over time. The + + disposition effect + + drives investors to sell winners too early and hold losers too long, creating predictable reversion patterns. + + Availability bias + + causes dramatic events to receive outsized attention, producing temporary mispricings that fade as investors process the actual impact. +

          +

          + Algorithmic approach +

          +

          + A typical mean reversion algorithm follows these steps: +

          +
            +
          • + Select one or more assets and compute a measure of deviation from the mean. Common measures include z-scores, Bollinger Bands, and RSI. +
          • +
          • + When the deviation exceeds a threshold (for example, price drops below the lower Bollinger Band), enter a contrarian position. +
          • +
          • + Exit the position when the price reverts to the mean or crosses a take-profit level. +
          • +
          • + Use stop-losses to protect against cases where the deviation reflects a genuine regime change rather than a temporary overreaction. +
          • +
          +

          + Pairs trading is a common mean reversion variant. You identify two correlated assets, monitor the spread between them, and trade the convergence when the spread widens beyond its historical norm. +

          +

          + Biases that threaten mean reversion traders +

          +

          + Mean reversion traders face their own behavioral pitfalls: +

          +
            +
          • + + Recency bias + + - Extrapolating a recent deviation as the "new normal" instead of recognizing it as a temporary overreaction. This can cause you to exit too early or avoid entering at all. +
          • +
          • + + Overconfidence + + - Believing a deviation will revert when it actually reflects a fundamental shift. Not every drop is a buying opportunity; sometimes the new price is the correct price. +
          • +
          + + + +

          Value Factor

          + + +

          + Value strategies buy assets that appear undervalued relative to their fundamentals and sell assets that appear overvalued. The value premium is one of the most studied anomalies in finance: stocks with low price-to-earnings ratios, low price-to-book ratios, or high dividend yields have historically outperformed their expensive peers over long horizons. +

          +

          + Behavioral biases that create value opportunities +

          +

          + Several cognitive biases cause the market to misprice value stocks. + + Loss aversion + + causes investors to dump declining stocks to avoid further pain, pushing prices below intrinsic value. + + Recency bias + + leads investors to overweight recent poor performance and project it into the future, treating temporarily struggling companies as permanently impaired. + + Status quo bias + + makes investors reluctant to buy unfamiliar or out-of-favor stocks, leaving undervalued stocks cheap for longer. +

          +

          + Algorithmic approach +

          +

          + A typical value algorithm follows these steps: +

          +
            +
          • + Define a universe of equities with sufficient liquidity and market capitalization. +
          • +
          • + Screen for value using fundamental data: low P/E ratio, low price-to-book ratio, high dividend yield, or a composite of multiple value factors. +
          • +
          • + Rank the universe by the value metric and select the most undervalued stocks. +
          • +
          • + Construct an equal-weight or value-weight portfolio of the selected stocks. +
          • +
          • + Rebalance quarterly to allow time for the value thesis to play out while keeping the portfolio current. +
          • +
          +

          + Value strategies require patience because undervalued stocks can remain cheap for extended periods. The quarterly rebalance frequency reflects this longer time horizon compared to momentum or mean reversion strategies. +

          +

          + Biases that threaten value traders +

          +

          + Value investors face specific behavioral challenges: +

          +
            +
          • + + Loss aversion + + - Value stocks often continue to decline after you buy them. The same bias that creates the opportunity also makes it difficult to hold positions through drawdowns. +
          • +
          • + + Narrative fallacy + + - Requiring a compelling "story" for why a cheap stock will recover. Sometimes the quantitative signal is sufficient, and waiting for a narrative can cause you to miss the opportunity. +
          • +
          + + + +

          Quality Factor

          + + +

          + Quality factor strategies invest in companies with strong fundamentals: high profitability, stable earnings, low financial leverage, and consistent growth. These "boring but profitable" companies tend to outperform over time, despite receiving less attention than high-growth or turnaround stories. +

          +

          + Behavioral biases that create the quality premium +

          +

          + Several cognitive biases cause investors to undervalue quality stocks. The + + narrative fallacy + + draws investors toward stocks with exciting stories while steady, predictable businesses get ignored. + + Overconfidence bias + + leads investors to take concentrated bets on speculative stocks while overlooking quality companies with proven track records. The + + endowment effect + + makes investors reluctant to sell overvalued glamour stocks and rotate into higher-quality alternatives. +

          +

          + Algorithmic approach +

          +

          + A typical quality factor algorithm follows these steps: +

          +
            +
          • + Define a universe of equities with sufficient liquidity and market capitalization. +
          • +
          • + Screen for quality using fundamental metrics: high return on equity (ROE), low debt-to-equity ratio, stable or growing earnings, and strong gross margins. +
          • +
          • + Rank the universe by a composite quality score that combines multiple metrics. +
          • +
          • + Construct a portfolio of the highest-ranked quality stocks. +
          • +
          • + Rebalance quarterly as new fundamental data becomes available. +
          • +
          +

          + Quality strategies pair well with value strategies because they address a common criticism of pure value investing: buying cheap stocks that are cheap for good reason (value traps). Adding a quality filter helps you avoid deteriorating businesses and focus on undervalued companies with strong fundamentals. +

          +

          + Biases that threaten quality traders +

          +

          + Quality factor investors face their own behavioral challenges: +

          +
            +
          • + + Narrative fallacy + + - Even algo traders can be tempted to override quality signals in favor of an exciting growth story. Trust the quantitative metrics over qualitative narratives. +
          • +
          • + + Overconfidence + + - Believing that a high-risk, high-reward bet will pay off better than a diversified portfolio of quality companies. The quality premium is modest but consistent, and chasing larger returns often destroys value. +
          • +
          + + + +

          Dangerous Investment Biases

          + + +

          + The following 12 cognitive biases are among the most dangerous for investors. Each bias creates exploitable market inefficiencies, but each can also undermine your own trading decisions. For every bias, consider two questions: how can your algorithm exploit this bias in others, and how can you prevent this bias from affecting your algorithm design? +

          +

          + 1. Anchoring bias +

          +

          + Anchoring is the tendency to over-rely on the first piece of information you encounter. Investors anchor to purchase prices, 52-week highs, analyst price targets, or round numbers. This causes them to adjust too slowly when new information changes an asset's fair value. +

          +

          + + Susceptible strategies: + + Momentum (slow adjustment creates the trends that momentum exploits), Value Factor (anchoring to past prices instead of current intrinsic value). +

          +

          + + Algorithmic antidote: + + Use relative metrics (percentile ranks, z-scores) instead of absolute price levels. Recompute signals from scratch at each rebalance rather than adjusting from previous values. +

          +

          + 2. Availability bias +

          +

          + Availability bias is the tendency to overweight information that is easy to recall, usually because it is recent, dramatic, or emotionally charged. A vivid market crash receives more mental weight than a slow, steady recovery. This causes investors to overreact to dramatic events and create temporary mispricings. +

          +

          + + Susceptible strategies: + + Mean Reversion (dramatic events cause temporary mispricings that revert as emotions fade). +

          +

          + + Algorithmic antidote: + + Base decisions on systematic data analysis rather than salient events. Weight all data points equally unless you have a quantitative reason to do otherwise. +

          +

          + 3. Confirmation bias +

          +

          + Confirmation bias is the tendency to seek information that supports your existing beliefs and ignore information that contradicts them. An investor who is bullish on a stock will focus on positive news and dismiss negative signals. This selective processing reinforces trends and delays corrections. +

          +

          + + Susceptible strategies: + + Momentum (reinforces the belief that trends will continue), All types (ignoring disconfirming signals in backtests). +

          +

          + + Algorithmic antidote: + + Validate strategies with out-of-sample data and stress tests. Actively look for conditions where your strategy fails. If you can't find scenarios where it loses money, your testing process likely has confirmation bias built into it. +

          +

          + 4. Disposition effect +

          +

          + The disposition effect is the tendency to sell winning investments and hold losing ones. Investors want to realize gains (which feels good) and avoid realizing losses (which feels bad). This creates predictable patterns: winning stocks face selling pressure and losing stocks face holding pressure, both of which eventually correct. +

          +

          + + Susceptible strategies: + + Mean Reversion (disposition-driven selling and holding creates reversion patterns), Momentum (undermines trend following by encouraging premature profit-taking). +

          +

          + + Algorithmic antidote: + + Remove your entry price from the decision process entirely. Use trailing stops or signal-based exits rather than profit targets tied to your cost basis. +

          +

          + 5. Endowment effect +

          +

          + The endowment effect is the tendency to value something more simply because you own it. Investors demand a higher price to sell a stock they own than they would pay to buy the same stock. This creates inertia in portfolios and causes investors to hold overvalued positions longer than they should. +

          +

          + + Susceptible strategies: + + Quality Factor (overvaluing flashy holdings over fundamentally strong alternatives), Value Factor (refusing to sell a holding that has become overvalued). +

          +

          + + Algorithmic antidote: + + Rebalance on a fixed schedule using objective ranking criteria. An algorithm that ranks the entire universe at each rebalance does not "remember" what it owns and is immune to the endowment effect. +

          +

          + 6. Herd mentality +

          +

          + Herd mentality is the tendency to follow the crowd rather than think independently. When investors see others buying, they buy. When they see others selling, they sell. This collective behavior amplifies price movements beyond what fundamentals justify and creates the trends that momentum strategies exploit. +

          +

          + + Susceptible strategies: + + Momentum (crowd behavior amplifies trends that the strategy rides). +

          +

          + + Algorithmic antidote: + + Monitor crowding metrics and position concentration. Reduce exposure when too many market participants are on the same side of a trade. Momentum crashes often occur when a crowded trade reverses suddenly. +

          +

          + 7. Loss aversion +

          +

          + Losses feel approximately twice as painful as equivalent gains feel pleasurable. This asymmetry causes investors to hold losing positions too long (hoping to break even) and sell winning positions too early (locking in gains). Loss aversion creates undervaluation in declining stocks and suppresses returns in rising stocks. +

          +

          + + Susceptible strategies: + + Value Factor (difficulty holding underperformers through drawdowns), Momentum (cutting winners short instead of letting them run). +

          +

          + + Algorithmic antidote: + + Define exit rules based on quantitative signals, not profit or loss relative to your entry price. Your algorithm should not know or care about its purchase price when making sell decisions. +

          +

          + 8. Narrative fallacy +

          +

          + The narrative fallacy is the tendency to create compelling stories to explain random events. Investors prefer stocks with an exciting narrative (a disruptive technology, a charismatic founder) over stocks with strong but boring fundamentals. This preference channels capital toward glamour stocks and away from quality and value stocks. +

          +

          + + Susceptible strategies: + + Quality Factor (investors prefer exciting stories over fundamentals), Value Factor (requiring a "story" to justify buying an undervalued stock instead of trusting the data). +

          +

          + + Algorithmic antidote: + + Let quantitative signals drive decisions. If your algorithm requires a human-readable narrative to justify every trade, you are introducing narrative bias into a systematic process. +

          +

          + 9. Overconfidence bias +

          +

          + Overconfidence is the tendency to overestimate your knowledge, ability, or the precision of your predictions. Overconfident investors trade too frequently, take concentrated positions, and underestimate risk. In algo trading, overconfidence manifests as overfitting to historical data and deploying strategies with insufficient out-of-sample validation. +

          +

          + + Susceptible strategies: + + All types (overtrading, over-concentration, and insufficient diversification). +

          +

          + + Algorithmic antidote: + + Use conservative position sizing. Require a strategy to pass multiple independent tests before deploying capital. Track your prediction accuracy honestly and adjust your confidence accordingly. +

          +

          + 10. Recency bias +

          +

          + Recency bias is the tendency to overweight recent events when making decisions. Investors extrapolate recent trends, assuming that what happened last quarter will continue next quarter. This causes overreaction to short-term news and creates mispricings that revert over time. +

          +

          + + Susceptible strategies: + + Mean Reversion (overreaction creates the mean-reversion opportunities), Value Factor (recent poor performance causes investors to undervalue fundamentally sound companies). +

          +

          + + Algorithmic antidote: + + Use longer lookback periods that incorporate multiple market regimes. Evaluate strategy performance across full market cycles, not just recent periods. +

          +

          + 11. Status quo bias +

          +

          + Status quo bias is the preference for the current state of affairs. Changing your portfolio feels risky, even when the evidence supports a change. Investors stick with familiar holdings and avoid unfamiliar opportunities, which delays the correction of mispricings. +

          +

          + + Susceptible strategies: + + Value Factor (reluctance to buy out-of-favor or unfamiliar stocks). +

          +

          + + Algorithmic antidote: + + Automate rebalancing so that portfolio changes happen systematically. An algorithm that executes a defined rebalancing rule does not experience the discomfort of change. +

          +

          + 12. Sunk cost fallacy +

          +

          + The sunk cost fallacy is the tendency to continue investing in something because of the resources already committed, even when continuing is irrational. Investors double down on losing positions because they have "already invested so much" rather than evaluating the position on its current merits. +

          +

          + + Susceptible strategies: + + Value Factor (averaging down on a stock when the original value thesis is broken). +

          +

          + + Algorithmic antidote: + + Evaluate every position at every rebalance as if you were building the portfolio from scratch. The question is not "should I hold this?" but "would I buy this today at this price?" +

          + + + +

          Examples

          + + +

          + The following examples demonstrate algorithmic strategies that exploit behavioral biases. Each example targets a different strategy family and the biases that create its edge. +

          +

          + Example 1: Cross-Sectional Momentum Strategy +

          +

          + The following algorithm exploits herd mentality and anchoring bias by going long equities with the strongest trailing 12-month returns (skipping the most recent month to avoid short-term reversal). It rebalances monthly using a fundamental universe to identify the top momentum stocks. +

          +
          +
          public class MomentumBiasAlgorithm : QCAlgorithm
          +{
          +    private List<Symbol> _selected = new();
          +
          +    public override void Initialize()
          +    {
          +        SetStartDate(2024, 9, 1);
          +        SetEndDate(2024, 12, 31);
          +        SetCash(100000);
          +
          +        // Use a universe of liquid US equities.
          +        UniverseSettings.Resolution = Resolution.Daily;
          +        AddUniverse(FundamentalSelection);
          +
          +        // Rebalance on the first trading day of each month.
          +        Schedule.On(
          +            DateRules.MonthStart(),
          +            TimeRules.AfterMarketOpen("SPY", 30),
          +            Rebalance
          +        );
          +    }
          +
          +    private IEnumerable<Symbol> FundamentalSelection(IEnumerable<Fundamental> fundamental)
          +    {
          +        // Filter for liquid equities with prices above $5.
          +        return fundamental
          +            .Where(x => x.HasFundamentalData && x.Price > 5m && x.DollarVolume > 10000000)
          +            .OrderByDescending(x => x.DollarVolume)
          +            .Take(500)
          +            .Select(x => x.Symbol);
          +    }
          +
          +    private void Rebalance()
          +    {
          +        // Calculate trailing 12-month return with a 1-month skip for each security.
          +        var momentum = new Dictionary<Symbol, double>();
          +
          +        foreach (var security in ActiveSecurities.Values)
          +        {
          +            // Request 13 months of history: 12-month return skipping the most recent month.
          +            var history = History<TradeBar>(security.Symbol, 280, Resolution.Daily).ToList();
          +            if (history.Count < 252) continue;
          +
          +            // Skip the most recent 21 trading days (~1 month) to avoid short-term reversal.
          +            var endPrice = history[history.Count - 22].Close;
          +            var startPrice = history[0].Close;
          +            if (startPrice == 0) continue;
          +
          +            momentum[security.Symbol] = (double)(endPrice / startPrice - 1m);
          +        }
          +
          +        if (momentum.Count == 0) return;
          +
          +        // Select the top 10% of momentum stocks.
          +        var topCount = Math.Max(1, momentum.Count / 10);
          +        _selected = momentum
          +            .OrderByDescending(x => x.Value)
          +            .Take(topCount)
          +            .Select(x => x.Key)
          +            .ToList();
          +
          +        // Equal-weight the portfolio across the selected stocks.
          +        var weight = 1m / _selected.Count();
          +        foreach (var symbol in _selected)
          +        {
          +            SetHoldings(symbol, weight);
          +        }
          +
          +        // Liquidate positions no longer in the selection.
          +        foreach (var holding in Portfolio.Values)
          +        {
          +            if (holding.Invested && !_selected.Contains(holding.Symbol))
          +            {
          +                Liquidate(holding.Symbol);
          +            }
          +        }
          +    }
          +}
          +
          class MomentumBiasAlgorithm(QCAlgorithm):
          +
          +    def initialize(self) -> None:
          +        self.set_start_date(2024, 9, 1)
          +        self.set_end_date(2024, 12, 31)
          +        self.set_cash(100000)
          +
          +        # Use a universe of liquid US equities.
          +        self.universe_settings.resolution = Resolution.DAILY
          +        self.add_universe(self.fundamental_selection)
          +
          +        # Rebalance on the first trading day of each month.
          +        self.schedule.on(
          +            self.date_rules.month_start(),
          +            self.time_rules.after_market_open("SPY", 30),
          +            self.rebalance
          +        )
          +
          +        self.selected = []
          +
          +    def fundamental_selection(self, fundamental: list[Fundamental]) -> list[Symbol]:
          +        # Filter for liquid equities with prices above $5.
          +        filtered = [x for x in fundamental if x.has_fundamental_data and x.price > 5 and x.dollar_volume > 10000000]
          +        sorted_by_volume = sorted(filtered, key=lambda x: x.dollar_volume, reverse=True)[:500]
          +        return [x.symbol for x in sorted_by_volume]
          +
          +    def rebalance(self) -> None:
          +        # Calculate trailing 12-month return with a 1-month skip for each security.
          +        momentum = {}
          +
          +        for symbol, security in self.active_securities.items():
          +            # Request 13 months of history: 12-month return skipping the most recent month.
          +            history = self.history[TradeBar](symbol, 280, Resolution.DAILY)
          +            history_list = list(history)
          +            if len(history_list) < 252:
          +                continue
          +
          +            # Skip the most recent 21 trading days (~1 month) to avoid short-term reversal.
          +            end_price = history_list[-22].close
          +            start_price = history_list[0].close
          +            if start_price == 0:
          +                continue
          +
          +            momentum[symbol] = float(end_price / start_price - 1)
          +
          +        if not momentum:
          +            return
          +
          +        # Select the top 10% of momentum stocks.
          +        top_count = max(1, len(momentum) // 10)
          +        sorted_momentum = sorted(momentum.items(), key=lambda x: x[1], reverse=True)[:top_count]
          +        self.selected = [x[0] for x in sorted_momentum]
          +
          +        # Equal-weight the portfolio across the selected stocks.
          +        weight = 1.0 / len(self.selected)
          +        for symbol in self.selected:
          +            self.set_holdings(symbol, weight)
          +
          +        # Liquidate positions no longer in the selection.
          +        for holding in self.portfolio.values():
          +            if holding.invested and holding.symbol not in self.selected:
          +                self.liquidate(holding.symbol)
          +
          +

          + Example 2: Bollinger Band Mean Reversion Strategy +

          +

          + The following algorithm exploits overreaction and the disposition effect. It uses Bollinger Bands on SPY to identify overbought and oversold conditions. When the price drops below the lower band, the algorithm enters a long position betting on reversion to the mean. It exits when the price returns to the middle band. +

          +
          +
          public class MeanReversionBiasAlgorithm : QCAlgorithm
          +{
          +    private Symbol _spy;
          +    private BollingerBands _bb;
          +
          +    public override void Initialize()
          +    {
          +        SetStartDate(2024, 9, 1);
          +        SetEndDate(2024, 12, 31);
          +        SetCash(100000);
          +
          +        // Subscribe to SPY daily data.
          +        _spy = AddEquity("SPY", Resolution.Daily).Symbol;
          +
          +        // Create a 20-day Bollinger Band with 2 standard deviations.
          +        _bb = BB(_spy, 20, 2m, MovingAverageType.Simple, Resolution.Daily);
          +
          +        // Warm up the indicator with historical data.
          +        SetWarmUp(20, Resolution.Daily);
          +    }
          +
          +    public override void OnData(Slice slice)
          +    {
          +        if (IsWarmingUp || !_bb.IsReady) return;
          +        if (!slice.Bars.ContainsKey(_spy)) return;
          +
          +        var price = slice.Bars[_spy].Close;
          +
          +        if (!Portfolio[_spy].Invested)
          +        {
          +            // Enter long when price drops below the lower Bollinger Band (oversold / overreaction).
          +            if (price < _bb.LowerBand.Current.Value)
          +            {
          +                SetHoldings(_spy, 1m, tag: $"Enter long at {price:F2} (lower band: {_bb.LowerBand.Current.Value:F2})");
          +            }
          +        }
          +        else if (Portfolio[_spy].IsLong)
          +        {
          +            // Exit when price reverts to the middle band (mean).
          +            if (price >= _bb.MiddleBand.Current.Value)
          +            {
          +                Liquidate(_spy, tag: $"Exit at {price:F2} (middle band: {_bb.MiddleBand.Current.Value:F2})");
          +            }
          +        }
          +    }
          +}
          +
          class MeanReversionBiasAlgorithm(QCAlgorithm):
          +
          +    def initialize(self) -> None:
          +        self.set_start_date(2024, 9, 1)
          +        self.set_end_date(2024, 12, 31)
          +        self.set_cash(100000)
          +
          +        # Subscribe to SPY daily data.
          +        self.spy = self.add_equity("SPY", Resolution.DAILY).symbol
          +
          +        # Create a 20-day Bollinger Band with 2 standard deviations.
          +        self.bollinger = self.bb(self.spy, 20, 2, MovingAverageType.SIMPLE, Resolution.DAILY)
          +
          +        # Warm up the indicator with historical data.
          +        self.set_warm_up(20, Resolution.DAILY)
          +
          +    def on_data(self, slice: Slice) -> None:
          +        if self.is_warming_up or not self.bollinger.is_ready:
          +            return
          +        if self.spy not in slice.bars:
          +            return
          +
          +        price = slice.bars[self.spy].close
          +
          +        if not self.portfolio[self.spy].invested:
          +            # Enter long when price drops below the lower Bollinger Band (oversold / overreaction).
          +            if price < self.bollinger.lower_band.current.value:
          +                self.set_holdings(self.spy, 1, tag=f"Enter long at {price:.2f} (lower band: {self.bollinger.lower_band.current.value:.2f})")
          +        elif self.portfolio[self.spy].is_long:
          +            # Exit when price reverts to the middle band (mean).
          +            if price >= self.bollinger.middle_band.current.value:
          +                self.liquidate(self.spy, tag=f"Exit at {price:.2f} (middle band: {self.bollinger.middle_band.current.value:.2f})")
          +
          +

          + Example 3: Fundamental Value Strategy +

          +

          + The following algorithm exploits loss aversion and recency bias by screening for undervalued equities using the price-to-earnings ratio. Investors dump stocks with recent poor performance, pushing prices below intrinsic value. The algorithm identifies these opportunities using fundamental data and rebalances quarterly. +

          +
          +
          public class ValueBiasAlgorithm : QCAlgorithm
          +{
          +    private List<Symbol> _selected = new();
          +    private int _lastRebalanceMonth = -1;
          +
          +    public override void Initialize()
          +    {
          +        SetStartDate(2024, 9, 1);
          +        SetEndDate(2024, 12, 31);
          +        SetCash(100000);
          +
          +        // Use a fundamental universe for value screening.
          +        UniverseSettings.Resolution = Resolution.Daily;
          +        AddUniverse(FundamentalSelection);
          +    }
          +
          +    private IEnumerable<Symbol> FundamentalSelection(IEnumerable<Fundamental> fundamental)
          +    {
          +        // Only rebalance quarterly (January, April, July, October).
          +        if (Time.Month != 1 && Time.Month != 4 && Time.Month != 7 && Time.Month != 10)
          +        {
          +            return Universe.Unchanged;
          +        }
          +        if (Time.Month == _lastRebalanceMonth) return Universe.Unchanged;
          +        _lastRebalanceMonth = Time.Month;
          +
          +        // Screen for value: liquid stocks with positive earnings and low P/E ratios.
          +        var withEarnings = fundamental
          +            .Where(x => x.HasFundamentalData && x.Price > 5m && x.DollarVolume > 5000000
          +                      && x.ValuationRatios.PERatio > 0 && x.ValuationRatios.PERatio < 50
          +                      && x.MarketCap > 1000000000)
          +            .ToList();
          +
          +        if (withEarnings.Count() == 0) return Enumerable.Empty<Symbol>();
          +
          +        // Select the 20 stocks with the lowest P/E ratio (most undervalued).
          +        _selected = withEarnings
          +            .OrderBy(x => x.ValuationRatios.PERatio)
          +            .Take(20)
          +            .Select(x => x.Symbol)
          +            .ToList();
          +
          +        return _selected;
          +    }
          +
          +    public override void OnSecuritiesChanged(SecurityChanges changes)
          +    {
          +        // Liquidate stocks removed from the universe.
          +        foreach (var security in changes.RemovedSecurities)
          +        {
          +            if (security.Invested)
          +            {
          +                Liquidate(security.Symbol);
          +            }
          +        }
          +
          +        // Equal-weight the portfolio across selected value stocks.
          +        if (_selected.Any())
          +        {
          +            var weight = 1m / _selected.Count();
          +            foreach (var symbol in _selected)
          +            {
          +                SetHoldings(symbol, weight);
          +            }
          +        }
          +    }
          +}
          +
          class ValueBiasAlgorithm(QCAlgorithm):
          +
          +    def initialize(self) -> None:
          +        self.set_start_date(2024, 9, 1)
          +        self.set_end_date(2024, 12, 31)
          +        self.set_cash(100000)
          +
          +        # Use a fundamental universe for value screening.
          +        self.universe_settings.resolution = Resolution.DAILY
          +        self.add_universe(self.fundamental_selection)
          +
          +        self.selected = []
          +        self.last_rebalance_month = -1
          +
          +    def fundamental_selection(self, fundamental: list[Fundamental]) -> list[Symbol]:
          +        # Only rebalance quarterly (January, April, July, October).
          +        if self.time.month not in [1, 4, 7, 10]:
          +            return Universe.UNCHANGED
          +        if self.time.month == self.last_rebalance_month:
          +            return Universe.UNCHANGED
          +        self.last_rebalance_month = self.time.month
          +
          +        # Screen for value: liquid stocks with positive earnings and low P/E ratios.
          +        with_earnings = [x for x in fundamental
          +                         if x.has_fundamental_data and x.price > 5 and x.dollar_volume > 5000000
          +                         and 0 < x.valuation_ratios.pe_ratio < 50
          +                         and x.market_cap > 1e9]
          +
          +        if not with_earnings:
          +            return []
          +
          +        # Select the 20 stocks with the lowest P/E ratio (most undervalued).
          +        sorted_by_pe = sorted(with_earnings, key=lambda x: x.valuation_ratios.pe_ratio)[:20]
          +        self.selected = [x.symbol for x in sorted_by_pe]
          +        return self.selected
          +
          +    def on_securities_changed(self, changes: SecurityChanges) -> None:
          +        # Liquidate stocks removed from the universe.
          +        for security in changes.removed_securities:
          +            if security.invested:
          +                self.liquidate(security.symbol)
          +
          +        # Equal-weight the portfolio across selected value stocks.
          +        if self.selected:
          +            weight = 1.0 / len(self.selected)
          +            for symbol in self.selected:
          +                self.set_holdings(symbol, weight)
          +
          +

          + Example 4: Quality Factor Strategy +

          +

          + The following algorithm exploits narrative fallacy and overconfidence by screening for high-quality companies that the market overlooks in favor of more exciting stories. It uses return on equity and debt-to-equity ratio as quality metrics, constructs an equal-weight portfolio, and rebalances quarterly. +

          +
          +
          public class QualityBiasAlgorithm : QCAlgorithm
          +{
          +    private List<Symbol> _selected = new();
          +    private int _lastRebalanceMonth = -1;
          +
          +    public override void Initialize()
          +    {
          +        SetStartDate(2024, 9, 1);
          +        SetEndDate(2024, 12, 31);
          +        SetCash(100000);
          +
          +        // Use a fundamental universe for quality screening.
          +        UniverseSettings.Resolution = Resolution.Daily;
          +        AddUniverse(FundamentalSelection);
          +    }
          +
          +    private IEnumerable<Symbol> FundamentalSelection(IEnumerable<Fundamental> fundamental)
          +    {
          +        // Only rebalance quarterly (January, April, July, October).
          +        if (Time.Month != 1 && Time.Month != 4 && Time.Month != 7 && Time.Month != 10)
          +        {
          +            return Universe.Unchanged;
          +        }
          +        if (Time.Month == _lastRebalanceMonth) return Universe.Unchanged;
          +        _lastRebalanceMonth = Time.Month;
          +
          +        // Screen for quality: liquid stocks with high ROE and low debt-to-equity.
          +        var quality = fundamental
          +            .Where(x => x.HasFundamentalData && x.Price > 5m && x.DollarVolume > 5000000
          +                      && x.OperationRatios.ROE.Value > 0
          +                      && x.OperationRatios.TotalDebtEquityRatio.Value >= 0
          +                      && x.OperationRatios.TotalDebtEquityRatio.Value < 1.5
          +                      && x.MarketCap > 1000000000)
          +            .ToList();
          +
          +        if (quality.Count() == 0) return Enumerable.Empty<Symbol>();
          +
          +        // Rank by ROE descending (higher ROE = higher quality).
          +        _selected = quality
          +            .OrderByDescending(x => x.OperationRatios.ROE.Value)
          +            .Take(20)
          +            .Select(x => x.Symbol)
          +            .ToList();
          +
          +        return _selected;
          +    }
          +
          +    public override void OnSecuritiesChanged(SecurityChanges changes)
          +    {
          +        // Liquidate stocks removed from the universe.
          +        foreach (var security in changes.RemovedSecurities)
          +        {
          +            if (security.Invested)
          +            {
          +                Liquidate(security.Symbol);
          +            }
          +        }
          +
          +        // Equal-weight the portfolio across selected quality stocks.
          +        if (_selected.Any())
          +        {
          +            var weight = 1m / _selected.Count();
          +            foreach (var symbol in _selected)
          +            {
          +                SetHoldings(symbol, weight);
          +            }
          +        }
          +    }
          +}
          +
          class QualityBiasAlgorithm(QCAlgorithm):
          +
          +    def initialize(self) -> None:
          +        self.set_start_date(2024, 9, 1)
          +        self.set_end_date(2024, 12, 31)
          +        self.set_cash(100000)
          +
          +        # Use a fundamental universe for quality screening.
          +        self.universe_settings.resolution = Resolution.DAILY
          +        self.add_universe(self.fundamental_selection)
          +
          +        self.selected = []
          +        self.last_rebalance_month = -1
          +
          +    def fundamental_selection(self, fundamental: list[Fundamental]) -> list[Symbol]:
          +        # Only rebalance quarterly (January, April, July, October).
          +        if self.time.month not in [1, 4, 7, 10]:
          +            return Universe.UNCHANGED
          +        if self.time.month == self.last_rebalance_month:
          +            return Universe.UNCHANGED
          +        self.last_rebalance_month = self.time.month
          +
          +        # Screen for quality: liquid stocks with high ROE and low debt-to-equity.
          +        quality = [x for x in fundamental
          +                   if x.has_fundamental_data and x.price > 5 and x.dollar_volume > 5000000
          +                   and x.operation_ratios.roe.value > 0
          +                   and 0 <= x.operation_ratios.total_debt_equity_ratio.value < 1.5
          +                   and x.market_cap > 1e9]
          +
          +        if not quality:
          +            return []
          +
          +        # Rank by ROE descending (higher ROE = higher quality).
          +        sorted_by_roe = sorted(quality, key=lambda x: x.operation_ratios.roe.value, reverse=True)[:20]
          +        self.selected = [x.symbol for x in sorted_by_roe]
          +        return self.selected
          +
          +    def on_securities_changed(self, changes: SecurityChanges) -> None:
          +        # Liquidate stocks removed from the universe.
          +        for security in changes.removed_securities:
          +            if security.invested:
          +                self.liquidate(security.symbol)
          +
          +        # Equal-weight the portfolio across selected quality stocks.
          +        if self.selected:
          +            weight = 1.0 / len(self.selected)
          +            for symbol in self.selected:
          +                self.set_holdings(symbol, weight)
          +
          + + + +

           

          + +
          +

          Key Concepts

          Globals and Statics

          @@ -5809,7 +6880,7 @@

          API Access by Global Static Variable

           

          -
          +

          Key Concepts

          Libraries

          @@ -5985,7 +7056,7 @@

          Project Libraries

           

          -
          +

          Key Concepts

          Debugging Tools

          @@ -6422,7 +7493,7 @@

          JIT Compilation

           

          -
          +

          Key Concepts

          Glossary

          @@ -8984,11 +10055,11 @@

          Set Benchmark

          The benchmark performance is input to calculate several statistics on your algorithm, including - + alpha and - + beta . To set a benchmark for your algorithm, call the @@ -19927,7 +20998,7 @@

          Helper Methods

          To get the Option - + payoff , call the @@ -19945,7 +21016,7 @@

          Helper Methods

          To get the Option - + intrinsic value , call the @@ -19963,7 +21034,7 @@

          Helper Methods

          To get the Option - + out-of-the-money amount , call the @@ -22777,7 +23848,7 @@

          Sell(_options.option2, 1); } // Liquidate any assigned positions. - if (Portfolio[_underlying].Invested) + if (_underlying != null && Portfolio[_underlying].Invested) { Liquidate(_underlying); } @@ -22882,7 +23953,7 @@

          self.sell(self._options[0], 1) self.sell(self._options[1], 1) # Liquidate any assigned positions. - if self.portfolio[self._underlying].invested: + if not self._underlying and self.portfolio[self._underlying].invested: self.liquidate(self._underlying)

          @@ -23456,7 +24527,7 @@

          Sell(_options.option2, 1); } // Liquidate any assigned positions. - if (Portfolio[_underlying].Invested) + if (_underlying != null && Portfolio[_underlying].Invested) { Liquidate(_underlying); } @@ -23561,7 +24632,7 @@

          self.sell(self._options[0], 1) self.sell(self._options[1], 1) # Liquidate any assigned positions. - if self.portfolio[self._underlying].invested: + if not self._underlying and self.portfolio[self._underlying].invested: self.liquidate(self._underlying)

          @@ -24034,7 +25105,7 @@

          Sell(_options.option2, 1); } // Liquidate any assigned positions. - if (Portfolio[_underlying].Invested) + if (_underlying != null && Portfolio[_underlying].Invested) { Liquidate(_underlying); } @@ -24139,7 +25210,7 @@

          self.sell(self._options[0], 1) self.sell(self._options[1], 1) # Liquidate any assigned positions. - if self.portfolio[self._underlying].invested: + if not self._underlying and self.portfolio[self._underlying].invested: self.liquidate(self._underlying)

        @@ -24611,7 +25682,7 @@

        Sell(_options.option2, 1); } // Liquidate any assigned positions. - if (Portfolio[_underlying].Invested) + if (_underlying != null && Portfolio[_underlying].Invested) { Liquidate(_underlying); } @@ -24716,7 +25787,7 @@

        self.sell(self._options[0], 1) self.sell(self._options[1], 1) # Liquidate any assigned positions. - if self.portfolio[self._underlying].invested: + if not self._underlying and self.portfolio[self._underlying].invested: self.liquidate(self._underlying)

        @@ -25189,7 +26260,7 @@

        Sell(_options.option2, 1); } // Liquidate any assigned positions. - if (Portfolio[_underlying].Invested) + if (_underlying != null && Portfolio[_underlying].Invested) { Liquidate(_underlying); } @@ -25294,7 +26365,7 @@

        self.sell(self._options[0], 1) self.sell(self._options[1], 1) # Liquidate any assigned positions. - if self.portfolio[self._underlying].invested: + if not self._underlying and self.portfolio[self._underlying].invested: self.liquidate(self._underlying)

        @@ -25772,7 +26843,7 @@

        Sell(_options.option2, 1); } // Liquidate any assigned positions. - if (Portfolio[_underlying].Invested) + if (_underlying != null && Portfolio[_underlying].Invested) { Liquidate(_underlying); } @@ -25877,7 +26948,7 @@

        self.sell(self._options[0], 1) self.sell(self._options[1], 1) # Liquidate any assigned positions. - if self.portfolio[self._underlying].invested: + if not self._underlying and self.portfolio[self._underlying].invested: self.liquidate(self._underlying)

        @@ -107040,7 +108111,7 @@

        Helper Methods

        To get the Option - + payoff , call the @@ -107058,7 +108129,7 @@

        Helper Methods

        To get the Option - + intrinsic value , call the @@ -107076,7 +108147,7 @@

        Helper Methods

        To get the Option - + out-of-the-money amount , call the @@ -109131,7 +110202,7 @@

        Sell(_options.option2, 1); } // Liquidate any assigned positions. - if (Portfolio[_future.Mapped].Invested) + if (_future.Mapped != null && Portfolio[_future.Mapped].Invested) { Liquidate(_future.Mapped); } @@ -109242,7 +110313,7 @@

        self.sell(self._options[0], 1) self.sell(self._options[1], 1) # Liquidate any assigned positions. - if self.portfolio[self._future.mapped].invested: + if not self._future.mapped and self.portfolio[self._future.mapped].invested: self.liquidate(self._future.mapped)

        @@ -109830,7 +110901,7 @@

        Sell(_options.option2, 1); } // Liquidate any assigned positions. - if (Portfolio[_future.Mapped].Invested) + if (_future.Mapped != null && Portfolio[_future.Mapped].Invested) { Liquidate(_future.Mapped); } @@ -109941,7 +111012,7 @@

        self.sell(self._options[0], 1) self.sell(self._options[1], 1) # Liquidate any assigned positions. - if self.portfolio[self._future.mapped].invested: + if not self._future.mapped and self.portfolio[self._future.mapped].invested: self.liquidate(self._future.mapped)

        @@ -110428,7 +111499,7 @@

        Sell(_options.option2, 1); } // Liquidate any assigned positions. - if (Portfolio[_future.Mapped].Invested) + if (_future.Mapped != null && Portfolio[_future.Mapped].Invested) { Liquidate(_future.Mapped); } @@ -110539,7 +111610,7 @@

        self.sell(self._options[0], 1) self.sell(self._options[1], 1) # Liquidate any assigned positions. - if self.portfolio[self._future.mapped].invested: + if not self._future.mapped and self.portfolio[self._future.mapped].invested: self.liquidate(self._future.mapped)

        @@ -111025,7 +112096,7 @@

        Sell(_options.option2, 1); } // Liquidate any assigned positions. - if (Portfolio[_future.Mapped].Invested) + if (_future.Mapped != null && Portfolio[_future.Mapped].Invested) { Liquidate(_future.Mapped); } @@ -111136,7 +112207,7 @@

        self.sell(self._options[0], 1) self.sell(self._options[1], 1) # Liquidate any assigned positions. - if self.portfolio[self._future.mapped].invested: + if not self._future.mapped and self.portfolio[self._future.mapped].invested: self.liquidate(self._future.mapped)

        @@ -111623,7 +112694,7 @@

        Sell(_options.option2, 1); } // Liquidate any assigned positions. - if (Portfolio[_future.Mapped].Invested) + if (_future.Mapped != null && Portfolio[_future.Mapped].Invested) { Liquidate(_future.Mapped); } @@ -111734,7 +112805,7 @@

        self.sell(self._options[0], 1) self.sell(self._options[1], 1) # Liquidate any assigned positions. - if self.portfolio[self._future.mapped].invested: + if not self._future.mapped and self.portfolio[self._future.mapped].invested: self.liquidate(self._future.mapped)

        @@ -112226,7 +113297,7 @@

        Sell(_options.option2, 1); } // Liquidate any assigned positions. - if (Portfolio[_future.Mapped].Invested) + if (_future.Mapped != null && Portfolio[_future.Mapped].Invested) { Liquidate(_future.Mapped); } @@ -112337,7 +113408,7 @@

        self.sell(self._options[0], 1) self.sell(self._options[1], 1) # Liquidate any assigned positions. - if self.portfolio[self._future.mapped].invested: + if not self._future.mapped and self.portfolio[self._future.mapped].invested: self.liquidate(self._future.mapped)

        @@ -121534,7 +122605,7 @@

        Helper Methods

        To get the Option - + payoff , call the @@ -121552,7 +122623,7 @@

        Helper Methods

        To get the Option - + intrinsic value , call the @@ -121570,7 +122641,7 @@

        Helper Methods

        To get the Option - + out-of-the-money amount , call the @@ -123919,7 +124990,7 @@

        Sell(_options.option2, 1); } // Liquidate any assigned positions. - if (Portfolio[_underlying].Invested) + if (_underlying != null && Portfolio[_underlying].Invested) { Liquidate(_underlying); } @@ -124024,7 +125095,7 @@

        self.sell(self._options[0], 1) self.sell(self._options[1], 1) # Liquidate any assigned positions. - if self.portfolio[self._underlying].invested: + if not self._underlying and self.portfolio[self._underlying].invested: self.liquidate(self._underlying)

        @@ -124598,7 +125669,7 @@

        Sell(_options.option2, 1); } // Liquidate any assigned positions. - if (Portfolio[_underlying].Invested) + if (_underlying != null && Portfolio[_underlying].Invested) { Liquidate(_underlying); } @@ -124703,7 +125774,7 @@

        self.sell(self._options[0], 1) self.sell(self._options[1], 1) # Liquidate any assigned positions. - if self.portfolio[self._underlying].invested: + if not self._underlying and self.portfolio[self._underlying].invested: self.liquidate(self._underlying)

        @@ -125176,7 +126247,7 @@

        Sell(_options.option2, 1); } // Liquidate any assigned positions. - if (Portfolio[_underlying].Invested) + if (_underlying != null && Portfolio[_underlying].Invested) { Liquidate(_underlying); } @@ -125281,7 +126352,7 @@

        self.sell(self._options[0], 1) self.sell(self._options[1], 1) # Liquidate any assigned positions. - if self.portfolio[self._underlying].invested: + if not self._underlying and self.portfolio[self._underlying].invested: self.liquidate(self._underlying)

        @@ -125753,7 +126824,7 @@

        Sell(_options.option2, 1); } // Liquidate any assigned positions. - if (Portfolio[_underlying].Invested) + if (_underlying != null && Portfolio[_underlying].Invested) { Liquidate(_underlying); } @@ -125858,7 +126929,7 @@

        self.sell(self._options[0], 1) self.sell(self._options[1], 1) # Liquidate any assigned positions. - if self.portfolio[self._underlying].invested: + if not self._underlying and self.portfolio[self._underlying].invested: self.liquidate(self._underlying)

        @@ -126331,7 +127402,7 @@

        Sell(_options.option2, 1); } // Liquidate any assigned positions. - if (Portfolio[_underlying].Invested) + if (_underlying != null && Portfolio[_underlying].Invested) { Liquidate(_underlying); } @@ -126436,7 +127507,7 @@

        self.sell(self._options[0], 1) self.sell(self._options[1], 1) # Liquidate any assigned positions. - if self.portfolio[self._underlying].invested: + if not self._underlying and self.portfolio[self._underlying].invested: self.liquidate(self._underlying)

        @@ -126914,7 +127985,7 @@

        Sell(_options.option2, 1); } // Liquidate any assigned positions. - if (Portfolio[_underlying].Invested) + if (_underlying != null && Portfolio[_underlying].Invested) { Liquidate(_underlying); } @@ -127019,7 +128090,7 @@

        self.sell(self._options[0], 1) self.sell(self._options[1], 1) # Liquidate any assigned positions. - if self.portfolio[self._underlying].invested: + if not self._underlying and self.portfolio[self._underlying].invested: self.liquidate(self._underlying)

        @@ -143557,28 +144628,28 @@

        Time Zone

        "IBJP225": "Japan\u00a0225", "IBHK50": "Hong\u00a0Kong\u00a050", "IBAU200": "Australia 200", - "AUDCAD": "oanda", - "AUDCHF": "oanda", + "AUDCAD": "AUD/CAD", + "AUDCHF": "AUD/CHF", "AUDCNH": "AUD/CNH", - "AUDHKD": "oanda", + "AUDHKD": "AUD/HKD", "AUDJPY": "AUD/JPY", - "AUDNZD": "oanda", - "AUDSGD": "oanda", + "AUDNZD": "AUD/NZD", + "AUDSGD": "AUD/SGD", "AUDUSD": "AUD/USD", "AUDZAR": "AUD/ZAR", - "CADCHF": "oanda", + "CADCHF": "CAD/CHF", "CADCNH": "CAD/CNH", - "CADHKD": "oanda", - "CADJPY": "oanda", + "CADHKD": "CAD/HKD", + "CADJPY": "CAD/JPY", "CHFCNH": "CHF/CNH", "CHFCZK": "CHF/CZK", "CHFDKK": "CHF/DKK", "CHFHUF": "CHF/HUF", - "CHFJPY": "oanda", + "CHFJPY": "CHF/JPY", "CHFNOK": "CHF/NOK", "CHFPLN": "CHF/PLN", "CHFSEK": "CHF/SEK", - "CHFZAR": "oanda", + "CHFZAR": "CHF/ZAR", "CNHHKD": "CNH/HKD", "CNHJPY": "CNH/JPY", "DKKJPY": "DKK/JPY", @@ -143588,67 +144659,67 @@

        Time Zone

        "EURCAD": "EUR/CAD", "EURCHF": "EUR/CHF", "EURCNH": "EUR/CNH", - "EURCZK": "oanda", - "EURDKK": "oanda", + "EURCZK": "EUR/CZK", + "EURDKK": "EUR/DKK", "EURGBP": "EUR/GBP", - "EURHKD": "oanda", - "EURHUF": "oanda", + "EURHKD": "EUR/HKD", + "EURHUF": "EUR/HUF", "EURILS": "EUR/ILS", "EURJPY": "EUR/JPY", "EURMXN": "EUR/MXN", - "EURNOK": "oanda", - "EURNZD": "oanda", - "EURPLN": "oanda", + "EURNOK": "EUR/NOK", + "EURNZD": "EUR/NZD", + "EURPLN": "EUR/PLN", "EURRUB": "EUR/RUB", - "EURSEK": "oanda", - "EURSGD": "oanda", - "EURUSD": "EUR-USD", - "EURZAR": "oanda", - "GBPAUD": "oanda", - "GBPCAD": "oanda", - "GBPCHF": "oanda", + "EURSEK": "EUR/SEK", + "EURSGD": "EUR/SGD", + "EURUSD": "EUR/USD", + "EURZAR": "EUR/ZAR", + "GBPAUD": "GBP/AUD", + "GBPCAD": "GBP/CAD", + "GBPCHF": "GBP/CHF", "GBPCNH": "GBP/CNH", "GBPCZK": "GBP/CZK", "GBPDKK": "GBP/DKK", - "GBPHKD": "oanda", + "GBPHKD": "GBP/HKD", "GBPHUF": "GBP/HUF", - "GBPJPY": "oanda", + "GBPJPY": "GBP/JPY", "GBPMXN": "GBP/MXN", "GBPNOK": "GBP/NOK", - "GBPNZD": "oanda", - "GBPPLN": "oanda", + "GBPNZD": "GBP/NZD", + "GBPPLN": "GBP/PLN", "GBPSEK": "GBP/SEK", - "GBPSGD": "oanda", + "GBPSGD": "GBP/SGD", "GBPUSD": "GBP/USD", - "GBPZAR": "oanda", - "HKDJPY": "oanda", + "GBPZAR": "GBP/ZAR", + "HKDJPY": "HKD/JPY", "MXNJPY": "MXN/JPY", "NOKJPY": "NOK/JPY", "NOKSEK": "NOK/SEK", - "NZDCAD": "oanda", - "NZDCHF": "oanda", - "NZDJPY": "oanda", - "NZDUSD": "oanda", + "NZDCAD": "NZD/CAD", + "NZDCHF": "NZD/CHF", + "NZDJPY": "NZD/JPY", + "NZDUSD": "NZD/USD", "SEKJPY": "SEK/JPY", "SGDCNH": "SGD/CNH", - "SGDJPY": "oanda", + "SGDJPY": "SGD/JPY", "USDCAD": "USD/CAD", "USDCHF": "USD/CHF", - "USDCNH": "oanda", - "USDCZK": "oanda", - "USDDKK": "oanda", - "USDHKD": "oanda", - "USDHUF": "oanda", + "USDCNH": "USD/CNH", + "USDCZK": "USD/CZK", + "USDDKK": "USD/DKK", + "USDHKD": "USD/HKD", + "USDHUF": "USD/HUF", "USDILS": "USD/ILS", "USDJPY": "USD/JPY", - "USDMXN": "oanda", - "USDNOK": "oanda", - "USDPLN": "oanda", + "USDMXN": "USD/MXN", + "USDNOK": "USD/NOK", + "USDPLN": "USD/PLN", "USDRUB": "USD/RUB", - "USDSEK": "oanda", - "USDSGD": "oanda", - "USDZAR": "oanda", - "ZARJPY": "oanda" + "USDSEK": "USD/SEK", + "USDSGD": "USD/SGD", + "USDZAR": "USD/ZAR", + "ZARJPY": "ZAR/JPY" }; const DEFAULT_KEY = 'Cfd-interactivebrokers-[*]'; @@ -273062,9 +274133,19 @@

        Set Data Sources

        "https://cdn.quantconnect.com/uploads/multi_csv_zipped_file.zip#csv_file_10.csv", SubscriptionTransportMedium.RemoteFile, FileFormat.ZipEntryName); + + // Example of loading a remote file with Basic authentication headers: + var user = "username"; + var password = "12345"; + var byteKey = Encoding.ASCII.GetBytes($"{user}:{password}"); + var authorizationHeaders = new Dictionary + { + { "Authorization", $"Basic {Convert.ToBase64String(byteKey)}" } + }; + return new SubscriptionDataSource(source, SubscriptionTransportMedium.RemoteFile, FileFormat.Csv, authorizationHeaders); */ } -} +}
        class MyCustomDataType(PythonData):
             def get_source(self,
                  config: SubscriptionDataConfig,
        @@ -273093,6 +274174,16 @@ 

        Set Data Sources

        # SubscriptionTransportMedium.REMOTE_FILE, # FileFormat.ZIP_ENTRY_NAME # ) + + # Example of loading a remote file with Basic authentication headers: + # import base64 + # user = "username" + # password = "12345" + # byte_key = base64.b64encode(f"{user}:{password}".encode("ascii")) + # authorization_headers = { + # "Authorization": f"Basic {byte_key.decode('ascii')}" + # } + # return SubscriptionDataSource(source, SubscriptionTransportMedium.REMOTE_FILE, FileFormat.CSV, authorization_headers)

        @@ -273275,9 +274366,12 @@

        Set Data Sources

        - + IEnumerable<KeyValuePair<string, string>> + + dict[str, str] + The headers to be used for this source. In cloud algorithms, each of the key-value pairs can consist of up to 1,000 characters. @@ -273400,7 +274494,7 @@

        Set Data Sources

        The data comes from the object store
        - + Example of Custom Data The maximum percentage - + drawdown allowed for any single security holding @@ -490641,7 +493561,7 @@

        Maximum Portfolio Drawdown Model

        MaximumDrawdownPercentPortfolio model monitors the portfolio - + drawdown . The drawdown can be relative to the starting portfolio value or the maximum portfolio value. When the portfolio value drops below a percentage threshold, the model liquidates the portfolio and cancels all insights in the @@ -490977,7 +493897,7 @@

        Trailing Stop Model

        TrailingStopRiskManagementModel monitors the - + drawdown of each security in the portfolio. When the peak-to-trough drawdown of the unrealized profit exceeds a threshold, it liquidates the position and cancels all insights in the @@ -499267,7 +502187,7 @@

        Encapsulate Event Handlers

        To access your algorithm's attributes, you need to create a - + global static variable , and assign the reference to the global variable in the algorithm's