Created with Highcharts 12.1.2EquityJan 1Jan…Jan 3Jan 5Jan 7Jan 9Jan 11Jan 13Jan 15Jan 17Jan 19Jan 21Jan 23Jan 25Jan 27Jan 29Jan 31Feb 2Feb 4Feb 6Feb 8Feb 10Feb 12Feb 14Feb 1690k100k110k120k-4-2002550-5050200k400k02.5M5M0510
Overall Statistics
Total Orders
528
Average Win
0.00%
Average Loss
0.00%
Compounding Annual Return
115.160%
Drawdown
2.600%
Expectancy
-0.060
Start Equity
100000.00
End Equity
109839.44
Net Profit
9.839%
Sharpe Ratio
7.783
Sortino Ratio
10.114
Probabilistic Sharpe Ratio
96.766%
Loss Rate
52%
Win Rate
48%
Profit-Loss Ratio
0.94
Alpha
0.587
Beta
0.367
Annual Standard Deviation
0.09
Annual Variance
0.008
Information Ratio
3.864
Tracking Error
0.103
Treynor Ratio
1.897
Total Fees
$0.00
Estimated Strategy Capacity
$220000.00
Lowest Capacity Asset
EURUSD 8G
Portfolio Turnover
83.31%
from AlgorithmImports import *

class RangeBoundHedgingAlgorithm(QCAlgorithm):
    def Initialize(self):
        self.SetStartDate(2020, 1, 1)
        self.SetEndDate(2020, 2, 15)
        self.SetCash(100000)
        
        # Trading pair setup
        self.symbol = self.AddForex("EURUSD", Resolution.Minute).Symbol
        
        # Strategy parameters
        self.r2r = 2  # Risk to reward ratio
        self.buyLine = None
        self.sellLine = None
        self.maxDrawdown = 0.02  # 2% maximum drawdown
        self.positionSize = 0.1  # 10% of portfolio per trade
        
        # Position tracking
        self.openBuyLots = 0
        self.openSellLots = 0
        
        # Technical indicators
        self.sma = self.SMA(self.symbol, 20)
        self.atr = self.ATR(self.symbol, 14)
        self.high = self.MAX(self.symbol, 20)
        self.low = self.MIN(self.symbol, 20)
        
        # Risk management setup
        self.SetRiskManagement(RangeBoundRiskManagement(self.maxDrawdown))
        
        # Warm up period
        self.SetWarmUp(20)

    def OnData(self, data):
        if self.IsWarmingUp or not data.ContainsKey(self.symbol): 
            return

        self.UpdateRangeLevels()
        
        # Check if range levels are properly initialized
        if self.buyLine is None or self.sellLine is None:
            return
            
        current_price = data[self.symbol].Close
        
        # Risk checks
        if self.IsExcessiveDrawdown() or self.IsVolatilityHigh():
            self.LiquidatePositions() # Custom Liquidate function that also resets out variables
            return
            
        # Trading logic
        if current_price <= self.buyLine:
            buy_lots = self.CalculateBuyLots()
            if buy_lots > 0 and self.CanTrade():
                self.ExecuteBuyOrder(buy_lots)
                
        elif current_price >= self.sellLine:
            sell_lots = self.CalculateSellLots()
            if sell_lots > 0 and self.CanTrade():
                self.ExecuteSellOrder(sell_lots)

    def OnOrderEvent(self, orderEvent):
        if orderEvent.Status == OrderStatus.Filled:
            order = self.Transactions.GetOrderById(orderEvent.OrderId)
            # Update position tracking
            if order.Direction == OrderDirection.Buy:
                self.openBuyLots += abs(order.Quantity)
            else:
                self.openSellLots += abs(order.Quantity)

    def LiquidatePositions(self):
        self.Liquidate()
        self.openBuyLots = 0
        self.openSellLots = 0


    def CalculateBuyLots(self):
        """Calculate buy lot size with position sizing"""
        if self.openBuyLots == 0 and self.openSellLots == 0:
            # Initial trade size
            return self.Portfolio.TotalPortfolioValue * self.positionSize
        base_lots = ((self.r2r + 1) / self.r2r * self.openSellLots - self.openBuyLots) * 1.1
        return min(base_lots, self.Portfolio.TotalPortfolioValue * self.positionSize)

    def CalculateSellLots(self):
        """Calculate sell lot size with position sizing"""
        if self.openBuyLots == 0 and self.openSellLots == 0:
            # Initial trade size
            return self.Portfolio.TotalPortfolioValue * self.positionSize
        base_lots = ((self.r2r + 1) / self.r2r * self.openBuyLots - self.openSellLots) * 1.1
        return min(base_lots, self.Portfolio.TotalPortfolioValue * self.positionSize)

    def ExecuteBuyOrder(self, lots):
        """Execute buy order with risk management"""
        stop_price = self.buyLine - self.atr.Current.Value
        take_profit = self.buyLine + (self.atr.Current.Value * self.r2r)
        
        ticket = self.MarketOrder(self.symbol, lots)
        if ticket.Status == OrderStatus.Filled:
            self.openBuyLots += lots
            self.StopMarketOrder(self.symbol, -lots, stop_price)
            self.LimitOrder(self.symbol, -lots, take_profit)

    def ExecuteSellOrder(self, lots):
        """Execute sell order with risk management"""
        stop_price = self.sellLine + self.atr.Current.Value
        take_profit = self.sellLine - (self.atr.Current.Value * self.r2r)
        
        ticket = self.MarketOrder(self.symbol, -lots)
        if ticket.Status == OrderStatus.Filled:
            self.openSellLots += lots
            self.StopMarketOrder(self.symbol, lots, stop_price)
            self.LimitOrder(self.symbol, lots, take_profit)

    def IsExcessiveDrawdown(self):
        """Check if current drawdown exceeds threshold"""
        # Calculate unrealized profit percentage manually
        total_unrealized_profit_pct = (self.Portfolio.TotalUnrealizedProfit / 
                                    self.Portfolio.TotalPortfolioValue)
        return total_unrealized_profit_pct < -self.maxDrawdown

    def IsVolatilityHigh(self):
        """Check if current volatility is too high"""
        return self.atr.Current.Value > self.atr.Current.Value * 1.5  # Compare to historical average

    def CanTrade(self):
        """Check if trading conditions are met"""
        return (
            self.Portfolio.MarginRemaining > self.Portfolio.TotalPortfolioValue * 0.25 and
            not self.IsExcessiveDrawdown() and
            not self.IsVolatilityHigh()
        )

    def UpdateRangeLevels(self):
        """Update support and resistance levels"""
        if not all([self.high.IsReady, self.low.IsReady, self.atr.IsReady]):
            return
            
        # Only update range levels if they haven't been set or significant time has passed
        if self.buyLine is None or self.sellLine is None:
            self.buyLine = self.low.Current.Value
            self.sellLine = self.high.Current.Value
            
            # Add buffer to range levels using ATR
            range_buffer = self.atr.Current.Value * 0.5
            self.buyLine -= range_buffer
            self.sellLine += range_buffer
            
            self.Plot("Range Levels", "Support", self.buyLine)
            self.Plot("Range Levels", "Resistance", self.sellLine)


class RangeBoundRiskManagement(RiskManagementModel):
    def __init__(self, maximum_drawdown_percent):
        self.maximum_drawdown_percent = maximum_drawdown_percent
        self.exit_triggered = False
        
    def ManageRisk(self, algorithm, targets):
        # Calculate total unrealized profit percent
        total_unrealized_profit_pct = (algorithm.Portfolio.TotalUnrealizedProfit / 
                                     algorithm.Portfolio.TotalPortfolioValue)
        
        if total_unrealized_profit_pct < -self.maximum_drawdown_percent:
            self.exit_triggered = True
            return [PortfolioTarget(symbol, 0) for symbol in algorithm.Securities.Keys]
            
        if self.exit_triggered and total_unrealized_profit_pct >= 0:
            self.exit_triggered = False
            
        return targets