Overall Statistics
Total Orders
1099
Average Win
5.92%
Average Loss
-3.06%
Compounding Annual Return
120.035%
Drawdown
74.300%
Expectancy
0.353
Start Equity
100000.00
End Equity
10337274.08
Net Profit
10237.274%
Sharpe Ratio
1.683
Sortino Ratio
2.365
Probabilistic Sharpe Ratio
55.160%
Loss Rate
54%
Win Rate
46%
Profit-Loss Ratio
1.94
Alpha
1.401
Beta
0.912
Annual Standard Deviation
0.89
Annual Variance
0.792
Information Ratio
1.586
Tracking Error
0.877
Treynor Ratio
1.641
Total Fees
$0.00
Estimated Strategy Capacity
$660000.00
Lowest Capacity Asset
ADAUSD 2XR
Portfolio Turnover
40.44%
from AlgorithmImports import *
import numpy as np

class MomentumBasedCryptoSelector(QCAlgorithm):
    def Initialize(self):
        self.SetStartDate(2019, 1, 1)
        #self.SetEndDate(2022, 6, 1)
        self.SetCash(100000)  # Set starting cash in USD

        # Updated list of crypto pairs to include the new assets
        self.crypto_symbols = [
            "BTCUSD", "ETHUSD", "LTCUSD", "XRPUSD", "BCHUSD",
            "ADAUSD", "DOGEUSD", "SOLUSD", "DOTUSD", "MATICUSD",
            "HNTUSD", "STXUSD", "MKRUSD"
        ]
        
        # Add all crypto pairs and store their Symbol objects
        self.symbols = [self.AddCrypto(symbol, Resolution.Daily).Symbol for symbol in self.crypto_symbols]

        # Warm-up period to ensure data availability (based on lookback period and SMA period)
        self.lookback_period = 5  # Set the lookback period as a variable
        self.sma_period = 50  # Set the SMA period as a variable
        self.SetWarmUp(self.sma_period)

        # Maximum allocation rule: 80% of the total portfolio value
        self.max_allocation_percentage = 0.8

    def OnData(self, slice):
        if self.IsWarmingUp:
            return

        # Dictionary to store momentum values for each symbol
        momentum_scores = {}

        # Calculate momentum and apply the SMA filter for each crypto pair
        for symbol in self.symbols:
            history = self.History(symbol, max(self.lookback_period, self.sma_period), Resolution.Daily)
            if history.empty or len(history["close"]) < self.sma_period:
                continue

            close_prices = history["close"].values

            # Calculate the 50-period SMA
            sma = np.mean(close_prices[-self.sma_period:])
            
            # Check if the current price is above the SMA
            if close_prices[-1] < sma:
                continue  # Skip symbols trading below their 50 SMA

            # Calculate momentum as the change in price over the lookback period
            momentum = close_prices[-1] - close_prices[-self.lookback_period]
            momentum_scores[symbol] = momentum

        # If no momentum scores were calculated, return
        if not momentum_scores:
            return

        # Find the symbol with the highest momentum
        best_symbol = max(momentum_scores, key=momentum_scores.get)

        # If already invested in the best symbol, do nothing
        if self.Portfolio[best_symbol].Invested:
            return

        # Liquidate any other holdings
        for symbol in self.symbols:
            if symbol != best_symbol and self.Portfolio[symbol].Invested:
                self.Liquidate(symbol)

        # Calculate the amount to invest based on the 80% maximum allocation rule
        available_cash = self.Portfolio.Cash
        max_investment_value = self.Portfolio.TotalPortfolioValue * self.max_allocation_percentage

        # Determine the proportion to allocate to the best symbol
        allocation_percentage = max_investment_value / self.Portfolio.TotalPortfolioValue

        # Invest in the symbol with the highest momentum, limited to 80% of total portfolio value
        self.SetHoldings(best_symbol, allocation_percentage)
        self.Debug(f"Entering Long Position: {best_symbol} with Momentum: {momentum_scores[best_symbol]} and Allocation: {allocation_percentage * 100:.2f}%")