Overall Statistics
Total Orders
7563
Average Win
0.50%
Average Loss
-0.30%
Compounding Annual Return
5.212%
Drawdown
24.000%
Expectancy
0.062
Start Equity
100000
End Equity
173982.72
Net Profit
73.983%
Sharpe Ratio
0.183
Sortino Ratio
0.201
Probabilistic Sharpe Ratio
0.366%
Loss Rate
60%
Win Rate
40%
Profit-Loss Ratio
1.67
Alpha
-0.007
Beta
0.392
Annual Standard Deviation
0.137
Annual Variance
0.019
Information Ratio
-0.366
Tracking Error
0.152
Treynor Ratio
0.064
Total Fees
$0.00
Estimated Strategy Capacity
$1100000.00
Lowest Capacity Asset
SHY SGNKIKYGE9NP
Portfolio Turnover
189.50%
# region imports
from AlgorithmImports import *
# endregion

class KellyCriterionSMACrossoverAlgorithm(QCAlgorithm):

    def initialize(self):
        self.set_start_date(2014, 1, 1)
        # Remove fees to focus the research on the portfolio weighting, not the signal.
        self.set_security_initializer(lambda s: s.set_fee_model(ConstantFeeModel(0)))
        # Add the risky and risk-free assets.
        self._risk_asset = self.add_equity('IBM', Resolution.HOUR, leverage=6)
        self._rf_asset = self.add_equity('SHY', Resolution.HOUR, leverage=6)
        # Add some strategy-specific indicators/variables.
        self._risk_asset.short_sma = self.sma(self._risk_asset.symbol, 1)
        self._risk_asset.long_sma = self.sma(self._risk_asset.symbol, 6)
        # Add a warm-up period so we some historical performance of the strategy once we start trading.
        self.set_warm_up(timedelta(365))
        # Add a list and Scheduled Event to track the average exposure to the risky asset.
        self._risky_weights = []
        self.schedule.on(self.date_rules.every_day(self._risk_asset.symbol), self.time_rules.at(23, 59), self._sample_weight)

    def on_data(self, data: Slice):
        # Wait until the market is open.
        if not data.bars or not self.is_market_open(self._risk_asset.symbol) or self.is_warming_up:
            return
        if  not self._risk_asset.holdings.is_long and self._risk_asset.short_sma > self._risk_asset.long_sma:
            self.set_holdings([PortfolioTarget(self._risk_asset.symbol, 1)], True)
        elif self._risk_asset.holdings.is_long and self._risk_asset.short_sma < self._risk_asset.long_sma:
            self.set_holdings([PortfolioTarget(self._rf_asset.symbol, 1)], True)

    def _sample_weight(self):
        self._risky_weights.append(self._risk_asset.holdings.holdings_value / self.portfolio.total_portfolio_value)

    def on_end_of_algorithm(self):
        self.log(f"Average weight: {sum(self._risky_weights) / len(self._risky_weights)}")