Overall Statistics
Total Orders
1524
Average Win
0.14%
Average Loss
-0.26%
Compounding Annual Return
-4.635%
Drawdown
7.700%
Expectancy
-0.025
Start Equity
1000000
End Equity
964932.21
Net Profit
-3.507%
Sharpe Ratio
-1.435
Sortino Ratio
-1.52
Probabilistic Sharpe Ratio
5.305%
Loss Rate
36%
Win Rate
64%
Profit-Loss Ratio
0.53
Alpha
-0.093
Beta
0.103
Annual Standard Deviation
0.06
Annual Variance
0.004
Information Ratio
-0.868
Tracking Error
0.172
Treynor Ratio
-0.84
Total Fees
$24866.61
Estimated Strategy Capacity
$360000.00
Lowest Capacity Asset
BND TRO5ZARLX6JP
Portfolio Turnover
223.43%
Drawdown Recovery
0
from AlgorithmImports import *
from datetime import timedelta

class DCA_SPY_Gold_With_TPSL(QCAlgorithm):
    def initialize(self):
        self.set_start_date(2019, 3, 25)
        self.set_end_date(2019, 7, 1)
        self.set_cash(1_000_000)
        
        # Add SPY, GLD, and BND (bonds)
        self.spy = self.add_equity("SPY", Resolution.MINUTE).symbol
        self.gld = self.add_equity("GLD", Resolution.MINUTE).symbol
        self.bnd = self.add_equity("BND", Resolution.DAILY).symbol

        # Portfolio allocation:
        self.spy_weight = 0.5
        self.gld_weight = 0.5

        # DCA stages for combined portfolio
        self.stages = [.4, .9, 1.0]
        self.stage_index = 0
        self.dca_active = True

        # Set TP/SL as percentage (e.g. 0.05 = 5%)
        self.take_profit_pct = 0.0008  # 0.08% daily gain
        self.stop_loss_pct = -0.010    # -10% daily loss

        self.take_profit = 0.0
        self.stop_loss = 0.0
        self.daily_pnl = 0.0
        self.daily_baseline_value = 0.0
        self.made_tp_sl = False

        self.schedule.on(
            self.date_rules.every_day(),
            self.time_rules.after_market_open(self.spy, 1),
            self.reset_daily
        )
        self.schedule.on(
            self.date_rules.every_day(),
            self.time_rules.every(timedelta(minutes=5)),
            self.dca_rebalance
        )

    def reset_daily(self):
        # Reset daily PnL baseline to current portfolio value
        self.daily_baseline_value = self.portfolio.total_portfolio_value
        
        # Calculate daily targets based on current portfolio value
        self.take_profit = self.daily_baseline_value * self.take_profit_pct
        self.stop_loss = self.daily_baseline_value * self.stop_loss_pct

        self.daily_pnl = 0.0
        self.made_tp_sl = False

        if not (self.portfolio[self.spy].invested or self.portfolio[self.gld].invested):
            # Only start new DCA if not already invested
            self.stage_index = 0
            self.dca_active = True
            self.dca_rebalance()
        else:
            # If we're carrying positions, just reset the daily tracking
            self.debug(f"Carrying positions overnight, resetting daily PnL baseline to ${self.daily_baseline_value:.2f}")

    def dca_rebalance(self):
        if not self.dca_active or self.stage_index >= len(self.stages):
            return
        
        target_total = self.stages[self.stage_index]
        
        # Calculate individual holdings based on weights
        spy_target = target_total * self.spy_weight
        gld_target = target_total * self.gld_weight
        
        # First liquidate bonds if we're entering a new position
        if self.portfolio[self.bnd].invested:
            self.liquidate(self.bnd)
            self.debug("Liquidating bonds to enter SPY/GLD positions")
        
        self.set_holdings(self.spy, spy_target)
        self.set_holdings(self.gld, gld_target)
        
        self.debug(f"DCA stage {self.stage_index+1}: SPY {spy_target*100:.1f}%, GLD {gld_target*100:.1f}% (Total: {target_total*100:.0f}%)")
        self.stage_index += 1

    def invest_in_bonds(self):
        """Invest remaining cash in bonds when not actively trading SPY/GLD"""
        if not (self.portfolio[self.spy].invested or self.portfolio[self.gld].invested):
            # Only invest in bonds if we're not holding SPY or GLD
            if not self.portfolio[self.bnd].invested:
                self.set_holdings(self.bnd, 1.0)  # Invest 100% in bonds
                self.debug("Investing in bonds (BND) - not actively trading SPY/GLD")

    def on_data(self, data: Slice):
        if (self.portfolio[self.spy].invested or self.portfolio[self.gld].invested) and not self.made_tp_sl:
            # Calculate daily PnL from the daily baseline
            self.daily_pnl = self.portfolio.total_portfolio_value - self.daily_baseline_value

            if self.daily_pnl >= self.take_profit:
                self.liquidate(self.spy)  # Liquidate SPY and GLD positions only
                self.liquidate(self.gld)
                self.debug(f"Take profit hit! Daily PnL: +${self.daily_pnl:.2f}")
                self.dca_active = False
                self.made_tp_sl = True
                self.stage_index = 0
                
                # Invest in bonds after liquidating
                self.invest_in_bonds()

            elif self.daily_pnl <= self.stop_loss:
                self.liquidate(self.spy)  # Liquidate SPY and GLD positions only
                self.liquidate(self.gld)
                self.debug(f"Stop loss hit! Daily PnL: ${self.daily_pnl:.2f}")
                self.dca_active = False
                self.made_tp_sl = True
                self.stage_index = 0
                
                # Invest in bonds after liquidating
                self.invest_in_bonds()
        
        # If we're not actively trading and not in bonds, invest in bonds
        elif not (self.portfolio[self.spy].invested or self.portfolio[self.gld].invested or self.portfolio[self.bnd].invested):
            self.invest_in_bonds()