Files
manifoldbt/examples/06_full_visualization.py
T
2026-03-18 02:39:46 +00:00

282 lines
10 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
"""Full Visualization Suite -- Bollinger Bands mean-reversion + all plots.
Strategy:
- Long when price touches lower band (oversold)
- Short when price touches upper band (overbought)
- Size proportional to distance from middle band
- Stop-loss 2%, take-profit 4%
Demonstrates every plotting function available in manifoldbt.
Usage:
python examples/06_full_visualization.py
"""
import os
import time
import manifoldbt as mbt
from manifoldbt.indicators import close, bollinger_bands, ema
from manifoldbt.helpers import time_range, Slippage, Interval
upper, middle, lower = bollinger_bands(close, period=20, num_std=2.0)
trend_ema = ema(close, 100)
# Z-score: how far price is from the mean, normalized by band width
band_width = upper - lower
zscore = (close - middle) / (band_width + mbt.lit(1e-12))
# Trend filter: EMA(100) above close = downtrend (no longs), below = uptrend (no shorts)
is_uptrend = close > trend_ema
is_downtrend = close < trend_ema
# -- Strategy -----------------------------------------------------------------
# Entry: touch lower band → long (only in uptrend), touch upper band → short (only in downtrend)
# Exit: long exits at upper band, short exits at lower band
# Size flips to 0 at opposite band = exit
# Long signal: price near lower band + uptrend
long_entry = (zscore < -0.5) & is_uptrend
# Short signal: price near upper band + downtrend
short_entry = (zscore > 0.5) & is_downtrend
# Long exits at upper band (zscore > 0.5), short exits at lower band (zscore < -0.5)
# When neither entry nor in opposite-band exit zone → flat (0)
signal = mbt.when(
long_entry, 1.0, # long
mbt.when(short_entry, -1.0, 0.0), # short / flat
)
strategy = (
mbt.Strategy.create("Reversion_strategy")
.signal("upper", upper)
.signal("lower", lower)
.signal("ema100", trend_ema)
.signal("zscore", zscore)
.size(signal * 0.25)
.describe(
"Bollinger Bands mean-reversion: long at lower band, short at upper band, "
"exit at opposite band. EMA(100) trend filter — no shorts in uptrend, "
"no longs in downtrend."
)
)
# -- Config -------------------------------------------------------------------
start, end = time_range("2021-01-01", "2026-01-01")
ALL_SYMBOLS = list(range(1, 23)) # 22 symbols: BTCUSDT to ARBUSDT
config = mbt.BacktestConfig(
universe=ALL_SYMBOLS,
time_range_start=start,
time_range_end=end,
bar_interval=Interval.minutes(120),
initial_capital=100_000,
execution=mbt.ExecutionConfig(
allow_short=True,
max_position_pct=0.5,
position_sizing_mode="FractionOfInitialCapital",
),
fees=mbt.FeeConfig.zero(),
slippage=Slippage.fixed_bps(0),
warmup_bars=25,
)
# -- Run ----------------------------------------------------------------------
if __name__ == "__main__":
root = os.path.join(os.path.dirname(__file__), "..")
os.makedirs(os.path.join(root, "output"), exist_ok=True)
store = mbt.DataStore(
data_root=os.path.abspath(os.path.join(root, "data")),
metadata_db=os.path.abspath(os.path.join(root, "metadata", "metadata.sqlite")),
)
# -- 1. Single backtest --------------------------------------------------
print("Running backtest...")
t0 = time.perf_counter()
result = mbt.run(strategy, config, store)
elapsed = time.perf_counter() - t0
print(result.summary())
print(f"Elapsed: {elapsed:.3f}s\n")
# -- 2. Tearsheet (3 figures: overview, returns, rolling) ---------------
print("Generating tearsheet...")
mbt.plot.tearsheet(
result, show=True,
save=os.path.join(root, "output", "tearsheet.png"),
)
# -- 3. Summary 3-panel ---------------------------------------------------
mbt.plot.summary(result, show=True)
# -- 4. Candlestick chart (symbol_id=1 matches universe) ----------------
mbt.plot.chart(
result, store, symbol_id=1,
emas=[10, 25],
smas=[50],
n_bars=120,
interactive=False,
show=True,
)
# -- 5. Individual charts -------------------------------------------------
mbt.plot.equity(result, show=True)
mbt.plot.drawdown(result, show=True)
mbt.plot.monthly_returns(result, show=True)
mbt.plot.annual_returns(result, show=True)
mbt.plot.returns_histogram(result, show=True)
mbt.plot.var_chart(result, show=True)
mbt.plot.rolling_sharpe(result, show=True)
mbt.plot.rolling_volatility(result, show=True)
# -- 6. Sweep heatmap 2D -------------------------------------------------
# Sweep over BB period and num_std by rebuilding strategies
print("\nRunning 2D sweep (BB period × num_std)...")
t0 = time.perf_counter()
periods = [10, 15, 20, 30]
stds = [1.5, 2.0, 2.5, 3.0]
sweep_strategies = []
for p in periods:
for ns in stds:
u, m, l = bollinger_bands(close, period=p, num_std=ns)
bw = u - l
zs = (close - m) / (bw + mbt.lit(1e-12))
up = close > trend_ema
dn = close < trend_ema
sig = mbt.when(
(zs < -0.5) & up, 1.0,
mbt.when((zs > 0.5) & dn, -1.0, 0.0),
)
s = (
mbt.Strategy.create(f"bb_p{p}_s{ns}")
.signal("zscore", zs)
.size(sig * 0.25)
.stop_loss(pct=2.0)
.take_profit(pct=4.0)
)
sweep_strategies.append(s)
batch_results = mbt.run_batch_lite(sweep_strategies, config, store)
# Build a sweep_result dict compatible with heatmap_2d
metric_grid = []
idx = 0
for _ in periods:
row = []
for _ in stds:
r = batch_results[idx]
row.append(r.metrics.get("sharpe", 0.0))
idx += 1
metric_grid.append(row)
sweep_result = {
"x_param": "num_std",
"y_param": "period",
"x_values": stds,
"y_values": periods,
"metric": "sharpe",
"metric_grid": metric_grid,
}
print(f"Sweep done in {time.perf_counter() - t0:.1f}s")
mbt.plot.heatmap_2d(sweep_result, show=True)
# -- 7. Walk-forward validation -------------------------------------------
# Manual walk-forward: split 2024 into 5 folds
print("\nRunning walk-forward (manual folds)...")
t0 = time.perf_counter()
fold_months = [
("2024-01-01", "2024-07-01", "2024-07-01", "2024-09-01"),
("2024-01-01", "2024-08-01", "2024-08-01", "2024-10-01"),
("2024-01-01", "2024-09-01", "2024-09-01", "2024-11-01"),
("2024-01-01", "2024-10-01", "2024-10-01", "2024-12-01"),
("2024-01-01", "2024-11-01", "2024-11-01", "2025-01-01"),
]
wf_folds = []
for train_start, train_end, test_start, test_end in fold_months:
ts, te = time_range(train_start, train_end)
train_cfg = mbt.BacktestConfig(
universe=ALL_SYMBOLS, time_range_start=ts, time_range_end=te,
bar_interval=Interval.minutes(60), initial_capital=100_000,
execution=config.execution, fees=config.fees,
slippage=config.slippage, warmup_bars=25,
)
ts2, te2 = time_range(test_start, test_end)
test_cfg = mbt.BacktestConfig(
universe=ALL_SYMBOLS, time_range_start=ts2, time_range_end=te2,
bar_interval=Interval.minutes(60), initial_capital=100_000,
execution=config.execution, fees=config.fees,
slippage=config.slippage, warmup_bars=25,
)
train_r = mbt.run(strategy, train_cfg, store)
test_r = mbt.run(strategy, test_cfg, store)
train_m = train_r.metrics
test_m = test_r.metrics
wf_folds.append({
"train_metric": train_m.get("sharpe", 0.0),
"test_metric": test_m.get("sharpe", 0.0),
})
wf_result = {
"metric": "sharpe",
"folds": wf_folds,
}
print(f"Walk-forward done in {time.perf_counter() - t0:.1f}s")
mbt.plot.walk_forward(wf_result, show=True)
# -- 8. Monte Carlo -------------------------------------------------------
print("\nRunning Monte Carlo (1000 paths)...")
mc_result = mbt.py_run_monte_carlo(result.raw, 1000, 42)
mbt.plot.monte_carlo(mc_result, show=True)
# -- 9. Parameter stability -----------------------------------------------
print("\nRunning stability analysis (BB period)...")
t0 = time.perf_counter()
stability_periods = [10, 12, 15, 18, 20, 25, 30, 40]
stability_metrics = []
for p in stability_periods:
u, m, l = bollinger_bands(close, period=p, num_std=2.0)
bw = u - l
zs = (close - m) / (bw + mbt.lit(1e-12))
up = close > trend_ema
dn = close < trend_ema
sig = mbt.when(
(zs < -0.5) & up, 1.0,
mbt.when((zs > 0.5) & dn, -1.0, 0.0),
)
s = (
mbt.Strategy.create(f"bb_stab_{p}")
.signal("zscore", zs)
.size(sig * 0.25)
.stop_loss(pct=2.0)
.take_profit(pct=4.0)
)
r = mbt.run(s, config, store)
stability_metrics.append(r.metrics.get("sharpe", 0.0))
import numpy as np
mean_m = float(np.mean(stability_metrics))
std_m = float(np.std(stability_metrics))
stab_result = {
"param_name": "period",
"metric": "sharpe",
"values": stability_periods,
"metric_values": stability_metrics,
"mean_metric": mean_m,
"std_metric": std_m,
"stability_score": 1.0 - (std_m / abs(mean_m)) if mean_m != 0 else 0.0,
}
print(f"Stability done in {time.perf_counter() - t0:.1f}s")
mbt.plot.stability(stab_result, show=True)
# -- 10. Research report (composite) --------------------------------------
print("\nGenerating research report...")
mbt.plot.research_report(
sweep_result=sweep_result,
wf_result=wf_result,
stability_result=stab_result,
show=True,
save=os.path.join(root, "output", "research.png"),
)
print("\nDone — all visualizations generated.")
print(f"PNGs saved to {os.path.join(root, 'output')}")