Overall Statistics |
Total Orders 7588 Average Win 0.00% Average Loss 0.00% Compounding Annual Return 158.386% Drawdown 6.500% Expectancy 2.173 Start Equity 1000000.00 End Equity 1101130.27 Net Profit 10.113% Sharpe Ratio 5.552 Sortino Ratio 10.982 Probabilistic Sharpe Ratio 90.945% Loss Rate 16% Win Rate 84% Profit-Loss Ratio 2.79 Alpha 0.981 Beta -0.032 Annual Standard Deviation 0.174 Annual Variance 0.03 Information Ratio 3.125 Tracking Error 0.181 Treynor Ratio -30.198 Total Fees $0.00 Estimated Strategy Capacity $140000.00 Lowest Capacity Asset EURUSD 8G Portfolio Turnover 173.55% |
from AlgorithmImports import * class RangeBoundHedgingAlgorithm(QCAlgorithm): def Initialize(self): self.SetStartDate(2019, 10, 20) self.SetEndDate(2019, 11, 25) self.SetCash(1000000) # 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.01 # 1% 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