This script backtests a long-only strategy on ETH-USD.
Signals are generated on daily data, executed next day, and performance
is summarized on weekly bars with a compact plotting dashboard.
It buys only when (1) price is in an uptrend and (2) volume-based accumulation is positive. It exits with an ATR trailing stop. After a stop-out, it waits a few days (cooldown) before re-entering.
Trend filter:
Close Location Value (CLV):
Accumulation/Distribution (AD) line:
ADOSC (fast/slow EMA of AD):
Base entry condition:
True Range:
ATR (EWMA, n=7):
Trailing stop (peak-based):
Peak_t = max(Peak_{t-1}, Close_t)
Stop_t = Peak_t - k*ATR_t (with k = 2.0)
Exit if Close_t <= Stop_t
Execution:
Costs (per position change):
cost_t = turnover_t * (fee_bps + slip_bps) / 10000
strategy_return_t = Position_t * return_t - cost_t
Download data (note the required .droplevel(1, 1)):
import numpy as np
import pandas as pd
import yfinance as yf
ticker = "ETH-USD"
df = yf.download(ticker, period="5y", auto_adjust=False).droplevel(1, 1)
df = df.dropna(subset=["Open","High","Low","Close","Volume"])Compute trend + ADOSC:
fast, slow = 7, 30
ma_len = 30
hl = (df["High"] - df["Low"]).replace(0, np.nan)
clv = ((2*df["Close"] - df["High"] - df["Low"]) / hl).fillna(0.0)
ad = (clv * df["Volume"]).cumsum()
adosc = ad.ewm(span=fast, adjust=False).mean() - ad.ewm(span=slow, adjust=False).mean()
trend = df["Close"].rolling(ma_len).mean()
base = ((df["Close"] > trend) & (adosc > 0)).fillna(False)ATR trailing stop + cooldown (signal-time):
atr_n = 7
atr_k = 2.0
cooldown_days = 5
prev_close = df["Close"].shift(1)
tr = pd.concat([
(df["High"] - df["Low"]),
(df["High"] - prev_close).abs(),
(df["Low"] - prev_close).abs()
], axis=1).max(axis=1)
atr = tr.ewm(alpha=1/atr_n, adjust=False).mean()
pos_sig = np.zeros(len(df), dtype=int)
state, peak, stop, cool = 0, 0.0, 0.0, 0
close = df["Close"].values
base_v = base.values
atr_v = atr.bfill().values
for i in range(len(df)):
if cool > 0:
cool -= 1
if state == 0:
if (cool == 0) and base_v[i]:
state = 1
peak = close[i]
stop = peak - atr_k * atr_v[i]
pos_sig[i] = 1
else:
pos_sig[i] = 0
else:
if close[i] > peak:
peak = close[i]
stop = max(stop, peak - atr_k * atr_v[i])
if close[i] <= stop:
state, peak, stop, cool = 0, 0.0, 0.0, cooldown_days
pos_sig[i] = 0
else:
if base_v[i]:
pos_sig[i] = 1
else:
state, peak, stop = 0, 0.0, 0.0
pos_sig[i] = 0
pos = pd.Series(pos_sig, index=df.index).shift(1).fillna(0)Returns + weekly aggregation:
fee_bps, slip_bps = 10, 5
week_rule = "W-FRI"
ret_d = df["Close"].pct_change().fillna(0)
turnover_d = pos.diff().abs().fillna(0)
cost_d = turnover_d * ((fee_bps + slip_bps) / 10000.0)
strat_d = (pos * ret_d - cost_d).dropna()
strat_w_all = (1 + strat_d).resample(week_rule).prod() - 1
bh_w_all = (1 + ret_d).resample(week_rule).prod() - 1
eq_w = (1 + strat_w_all.fillna(0)).cumprod()
bh_eq_w = (1 + bh_w_all.fillna(0)).cumprod()
dd_w = eq_w / eq_w.cummax() - 1WEEKLY PERFORMANCE SUMMARY (ATR trailing stop + cooldown)
Ticker: ETH-USD | Period: 5y
Rule: Close > MA30 AND ADOSC(7,30) > 0 (next-bar)
Stop: ATR7 k: 2.0 | Cooldown days: 5
Weeks (calendar): 262
Weeks traded (any exposure): 146 | Share: 0.5572519083969466
Strategy Return (full timeline): 2.6514895804456473
Buy&Hold Return (full timeline): 0.1583242364916655
Sharpe (computed on traded weeks only): 1.0372536514048858
Trades (daily position changes): 128
Time in Market (daily avg): 0.38040503557744937
Max Drawdown (full timeline): -0.37655867199981163
Traded weeks per year:
traded_weeks total_weeks pct_traded
year
2021 30 47 0.638298
2022 19 52 0.365385
2023 35 52 0.673077
2024 34 52 0.653846
2025 25 52 0.480769
2026 3 7 0.428571
WEEKLY EXPECTATIONS (TRADED WEEKS ONLY)
Count traded weeks: 146
Mean: 0.012659494020072583
Median: -0.005878062896593261
Std: 0.0880102080122433
Min: -0.19651188772753625
Max: 0.27285900405969654
Ann mean approx: 0.6582936890437743
Win%: 0.4589041095890411 | Loss%: 0.541095890410959
Percentiles:
p01: -0.1372171226200642 p05: -0.09557667231073391 p10: -0.08272669553487838 p25: -0.05020301575408151 p75: 0.05762687686437301 p90: 0.1434738138814331 p95: 0.17320910329592715 p99: 0.23330036001805157
Worst 5 traded weeks:
Date
2021-05-21 -0.196512
2021-07-09 -0.141652
2021-09-10 -0.131797
2026-01-23 -0.110539
2022-07-15 -0.103816
Best 5 traded weeks:
Date
2025-05-09 0.272859
2021-05-07 0.256570
2021-09-03 0.204860
2024-05-24 0.202732
2025-07-18 0.199849