Overall Statistics
Total Orders
8293
Average Win
0.37%
Average Loss
-0.38%
Compounding Annual Return
9.122%
Drawdown
31.000%
Expectancy
0.093
Start Equity
30000
End Equity
112112.21
Net Profit
273.707%
Sharpe Ratio
0.481
Sortino Ratio
0.391
Probabilistic Sharpe Ratio
2.561%
Loss Rate
45%
Win Rate
55%
Profit-Loss Ratio
0.98
Alpha
0
Beta
0
Annual Standard Deviation
0.114
Annual Variance
0.013
Information Ratio
0.605
Tracking Error
0.114
Treynor Ratio
0
Total Fees
$20867.87
Estimated Strategy Capacity
$14000000.00
Lowest Capacity Asset
CDRD R735QTJ8XC9X
Portfolio Turnover
28.66%
# region imports
from AlgorithmImports import *
from alpha_1637 import Strategy001AlphaModel
from porfolio_1737 import Stragety001PortfolioConstructionModel
# endregion

class MeanReversionAlgorithm(QCAlgorithm):

    def initialize(self):
        self.set_start_date(2009, 1, 10)
        self.set_end_date(2024, 5, 12)
        self.set_cash(30000)  # Set Strategy Cash
        
        self.set_brokerage_model(BrokerageName.INTERACTIVE_BROKERS_BROKERAGE, AccountType.MARGIN)

        self.add_universe_selection(ETFConstituentsUniverseSelectionModel("QQQ"))
        self.universe_settings.resolution = Resolution.DAILY

        self.add_alpha(Strategy001AlphaModel())

        self.set_portfolio_construction(Stragety001PortfolioConstructionModel())

        self.set_execution(ImmediateExecutionModel())
    
#region imports
from AlgorithmImports import *
#endregion

class Strategy001AlphaModel(AlphaModel):
    def __init__(self):
        self.entry_rolling_window_dict = {}
        self.exit_rolling_window_dict = {}

    def update(self, algorithm, data):
        entry_insights_list = self.entry_insights_update(algorithm, data)
        exit_insights_list = self.exit_insights_update(algorithm, data)
        insight_group = Insight.group(entry_insights_list + exit_insights_list)

        return insight_group
    
    def exit_insights_update(self, algorithm, data):
        exit_insights_list = []
        for symbol, trade_bar in data.items():
            self.exit_rolling_window_dict[symbol].add(trade_bar)
            
            if (self.exit_rolling_window_dict[symbol].count == 2) and algorithm.portfolio[symbol].invested:
                if (self.exit_rolling_window_dict[symbol][0] is None) or (self.exit_rolling_window_dict[symbol][1] is None):
                    algorithm.log(f"system error:{symbol.value}:self.exit_rolling_window_dict[symbol][0] or [1] is None: trade_bar is None? {trade_bar is None}")
                    return []
                if self.exit_rolling_window_dict[symbol][0].close > self.exit_rolling_window_dict[symbol][1].close:
                    exit_insights_list.append(
                        Insight(
                            symbol=symbol, 
                            period=timedelta(days=1), 
                            type=InsightType.PRICE, 
                            direction=InsightDirection.FLAT,
                            magnitude= self.exit_rolling_window_dict[symbol][0].volume,
                            confidence=self.exit_rolling_window_dict[symbol][0].volume))
                    # algorithm.log(f"exit insight:{symbol.value}")
        return exit_insights_list


    def entry_insights_update(self, algorithm, data):
        entry_insights_list = []
        if data.time.weekday() in [4, 5, 1]:
            for symbol, trade_bar in data.items():
                self.entry_rolling_window_dict[symbol].add(trade_bar)

                if (data.time.weekday() == 1) and (self.entry_rolling_window_dict[symbol].count == 3):
                    if (self.entry_rolling_window_dict[symbol][0] is None) or (self.entry_rolling_window_dict[symbol][1] is None) or (self.entry_rolling_window_dict[symbol][2] is None):
                        algorithm.log(f"system error:{symbol.value}:self.entry_rolling_window_dict[symbol][0] or [1] or [2] is None: trade_bar is None? {trade_bar is None}")
                        return []
                    flag = True
                    flag = flag and (self.entry_rolling_window_dict[symbol][0].close < self.entry_rolling_window_dict[symbol][1].close)
                    flag = flag and (self.entry_rolling_window_dict[symbol][1].close < self.entry_rolling_window_dict[symbol][2].close)
                    if flag:
                        entry_insights_list.append(
                            Insight(
                                symbol=symbol, 
                                period=timedelta(days=1), 
                                type=InsightType.PRICE, 
                                direction=InsightDirection.UP, 
                                magnitude=self.entry_rolling_window_dict[symbol][0].volume,
                                confidence=self.entry_rolling_window_dict[symbol][0].volume))
                        # algorithm.log(f"entry insight:{symbol.value}")
                        
        return entry_insights_list

    def on_securities_changed(self, algorithm, changes):
        for security in changes.added_securities:
            self.entry_rolling_window_dict.setdefault(security.symbol, RollingWindow[TradeBar](3))
            self.exit_rolling_window_dict.setdefault(security.symbol, RollingWindow[TradeBar](2))
#region imports
from AlgorithmImports import *
#endregion

 # Portfolio construction scaffolding class; basic method args.
class Stragety001PortfolioConstructionModel(PortfolioConstructionModel):
    # Create list of PortfolioTarget objects from Insights
    def create_targets(self, algorithm: QCAlgorithm, insights: List[Insight]) -> List[PortfolioTarget]:
        return super().create_targets(algorithm, insights)

    # Determines the target percent for each insight
    def determine_target_percent(self, activeInsights: List[Insight]) -> Dict[Insight, float]:
        targets = {}
        securities_count = 0
        for security_holding in self.algorithm.portfolio.values():
            if security_holding.invested:
                securities_count += 1
        self.algorithm.log(f"securities_count:{securities_count}")
        
        sorted_acive_insights = sorted(activeInsights, key=lambda x: x.confidence, reverse=True)

        # exit
        for insight in sorted_acive_insights:
            if insight.direction == InsightDirection.FLAT:
                targets[insight] = 0.0
                securities_count -= 1
            if securities_count > 5 or securities_count < 0:
                self.algorithm.log(f"securities_count:{securities_count}:exit")
                continue
            assert (securities_count >= 0) and (securities_count <= 5)

        # # entry
        for insight in sorted_acive_insights:
            if (insight.direction == InsightDirection.UP) and (securities_count < 5):
                targets[insight] = 0.2
                securities_count += 1
            if securities_count > 5 or securities_count < 0:
                self.algorithm.log(f"securities_count:{securities_count}:entry")
                continue
            assert (securities_count >= 0) and (securities_count <= 5)

        log_string = ""
        for t in targets:
            log_string += t.to_string() + "; "
        self.algorithm.log(log_string)

        return targets