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