vectorbt-expert by marketcalls/vectorbt-backtesting-skills
npx skills add https://github.com/marketcalls/vectorbt-backtesting-skills --skill vectorbt-expertpython-dotenv + find_dotenv() 从单个根目录 .env 文件加载 — 切勿硬编码密钥openalgo.ta 用于 Supertrend、Donchian、Ichimoku、HMA、KAMA、ALMA、ZLEMA、VWMAopenalgo.ta 用于 exrem、crossover、crossunder、flipNSE_INDEX)template="plotly_dark" 的 Plotly广告位招租
在这里展示您的产品或服务
触达数万 AI 开发者,精准高效
find_dotenv() 从项目根目录的单个 .env 文件加载(从脚本目录向上查找)backtesting/{strategy_name}/ 目录中(按需创建,非预先创建)vbt.MA.run()、vbt.RSI.run() 或任何 VectorBT 内置指标。ta.exrem()、ta.crossover()、ta.crossunder()、ta.flip()。如果无法导入 openalgo.ta(独立的 DuckDB 环境),则使用内联的 exrem() 备用方案。参见 duckdb-data。ta.exrem() 清洗信号。在 exrem 之前始终执行 .fillna(False)。^GSPC),加密货币=比特币 (BTC-USD)。参见 data-fetching 市场选择指南。xaxis type="category" 以避免周末间隙。min_size=1, size_granularity=1。duckdb.connect() 和 read_only=True 直接加载数据。自动检测格式:OpenAlgo Historify(表 market_data,纪元时间戳)与自定义(表 ohlcv,日期+时间列)。参见 duckdb-data。每个主题的详细参考位于 rules/ 目录:
| 规则文件 | 主题 |
|---|---|
| data-fetching | OpenAlgo(印度)、yfinance(美国)、CCXT(加密货币)、自定义提供商、.env 设置 |
| simulation-modes | from_signals、from_orders、from_holding、方向类型 |
| position-sizing | 金额/价值/百分比/目标百分比 头寸规模 |
| indicators-signals | TA-Lib 指标参考、信号生成 |
| openalgo-ta-helpers | OpenAlgo ta:exrem、crossover、Supertrend、Donchian、Ichimoku、移动平均 |
| stop-loss-take-profit | 固定止损、止盈、追踪止损 |
| parameter-optimization | 广播和基于循环的优化 |
| performance-analysis | 统计、指标、基准比较、复合年增长率 |
| plotting | K线图(分类 x 轴)、VectorBT 图表、自定义 Plotly |
| indian-market-costs | 按板块划分的印度市场费用模型 |
| us-market-costs | 美国市场费用模型(股票、期权、期货) |
| crypto-market-costs | 加密货币费用模型(现货、USDT-M、COIN-M 期货) |
| futures-backtesting | 手数规模(SEBI 2025年12月修订版)、价值规模 |
| long-short-trading | 同时做多/做空、方向比较 |
| duckdb-data | DuckDB 直接加载、Historify 格式、自动检测、重采样、多品种 |
| csv-data-resampling | 加载 CSV、按印度市场对齐重采样 |
| walk-forward | 前向分析、WFE 比率 |
| robustness-testing | 蒙特卡洛、噪声测试、参数敏感性、延迟测试 |
| pitfalls | 常见错误及上线前检查清单 |
| strategy-catalog | 包含代码片段的策略参考 |
| quantstats-tearsheet | QuantStats HTML 报告、指标、图表、蒙特卡洛 |
包含实际费用、NIFTY 基准、比较表格和通俗易懂报告的生产就绪脚本:
| 模板 | 路径 | 描述 |
|---|---|---|
| EMA 交叉 | assets/ema_crossover/backtest.py | EMA 10/20 交叉 |
| RSI | assets/rsi/backtest.py | RSI(14) 超卖/超买 |
| 唐奇安通道 | assets/donchian/backtest.py | 唐奇安通道突破 |
| 超级趋势 | assets/supertrend/backtest.py | 包含日内时段的超级趋势 |
| MACD | assets/macd/backtest.py | MACD 信号-蜡烛突破 |
| SDA2 | assets/sda2/backtest.py | SDA2 趋势跟踪 |
| 动量 | assets/momentum/backtest.py | 双重动量(MOM + MOM-of-MOM) |
| 双重动量 | assets/dual_momentum/backtest.py | 季度 ETF 轮动 |
| 买入并持有 | assets/buy_hold/backtest.py | 静态多资产配置 |
| RSI 累积 | assets/rsi_accumulation/backtest.py | 按层级每周 RSI 累积 |
| 前向分析 | assets/walk_forward/template.py | 前向分析模板 |
| 实际成本 | assets/realistic_costs/template.py | 交易成本影响比较 |
import os
from datetime import datetime, timedelta
from pathlib import Path
import numpy as np
import pandas as pd
import talib as tl
import vectorbt as vbt
from dotenv import find_dotenv, load_dotenv
from openalgo import api, ta
# --- 配置 ---
script_dir = Path(__file__).resolve().parent
load_dotenv(find_dotenv(), override=False)
SYMBOL = "SBIN"
EXCHANGE = "NSE"
INTERVAL = "D"
INIT_CASH = 1_000_000
FEES = 0.00111 # 印度交割股票(STT + 法定费用)
FIXED_FEES = 20 # 每单 20 卢比
ALLOCATION = 0.75
BENCHMARK_SYMBOL = "NIFTY"
BENCHMARK_EXCHANGE = "NSE_INDEX"
# --- 获取数据 ---
client = api(
api_key=os.getenv("OPENALGO_API_KEY"),
host=os.getenv("OPENALGO_HOST", "http://127.0.0.1:5000"),
)
end_date = datetime.now().date()
start_date = end_date - timedelta(days=365 * 3)
df = client.history(
symbol=SYMBOL, exchange=EXCHANGE, interval=INTERVAL,
start_date=start_date.strftime("%Y-%m-%d"),
end_date=end_date.strftime("%Y-%m-%d"),
)
if "timestamp" in df.columns:
df["timestamp"] = pd.to_datetime(df["timestamp"])
df = df.set_index("timestamp")
else:
df.index = pd.to_datetime(df.index)
df = df.sort_index()
if df.index.tz is not None:
df.index = df.index.tz_convert(None)
close = df["close"]
# --- 策略:EMA 交叉(TA-Lib) ---
ema_fast = pd.Series(tl.EMA(close.values, timeperiod=10), index=close.index)
ema_slow = pd.Series(tl.EMA(close.values, timeperiod=20), index=close.index)
buy_raw = (ema_fast > ema_slow) & (ema_fast.shift(1) <= ema_slow.shift(1))
sell_raw = (ema_fast < ema_slow) & (ema_fast.shift(1) >= ema_slow.shift(1))
entries = ta.exrem(buy_raw.fillna(False), sell_raw.fillna(False))
exits = ta.exrem(sell_raw.fillna(False), buy_raw.fillna(False))
# --- 回测 ---
pf = vbt.Portfolio.from_signals(
close, entries, exits,
init_cash=INIT_CASH, size=ALLOCATION, size_type="percent",
fees=FEES, fixed_fees=FIXED_FEES, direction="longonly",
min_size=1, size_granularity=1, freq="1D",
)
# --- 基准 ---
df_bench = client.history(
symbol=BENCHMARK_SYMBOL, exchange=BENCHMARK_EXCHANGE, interval=INTERVAL,
start_date=start_date.strftime("%Y-%m-%d"),
end_date=end_date.strftime("%Y-%m-%d"),
)
if "timestamp" in df_bench.columns:
df_bench["timestamp"] = pd.to_datetime(df_bench["timestamp"])
df_bench = df_bench.set_index("timestamp")
else:
df_bench.index = pd.to_datetime(df_bench.index)
df_bench = df_bench.sort_index()
if df_bench.index.tz is not None:
df_bench.index = df_bench.index.tz_convert(None)
bench_close = df_bench["close"].reindex(close.index).ffill().bfill()
pf_bench = vbt.Portfolio.from_holding(bench_close, init_cash=INIT_CASH, fees=FEES, freq="1D")
# --- 结果 ---
print(pf.stats())
# --- 策略 vs 基准 ---
comparison = pd.DataFrame({
"Strategy": [
f"{pf.total_return() * 100:.2f}%", f"{pf.sharpe_ratio():.2f}",
f"{pf.sortino_ratio():.2f}", f"{pf.max_drawdown() * 100:.2f}%",
f"{pf.trades.win_rate() * 100:.1f}%", f"{pf.trades.count()}",
f"{pf.trades.profit_factor():.2f}",
],
f"Benchmark ({BENCHMARK_SYMBOL})": [
f"{pf_bench.total_return() * 100:.2f}%", f"{pf_bench.sharpe_ratio():.2f}",
f"{pf_bench.sortino_ratio():.2f}", f"{pf_bench.max_drawdown() * 100:.2f}%",
"-", "-", "-",
],
}, index=["Total Return", "Sharpe Ratio", "Sortino Ratio", "Max Drawdown",
"Win Rate", "Total Trades", "Profit Factor"])
print(comparison.to_string())
# --- 解释 ---
print(f"* 总回报率:{pf.total_return() * 100:.2f}% vs NIFTY {pf_bench.total_return() * 100:.2f}%")
print(f"* 最大回撤:{pf.max_drawdown() * 100:.2f}%")
print(f" -> 在 Rs {INIT_CASH:,} 上,最糟糕的临时损失 = Rs {abs(pf.max_drawdown()) * INIT_CASH:,.0f}")
# --- 绘图 ---
fig = pf.plot(subplots=['value', 'underwater', 'cum_returns'], template="plotly_dark")
fig.show()
# --- 导出 ---
pf.positions.records_readable.to_csv(script_dir / f"{SYMBOL}_trades.csv", index=False)
import datetime as dt
from pathlib import Path
import duckdb
import numpy as np
import pandas as pd
import talib as tl
import vectorbt as vbt
try:
from openalgo import ta
exrem = ta.exrem
except ImportError:
def exrem(signal1, signal2):
result = signal1.copy()
active = False
for i in range(len(signal1)):
if active:
result.iloc[i] = False
if signal1.iloc[i] and not active:
active = True
if signal2.iloc[i]:
active = False
return result
# --- 配置 ---
SYMBOL = "SBIN"
DB_PATH = r"path/to/market_data.duckdb"
INIT_CASH = 1_000_000
FEES = 0.000225 # 日内股票
FIXED_FEES = 20
# --- 从 DuckDB 加载 ---
con = duckdb.connect(DB_PATH, read_only=True)
df = con.execute("""
SELECT date, time, open, high, low, close, volume
FROM ohlcv WHERE symbol = ? ORDER BY date, time
""", [SYMBOL]).fetchdf()
con.close()
df["datetime"] = pd.to_datetime(df["date"].astype(str) + " " + df["time"].astype(str))
df = df.set_index("datetime").sort_index()
df = df.drop(columns=["date", "time"])
# --- 重采样至 5分钟 ---
df_5m = df.resample("5min", origin="start_day", offset="9h15min",
label="right", closed="right").agg({
"open": "first", "high": "max", "low": "min", "close": "last", "volume": "sum"
}).dropna()
close = df_5m["close"]
# --- 策略 + 回测(与 OpenAlgo 模板相同) ---
每周安装次数
533
代码仓库
GitHub 星标数
104
首次出现
2026年2月25日
安全审计
安装于
codex523
opencode522
cursor514
gemini-cli513
github-copilot513
kimi-cli512
.env via python-dotenv + find_dotenv() — never hardcode keysopenalgo.ta for Supertrend, Donchian, Ichimoku, HMA, KAMA, ALMA, ZLEMA, VWMAopenalgo.ta for exrem, crossover, crossunder, flipNSE_INDEX) by defaulttemplate="plotly_dark".env at project root via find_dotenv() (walks up from script dir)backtesting/{strategy_name}/ directories (created on-demand, not pre-created)vbt.MA.run(), vbt.RSI.run(), or any VectorBT built-in indicator.ta.exrem(), ta.crossover(), ta.crossunder(), ta.flip(). If openalgo.ta is not importable (standalone DuckDB), use inline exrem() fallback. See duckdb-data.Detailed reference for each topic is in rules/:
| Rule File | Topic |
|---|---|
| data-fetching | OpenAlgo (India), yfinance (US), CCXT (Crypto), custom providers, .env setup |
| simulation-modes | from_signals, from_orders, from_holding, direction types |
| position-sizing | Amount/Value/Percent/TargetPercent sizing |
| indicators-signals | TA-Lib indicator reference, signal generation |
| openalgo-ta-helpers | OpenAlgo ta: exrem, crossover, Supertrend, Donchian, Ichimoku, MAs |
| stop-loss-take-profit | Fixed SL, TP, trailing stop |
Production-ready scripts with realistic fees, NIFTY benchmark, comparison table, and plain-language report:
| Template | Path | Description |
|---|---|---|
| EMA Crossover | assets/ema_crossover/backtest.py | EMA 10/20 crossover |
| RSI | assets/rsi/backtest.py | RSI(14) oversold/overbought |
| Donchian | assets/donchian/backtest.py | Donchian channel breakout |
| Supertrend | assets/supertrend/backtest.py | Supertrend with intraday sessions |
| MACD | assets/macd/backtest.py |
import os
from datetime import datetime, timedelta
from pathlib import Path
import numpy as np
import pandas as pd
import talib as tl
import vectorbt as vbt
from dotenv import find_dotenv, load_dotenv
from openalgo import api, ta
# --- Config ---
script_dir = Path(__file__).resolve().parent
load_dotenv(find_dotenv(), override=False)
SYMBOL = "SBIN"
EXCHANGE = "NSE"
INTERVAL = "D"
INIT_CASH = 1_000_000
FEES = 0.00111 # Indian delivery equity (STT + statutory)
FIXED_FEES = 20 # Rs 20 per order
ALLOCATION = 0.75
BENCHMARK_SYMBOL = "NIFTY"
BENCHMARK_EXCHANGE = "NSE_INDEX"
# --- Fetch Data ---
client = api(
api_key=os.getenv("OPENALGO_API_KEY"),
host=os.getenv("OPENALGO_HOST", "http://127.0.0.1:5000"),
)
end_date = datetime.now().date()
start_date = end_date - timedelta(days=365 * 3)
df = client.history(
symbol=SYMBOL, exchange=EXCHANGE, interval=INTERVAL,
start_date=start_date.strftime("%Y-%m-%d"),
end_date=end_date.strftime("%Y-%m-%d"),
)
if "timestamp" in df.columns:
df["timestamp"] = pd.to_datetime(df["timestamp"])
df = df.set_index("timestamp")
else:
df.index = pd.to_datetime(df.index)
df = df.sort_index()
if df.index.tz is not None:
df.index = df.index.tz_convert(None)
close = df["close"]
# --- Strategy: EMA Crossover (TA-Lib) ---
ema_fast = pd.Series(tl.EMA(close.values, timeperiod=10), index=close.index)
ema_slow = pd.Series(tl.EMA(close.values, timeperiod=20), index=close.index)
buy_raw = (ema_fast > ema_slow) & (ema_fast.shift(1) <= ema_slow.shift(1))
sell_raw = (ema_fast < ema_slow) & (ema_fast.shift(1) >= ema_slow.shift(1))
entries = ta.exrem(buy_raw.fillna(False), sell_raw.fillna(False))
exits = ta.exrem(sell_raw.fillna(False), buy_raw.fillna(False))
# --- Backtest ---
pf = vbt.Portfolio.from_signals(
close, entries, exits,
init_cash=INIT_CASH, size=ALLOCATION, size_type="percent",
fees=FEES, fixed_fees=FIXED_FEES, direction="longonly",
min_size=1, size_granularity=1, freq="1D",
)
# --- Benchmark ---
df_bench = client.history(
symbol=BENCHMARK_SYMBOL, exchange=BENCHMARK_EXCHANGE, interval=INTERVAL,
start_date=start_date.strftime("%Y-%m-%d"),
end_date=end_date.strftime("%Y-%m-%d"),
)
if "timestamp" in df_bench.columns:
df_bench["timestamp"] = pd.to_datetime(df_bench["timestamp"])
df_bench = df_bench.set_index("timestamp")
else:
df_bench.index = pd.to_datetime(df_bench.index)
df_bench = df_bench.sort_index()
if df_bench.index.tz is not None:
df_bench.index = df_bench.index.tz_convert(None)
bench_close = df_bench["close"].reindex(close.index).ffill().bfill()
pf_bench = vbt.Portfolio.from_holding(bench_close, init_cash=INIT_CASH, fees=FEES, freq="1D")
# --- Results ---
print(pf.stats())
# --- Strategy vs Benchmark ---
comparison = pd.DataFrame({
"Strategy": [
f"{pf.total_return() * 100:.2f}%", f"{pf.sharpe_ratio():.2f}",
f"{pf.sortino_ratio():.2f}", f"{pf.max_drawdown() * 100:.2f}%",
f"{pf.trades.win_rate() * 100:.1f}%", f"{pf.trades.count()}",
f"{pf.trades.profit_factor():.2f}",
],
f"Benchmark ({BENCHMARK_SYMBOL})": [
f"{pf_bench.total_return() * 100:.2f}%", f"{pf_bench.sharpe_ratio():.2f}",
f"{pf_bench.sortino_ratio():.2f}", f"{pf_bench.max_drawdown() * 100:.2f}%",
"-", "-", "-",
],
}, index=["Total Return", "Sharpe Ratio", "Sortino Ratio", "Max Drawdown",
"Win Rate", "Total Trades", "Profit Factor"])
print(comparison.to_string())
# --- Explain ---
print(f"* Total Return: {pf.total_return() * 100:.2f}% vs NIFTY {pf_bench.total_return() * 100:.2f}%")
print(f"* Max Drawdown: {pf.max_drawdown() * 100:.2f}%")
print(f" -> On Rs {INIT_CASH:,}, worst temporary loss = Rs {abs(pf.max_drawdown()) * INIT_CASH:,.0f}")
# --- Plot ---
fig = pf.plot(subplots=['value', 'underwater', 'cum_returns'], template="plotly_dark")
fig.show()
# --- Export ---
pf.positions.records_readable.to_csv(script_dir / f"{SYMBOL}_trades.csv", index=False)
import datetime as dt
from pathlib import Path
import duckdb
import numpy as np
import pandas as pd
import talib as tl
import vectorbt as vbt
try:
from openalgo import ta
exrem = ta.exrem
except ImportError:
def exrem(signal1, signal2):
result = signal1.copy()
active = False
for i in range(len(signal1)):
if active:
result.iloc[i] = False
if signal1.iloc[i] and not active:
active = True
if signal2.iloc[i]:
active = False
return result
# --- Config ---
SYMBOL = "SBIN"
DB_PATH = r"path/to/market_data.duckdb"
INIT_CASH = 1_000_000
FEES = 0.000225 # Intraday equity
FIXED_FEES = 20
# --- Load from DuckDB ---
con = duckdb.connect(DB_PATH, read_only=True)
df = con.execute("""
SELECT date, time, open, high, low, close, volume
FROM ohlcv WHERE symbol = ? ORDER BY date, time
""", [SYMBOL]).fetchdf()
con.close()
df["datetime"] = pd.to_datetime(df["date"].astype(str) + " " + df["time"].astype(str))
df = df.set_index("datetime").sort_index()
df = df.drop(columns=["date", "time"])
# --- Resample to 5min ---
df_5m = df.resample("5min", origin="start_day", offset="9h15min",
label="right", closed="right").agg({
"open": "first", "high": "max", "low": "min", "close": "last", "volume": "sum"
}).dropna()
close = df_5m["close"]
# --- Strategy + Backtest (same as OpenAlgo template) ---
Weekly Installs
533
Repository
GitHub Stars
104
First Seen
Feb 25, 2026
Security Audits
Gen Agent Trust HubPassSocketPassSnykWarn
Installed on
codex523
opencode522
cursor514
gemini-cli513
github-copilot513
kimi-cli512
DOCX文件创建、编辑与分析完整指南 - 使用docx-js、Pandoc和Python脚本
43,600 周安装
ta.exrem().fillna(False)^GSPC), Crypto=Bitcoin (BTC-USD). See data-fetching Market Selection Guide.xaxis type="category" to avoid weekend gaps.min_size=1, size_granularity=1 for equities.duckdb.connect() with read_only=True. Auto-detect format: OpenAlgo Historify (table market_data, epoch timestamps) vs custom (table ohlcv, date+time columns). See duckdb-data.| Broadcasting and loop-based optimization |
| performance-analysis | Stats, metrics, benchmark comparison, CAGR |
| plotting | Candlestick (category x-axis), VectorBT plots, custom Plotly |
| indian-market-costs | Indian market fee model by segment |
| us-market-costs | US market fee model (stocks, options, futures) |
| crypto-market-costs | Crypto fee model (spot, USDT-M, COIN-M futures) |
| futures-backtesting | Lot sizes (SEBI revised Dec 2025), value sizing |
| long-short-trading | Simultaneous long/short, direction comparison |
| duckdb-data | DuckDB direct loading, Historify format, auto-detect, resampling, multi-symbol |
| csv-data-resampling | Loading CSV, resampling with Indian market alignment |
| walk-forward | Walk-forward analysis, WFE ratio |
| robustness-testing | Monte Carlo, noise test, parameter sensitivity, delay test |
| pitfalls | Common mistakes and checklist before going live |
| strategy-catalog | Strategy reference with code snippets |
| quantstats-tearsheet | QuantStats HTML reports, metrics, plots, Monte Carlo |
| MACD signal-candle breakout |
| SDA2 | assets/sda2/backtest.py | SDA2 trend following |
| Momentum | assets/momentum/backtest.py | Double momentum (MOM + MOM-of-MOM) |
| Dual Momentum | assets/dual_momentum/backtest.py | Quarterly ETF rotation |
| Buy & Hold | assets/buy_hold/backtest.py | Static multi-asset allocation |
| RSI Accumulation | assets/rsi_accumulation/backtest.py | Weekly RSI slab-wise accumulation |
| Walk-Forward | assets/walk_forward/template.py | Walk-forward analysis template |
| Realistic Costs | assets/realistic_costs/template.py | Transaction cost impact comparison |