| Overall Statistics |
|
Total Orders 52 Average Win 0.56% Average Loss -0.35% Compounding Annual Return -0.167% Drawdown 2.700% Expectancy -0.185 Start Equity 1000.00 End Equity 987.35 Net Profit -1.265% Sharpe Ratio -2.166 Sortino Ratio -0.426 Probabilistic Sharpe Ratio 0.016% Loss Rate 68% Win Rate 32% Profit-Loss Ratio 1.58 Alpha -0.031 Beta 0.002 Annual Standard Deviation 0.014 Annual Variance 0 Information Ratio -0.72 Tracking Error 0.735 Treynor Ratio -19.659 Total Fees $0.00 Estimated Strategy Capacity $7200000.00 Lowest Capacity Asset ETHUSD 2XR Portfolio Turnover 0.24% Drawdown Recovery 1076 |
from AlgorithmImports import *
# Strategy
# Works on daily resolution.
# If price crosses 80% of past ATH.
# Buy with 10% cash,
# If price keeps increase to 2%, keep buying with 10% of remaining cash.
# Stop loss is at 0.5 ATR
# Trailing loss is 0.5% from recent high. (tight stop loss)
class EthAthLadderWithStops(QCAlgorithm):
def initialize(self):
self.set_start_date(2018, 1, 1)
self.set_cash(1000)
# Add ETH
self.eth = self.add_crypto("ETHUSD", Resolution.DAILY).symbol
# Set benchmark as ETHUSD
self.set_benchmark(self.eth)
# Indicators
self.atr_indicator = self.atr(self.eth, 14, MovingAverageType.SIMPLE, Resolution.DAILY)
# State
self.ath = None
self.prev_close = None
self.next_buy_price = None
self.entry_prices = [] # Track each ladder entry
self.stop_levels = [] # Stop loss levels per entry
self.trailing_high = None
self.min_cash_to_trade = 5
def on_data(self, data: Slice):
if not self.atr_indicator.is_ready or self.eth not in data.bars:
return
close = data[self.eth].close
if self.ath is None:
self.ath = close
self.prev_close = close
return
# --- Buy logic ---
prior_ath = self.ath
trigger_level = 0.8 * prior_ath
crossed_up = self.prev_close < trigger_level <= close if self.prev_close else False
if crossed_up and self.portfolio.cash > self.min_cash_to_trade and not self.portfolio[self.eth].invested:
self._enter_trade(close)
if self.next_buy_price is not None and close > self.next_buy_price and self.portfolio.cash > self.min_cash_to_trade:
self._enter_trade(close)
self.next_buy_price = close * 1.02
# --- Update ATH ---
if close > self.ath:
self.ath = close
# --- Risk management ---
self._check_stops(close)
self.prev_close = close
# --- Helpers ---
def _enter_trade(self, price: float):
cash_to_spend = 0.10 * self.portfolio.cash
if cash_to_spend <= self.min_cash_to_trade:
return
qty = cash_to_spend / price
if qty <= 0:
return
self.market_order(self.eth, qty, tag=f"Entry")
self.debug(f"Bought {qty:.6f} ETH at {price:.2f}")
# Save entry info
self.entry_prices.append(price)
self.trailing_high = price if self.trailing_high is None else max(self.trailing_high, price)
# ATR-based stop loss
stop_level = price - 0.5 * self.atr_indicator.current.value
self.stop_levels.append(stop_level)
# First ladder? set ladder target
if self.next_buy_price is None:
self.next_buy_price = price * 1.02
def _check_stops(self, price: float):
if not self.portfolio[self.eth].invested:
return
# --- Trailing stop ---
if self.trailing_high is None or price > self.trailing_high:
self.trailing_high = price
if price <= self.trailing_high * 0.99: # drop 1% from high
self.debug(f"Trailing stop hit at {price:.2f}. Exiting position.")
self._reset_position()
return
# --- ATR stop (per entry) ---
if self.stop_levels:
if price < min(self.stop_levels):
self.debug(f"Stop loss triggered at {price:.2f}. Exiting position.")
self._reset_position()
def _reset_position(self):
self.liquidate(self.eth)
self.entry_prices.clear()
self.stop_levels.clear()
self.trailing_high = None
self.next_buy_price = None