0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

FastAPI + Optunaでウォークフォワード分析Webアプリを作ってみた

Last updated at Posted at 2025-06-30

📌 はじめに

株式や先物のトレード戦略を作る際、過剰最適化を避けるために「ウォークフォワード分析(WFA)」が有効です。

今回は、Pythonのフレームワーク FastAPI と最適化ライブラリ Optuna を使って、
誰でもブラウザからウォークフォワード検証ができるWebアプリを開発しました。

⚙️ 使用技術

  • Python 3.8
  • FastAPI
  • Optuna
  • Matplotlib
  • pandas, numpy 他

🖥️ デモ(Renderで一時公開中)

Render:https://my-flask-app-mksr.onrender.com/

🧠 ウォークフォワード分析とは?

過去データを「学習期間」と「検証期間」に分けて、順次ずらしながら最適化と評価を行う手法。
過剰最適化(カーブフィッティング)を避けやすく、実運用に近い評価が可能です。
バックテストでk1,k2の値を試行回数分最適化して,フォワードテストで最適化されたk1,k2の値を使って評価をする,という流れ
walk foward test.png

🧪 特徴と機能

  • ブラウザ上で戦略のパラメータを入力
  • 処理ログとリアルタイム出力
  • バックテスト後に画像と結果を表示
  • CSVへの結果保存
  • Renderによるスマホアクセス対応
    Noteにもシステムの特徴をざっくり紹介しています。参考まで。
    https://note.com/trade_python/n/ne4d70a11c087

検証結果

🔧 コード抜粋(FastAPI)


from fastapi import FastAPI, Request, Form
from fastapi.responses import HTMLResponse, StreamingResponse, JSONResponse
from fastapi.templating import Jinja2Templates
from fastapi.staticfiles import StaticFiles

app = FastAPI()
templates = Jinja2Templates(directory="templates")
app.mount("/static", StaticFiles(directory="static"), name="static")

@app.get("/", response_class=HTMLResponse)
async def index(request: Request):
  return templates.TemplateResponse("index.html", {
      "request": request,
      "log": None,
      "img_base64": None,
      "knum": None,
      "TrialNum": None,
      "condition": "",
      "condition1": ""
  })

@app.get("/get_image", response_class=JSONResponse)
async def get_image():
  try:
    with open("result/last_img.png", "r") as f:
      base64_img = f.read()
    return {"img": base64_img}
  except:
    return {"img": ""}

@app.post("/run")
async def run(knum: int = Form(...), TrialNum: int = Form(...),
              condition: str = Form(""), condition1: str = Form("")):
  def generate():


## 🔧 コード抜粋(Optunaによるパラメータ最適化)

```python

        def objective(trial,t0,t1):
          if knum>0:
            k1= trial.suggest_int('k1',2,35)
            # k1= trial.suggest_int('k1', 0,23)
            # k1= trial.suggest_float('k1', 0,1)
            k2=0
            k3=0
            k4=0
          if knum>1:
            k2= trial.suggest_int('k2', 2,20)
            # k2= trial.suggest_int('k2', 10,20)
            # k2= trial.suggest_float('k2',0,1)
            k3=0
            k4=0
          if knum>2:
            # k3= trial.suggest_int('k3', 20,80)
            k3= trial.suggest_float('k3', 0,1)
            k4=0
          if knum>3:
            # k4= trial.suggest_int('k4', 1,5)
            k4= trial.suggest_float('k4', 0,1)
          # histry=1.0
          # histry1=backtest(k1,k2,t0,t1,"L",0)
          histry1=backtest(k1,k2,k3,k4,t0,t1,LSt,0,finishList0,rate,Close,Open,Low,High,Vol,condition,condition1)
          return histry1
        step0=2400
        aaa=1750

        if hd=="d":
          step0=100
          aaa=210
        for lp in range(step0*5+aaa,len(Close)-step0-10,step0):
          log_stream = io.StringIO()
          sys.stdout = log_stream  # 標準出力をキャプチャ

          # t0=aaa
          t0=lp-step0*5
          t1=lp
          t2=lp+step0+2
          prog = int(lp/len(Close)*100)
          try:
            optuna.logging.disable_default_handler()
            study = optuna.create_study(direction='maximize', pruner=SuccessiveHalvingPruner())
            study.optimize(lambda trial: objective(trial,t0,t1), n_trials=TrialNum)
            print(f"progress {cur},{prog}%")
          ##print(f"The best parameters are : \n {study.best_params}")
          
          except Exception as e:
            print(f"error {e}")
          finally:
            sys.stdout = sys.__stdout__  # 出力先を戻す

          for line in log_stream.getvalue().splitlines():
            yield f"{line}\n".encode("utf-8")

          if knum==1:
            k1 = study.best_params["k1"]
            k2=0
            k3=0
            k4=0
            histry1=backtest(k1,k2,k3,k4,t1,t2,LSt,1,finishList0,rate,Close,Open,Low,High,Vol,condition,condition1)
            # print("best setting",[k1])
          if knum==2:
            k1 = study.best_params["k1"]
            k2 = study.best_params["k2"]
            k3=0
            k4=0
            histry1=backtest(k1,k2,k3,k4,t1,t2,LSt,1,finishList0,rate,Close,Open,Low,High,Vol,condition,condition1)
            # print("best setting",[k1,k2])

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?