使用 OctoBot 建構與驗證量化交易策略:前進式回測、參數最佳化及互動式分析
In this tutorial, we build a complete quantitative backtesting workflow with OctoBot and OctoBot-Script while keeping the environment isolated from Colab’s preinstalled dependencies.
We configure a rule-based trading strategy that combines RSI-based oversold signals, EMA trend confirmation, and ATR-driven adaptive stop-loss and take-profit levels, and we execute it through OctoBot’s native market-order and backtesting APIs.
We also retrieve historical OHLCV data through OctoBot’s data layer with automatic exchange fallback, perform a multi-parameter grid search over an in-sample period, and select the strongest configuration based on its excess return relative to buy-and-hold.
We then validate the selected parameters on a completely separate out-of-sample period to assess generalization and identify potential overfitting.
Finally, we extract OctoBot’s backtest report data and use Pandas and Plotly to analyze parameter sensitivity, portfolio performance, price action, indicators, and execution results in an interactive Colab environment.
Copy CodeCopiedUse a different BrowserSYMBOL = "BTC/USDT" TIMEFRAME = "1d" EXCHANGES = ["binance", "kucoin", "okx", "bybit", "mexc", "kraken"] INSAMPLE = ("2019-01-01", "2023-01-01") OUTOFSAMPLE = ("2023-01-01", "2025-06-01") GRID = { "rsiperiod": [7, 14, 21], "rsithreshold": [25, 30, 35], "tpatrmult": [3.
0, 5.0], } FIXED = { "emafast": 50, "emaslow": 200, "atrperiod": 14, "slatrmult": 2.0, "positionsize": "20%", "minoffsetpct": 1.0, "maxoffsetpct": 40.0, } VENVDIR = "/content/octobotenv" WORKDIR = "/content/octobotlab" OCTOBOTV = "2.1.1" PYVERSION = "3.
12" import json, os, subprocess, sys, textwrap, time, itertools, shutil os.makedirs(WORKDIR, existok=True) PY = os.path.join(VENVDIR, "bin", "python") MARKER = os.path.join(VENVDIR, ".octobotready") def sh(cmd, kw): """Run a command, streaming its output live into the Colab cell.""" print(f"$ {' '.
join(cmd)}") p = subprocess.Popen(cmd, stdout=subprocess.PIPE, stderr=subprocess.STDOUT, text=True, bufsize=1, kw) for line in p.stdout: print(" " + line.rstrip()) p.wait() if p.returncode != 0: raise RuntimeError(f"command failed ({p.returncode}): {' '.join(cmd)}") if not os.path.
exists(MARKER): print("=" 90, "\n BUILDING OCTOBOT ENVIRONMENT (one-off, ~2 min)\n", "=" 90) subprocess.run([sys.executable, "-m", "pip", "install", "-q", "uv"], check=True) UV = [sys.
executable, "-m", "uv"] sh(UV + ["venv", "--python", PYVERSION, VENVDIR]) sh(UV + ["pip", "install", "--python", PY, "-q", f"OctoBot=={OCTOBOTV}", "wheel", "setuptools", "appdirs==1.4.
4"]) sh(UV + ["pip", "install", "--python", PY, "-q", "--no-build-isolation", "octobot-script"]) sh([PY, "-m", "octobotscript.cli", "installtentacles", "--quite"]) sh([PY, "-c", textwrap.dedent(""" import os, shutil, octobotscript.resources as r base = r.getreportresourcepath("") src, dstdir = os.
path.join(base, "index.html"), os.path.join(base, "dist") os.makedirs(dstdir, existok=True) dst = os.path.join(dstdir, "index.html") if os.path.exists(src) and not os.path.exists(dst): shutil.
copy2(src, dst); print("patched report template ->", dst) else: print("report template already fine") """)]) open(MARKER, "w").
write("ok") print("\n environment ready\n") else: print(" environment already built (delete", VENVDIR, "to rebuild)\n") We define the core trading configuration, including the symbol, timeframe, exchange fallback list, backtesting windows, parameter grid, and fixed strategy settings.
We then create an isolated Python environment with uv and install the pinned OctoBot and OctoBot-Script dependencies required for the workflow.We also install the OctoBot tentacles package and patch the report-template path so later backtest reporting works correctly inside the Colab environment.
Copy CodeCopiedUse a different BrowserWORKER = os.path.join(WORKDIR, "octobotworker.py") WORKERSRC = r''' import asyncio, itertools, json, os, sys, time, traceback import numpy as np import tulipy import octobotscript as obs CFG = json.load(open(os.environ["OBSCONFIG"])) OUT = os.
environ["OBSOUT"] FIX = CFG["fixed"] for kw in ("Close", "High", "Low", "Time", "market", "currentlivetime", "plotindicator"): if not hasattr(obs, kw): raise RuntimeError( f"octobotscript.{kw} missing -> tentacles are not installed." "Run: python -m octobotscript.
cli installtentacles" ) def tail(arrays): """tulipy indicators return different lengths; right-align them all.""" n = min(len(a) for a in arrays) return [np.
asarray(a)[-n:] for a in arrays] def clamp(v): return float(min(max(v, FIX["minoffsetpct"]), FIX["maxoffsetpct"])) def buildcallbacks(params, rundata): """ OctoBot-Script splits a strategy into: initialize(ctx) -> runs once on the first candle.Do vectorised work here.
strategy(ctx) -> runs on EVERY closed candle.Keep it cheap.""" async def initialize(ctx): closes = await obs.Close(ctx, maxhistory=True) highs = await obs.High(ctx, maxhistory=True) lows = await obs.Low(ctx, maxhistory=True) times = await obs.
Time(ctx, maxhistory=True, useclosetime=True) rsi = tulipy.rsi(closes, period=params["rsiperiod"]) emaf = tulipy.ema(closes, period=FIX["emafast"]) emas = tulipy.ema(closes, period=FIX["emaslow"]) atr = tulipy.
atr(highs, lows, closes, period=FIX["atrperiod"]) t, c, rsi, emaf, emas, atr = tail(times, closes, rsi, emaf, emas, atr) atrpct = np.where(c > 0, atr / c 100.0, 0.
0) entries, offsets = set(), {} for i in range(len(t)): oversold = rsi[i] < params["rsithreshold"] uptrend = emaf[i] > emas[i] if oversold and uptrend and atrpct[i] > 0: ts = float(t[i]) entries.
add(ts) offsets[ts] = ( clamp(FIX["slatrmult"] atrpct[i]), clamp(params["tpatrmult"] atrpct[i]), ) rundata["entries"] = entries rundata["offsets"] = offsets if rundata.get("plot"): await obs.plotindicator(ctx, f"RSI({params['rsiperiod']})", t, rsi, entries) await obs.
plotindicator(ctx, f"EMA{FIX['emafast']}", t, emaf) await obs.plotindicator(ctx, f"EMA{FIX['emaslow']}", t, emas) await obs.plotindicator(ctx, "ATR %", t, atrpct) async def strategy(ctx): now = obs.
currentlivetime(ctx) if now not in rundata["entries"]: return sl, tp = rundata["offsets"][now] await obs.market( ctx, "buy", amount=FIX["positionsize"], stoplossoffset=f"-{sl:.2f}%", takeprofitoffset=f"{tp:.2f}%", ) return initialize, strategy def metrics(res): br = res.report.
get("botreport", {}) first = lambda d: float(list(d.values())[0]) if isinstance(d, dict) and d else float("nan") return { "profitability": first(br.get("profitability", {})), "market": first(br.get("marketaverageprofitability", {})), "reference": br.get("referencemarket"), "startportfolio": str(br.
get("startingportfolio")), "endportfolio": str(br.get("endportfolio")), "candles": res.candlescount, "durations": round(res.duration or 0, 2), "errors": res.report.get("errorscount"), } async def loaddata(window): """Try each exchange until one serves data (Binance blocks many datacenter IPs).
""" start, end = window last = None for ex in CFG["exchanges"]: try: print(f" ↓ fetching {CFG['symbol']} {CFG['timeframe']} from {ex} " f"[{time.strftime('%Y-%m-%d', time.gmtime(start))} → " f"{time.strftime('%Y-%m-%d', time.gmtime(end))}]", flush=True) data = await obs.
getdata( CFG["symbol"], CFG["timeframe"], exchange=ex, exchangetype="spot", starttimestamp=start, endtimestamp=end, socialservices=[], ) print(f" ✓ {ex} ok -> {data.datafiles}", flush=True) return data, ex except Exception as e: last = e print(f" ✗ {ex}: {type(e).
name}: {e}", flush=True) raise RuntimeError(f"no exchange served data; last error: {last}") async def backtest(data, params, plot=False, storage=False): rundata = {"entries": None, "offsets": {}, "plot": plot} initf, stratf = buildcallbacks(params, rundata) res = await obs.
run( data, params, strategyfunc=stratf, initializefunc=initf, enablelogs=False, enablestorage=storage, ) return res, len(rundata["entries"] or (
Related
相關文章

仇太深,奧特曼炮轟A社“反人類”
量子位·2026年08月24日 16:01“Codex名字沒起好,我也沒用明白” 《奧特曼天降正義,小嘴抹毒暗諷A社“反人類”》!!這個標題有沒有港媒內味兒了(doge),您先別笑,這還真是奧特曼在最新採訪裡的大致意思。雖然沒有點名,但話裡話外就差直接報Dario Amodei的身份證號了。

消息稱字節整合 AI 生產力:TRAE、釦子併入豆包,將推統一辦公品牌“豆包工作”
作者:沁滄(實習) 責編:沁滄 評論: 感謝網友 HH_KK 的線索投遞!8 月 24 日消息,據智能湧現消息,字節跳動對旗下的辦公 AI 產品完成了一輪團隊整合:TRAE、釦子(Coze)團隊將整體併入豆包體系,其中 TRAE Work、釦子將與豆包在工作場景的產品能力進行整合;TRAE IDE 及 CLI 將作為豆包品牌下的編程產品線持續發展。

AI 大模型周榜:國產 glm-5.3-max 首秀闖入綜合榜前 15,kimi-k3-max 衝進前十
本週AI大模型Arena排行榜出現新面孔,智譜AI的glm-5.3-max首次入榜即拿下綜合榜第13名。月之暗面的kimi-k3-max排名持續攀升,成功擠進綜合榜前十,位列第10名。兩款國產大模型雙雙寫下佳績,成為本週關注焦點。

DeepSeek Harness來了:AI開始製造AI了?
DeepSeek Harness 正式推出,這項新工具被視為 AI 發展的重要里程碑,可能讓 AI 系統具備自主開發或優化其他 AI 的能力。外界關注此技術是否象徵 AI 開始「製造」AI,並可能加速人工智慧的進化與應用。目前相關細節與實際影響仍待進一步觀察。
神秘“牛來”大模型上線即登頂 背後廠商至今未揭曉
近日,一款代號為Ox Alpha的匿名AI模型在OpenRouter悄然上線,短時間內調用量迅速攀升,成功衝至平臺榜首,並刷新了該平臺的單日模型用量紀錄。不過,儘管表現驚豔,Ox Alpha背後的開發主體至今仍未揭曉。
具身智能資本熱浪再起,小鵬機器人首輪估值突破63億美元
本輪融資由IDG資本領投,高榕創投參投,並獲騰訊和阿里巴巴作為戰略投資者共同參與,小鵬集團仍保持控股地位。四家投資方均將該輪視為其在具身智能領域迄今披露的最大規模單筆投資之一。IDG資本指出,小鵬人形機器人代表當前國內產業領先水平,已具備與海外頭部企業全球競爭的技術實力。