Overall Statistics
Total Orders
25
Average Win
4.88%
Average Loss
-1.25%
Compounding Annual Return
7.717%
Drawdown
11.600%
Expectancy
0.632
Start Equity
100000
End Equity
107017.24
Net Profit
7.017%
Sharpe Ratio
0.236
Sortino Ratio
0.264
Probabilistic Sharpe Ratio
24.392%
Loss Rate
67%
Win Rate
33%
Profit-Loss Ratio
3.90
Alpha
-0.075
Beta
0.628
Annual Standard Deviation
0.163
Annual Variance
0.026
Information Ratio
-0.932
Tracking Error
0.153
Treynor Ratio
0.061
Total Fees
$144.71
Estimated Strategy Capacity
$630000.00
Lowest Capacity Asset
BW W1U0W5MNRKO5
Portfolio Turnover
0.73%
from typing import List, Iterable
from AlgorithmImports import *
from QuantConnect import *
from QuantConnect.Algorithm import QCAlgorithm
from QuantConnect.Data.UniverseSelection import *

class QuarterlyRebalancingAlgorithm(QCAlgorithm):
    def initialize(self) -> None:
        self.set_start_date(2019, 1, 1)
        self.set_end_date(2019, 12, 1)
        self.set_cash(100000)
        self.add_universe(self.coarse_selection_function, self.fine_selection_function)
        self.schedule.on(self.date_rules.month_start(1), self.time_rules.at(9, 31), self.liquidate_positions)
        self.schedule.on(self.date_rules.month_start(1), self.time_rules.at(9, 35), self.rebalance)
        self.set_security_initializer(lambda security: security.set_fee_model(ConstantFeeModel(1)))                     # Set transaction fees and slippage
        self.set_security_initializer(lambda security: security.set_slippage_model(ConstantSlippageModel(0.001)))
        self.quarterly_rebalance = False
        self.selected_symbols: List[symbol] = []
        self.liquidate_positions_counter = 0
        self.rebalance_counter = 0

    def liquidate_positions(self):
        if self.liquidate_positions_counter == 0 or self.liquidate_positions_counter == 3:
            for symbol in list(self.portfolio.keys()):
                self.liquidate(symbol)
            self.liquidate_positions_counter += 1
            if self.liquidate_positions_counter >= 3:
                self.liquidate_positions_counter = 1
        else:
            self.liquidate_positions_counter += 1

    def rebalance(self):
        if self.rebalance_counter == 0 or self.rebalance_counter == 3:
            weight = 1 / 10
            for symbol in self.selected_symbols:
                self.log(",".join([str(symbol.value) for symbol in self.selected_symbols]))
                self.set_holdings(symbol, weight)
            self.rebalance_counter += 1
            if self.rebalance_counter >= 3:
                self.rebalance_counter = 1  
        else:
            self.rebalance_counter += 1
        
    def coarse_selection_function(self, coarse):
        filtered = [x for x in coarse if x.price > 0.5]# and x.dollar_volume > 10000000]
        #filtered = [x for x in filtered if x.primary_exchange in ["NASDAQ", "NYSE"]]
        filtered = [x for x in filtered if not x.symbol.value.endswith(".OB")]
        return [x.symbol for x in filtered]
        
    def fine_selection_function(self, fine: List[FineFundamental]) -> List[Symbol]:
        selected_securities = []
        sortedsec= []
        selected = [c for c in fine if c.has_fundamental_data and not np.isnan(c.valuation_ratios.pe_ratio)]
        for f in selected:
            if f is not None:
                if f.market_cap >= 50000000 and f.valuation_ratios.pe_ratio is not None and f.valuation_ratios.pe_ratio > 0:
                    selected_securities.append(f)
        sorted_securities = sorted(selected_securities, key=lambda x: x.valuation_ratios.pe_ratio, reverse=False)            
        sortedsec = [c.symbol for c in sorted_securities]
        return sortedsec[:10]

    def on_securities_changed(self, changes: Iterable[SecurityChanges]) -> List[Symbol]:
        self.selected_symbols = []
        for security in changes.added_securities:
            self.selected_symbols.append(security.Symbol)