ページ戦略のライフサイクル · 8 / 29
ストラテジーのライフサイクル
QuantCraft のストラテジーは、少数のコールバック関数を定義する通常の Python ファイルです。バックテストエンジンは、過去データを順に進めながら、それらの関数を決まった順序で呼び出します。自分で呼び出す必要はなく、エンジンが呼び出します。Test を使って実行します(コードの実行)。
最初から最後までのライフサイクル:
on_init → on_bar → on_tick (×4 per bar) → account tick(close) → on_finish
└─────────── repeats for every bar ───────────┘この図はバックテストでの流れです。on_tick は各バーの合成 OHLC に基づいて 1 バーあたり 4 回発火します(実際のティック履歴はありません)。その 4 回のティックの後、エンジンは終値でアカウントの最終的な時価評価を行います。これは 5 回目の on_tick ではありません。on_timer は、timerIntervalMs が設定されたフォワード実行(チャートフォワード、バルクフォワード/QuantCloud)でのみ利用でき、IDE の Run backtest ダイアログにはタイマー設定がありません。フォワード実行では、on_tick は接続済みブローカーからの価格更新のたびに実行され、日中の境界ではしばしば on_tick の後に on_bar が発火します。バックテストの順序を前提にしないでください。
このページでは、各コールバック、複数シンボルのストラテジーがそれらにどう組み込まれるか、そして最初のストラテジーを書くための基本を説明します。
ストラテジーファイル
最小限のストラテジーは次のようになります。
import quantcraft.process.runtime as bt # account, chart_indicators, qc_* handles
from quantcraft import Timeframe
import quantcraft.runtime as rt
def on_init(symbols=None):
pass
def on_bar(bar_index, bar, fundamentals=None, calendar=None, symbol=None):
close_px = float(bar["close"])
daily = rt.get_bar(Timeframe.DAILY, shift=1)
def on_tick(bar_index, tick_in_bar, price, bar, fundamentals=None, calendar=None, symbol=None):
if tick_in_bar == 3:
close_px = price
def on_finish(bars, symbol=None, bars_by_symbol=None):
passすべてのコールバックに共通するルールがいくつかあります。
- ファイルのトップレベルの関数でなければなりません(ネストしたり、クラス内に置いたりしない)。
- バックテストエンジンは、呼び出し可能な
on_init、on_bar、on_tick、on_finishを必要とします(そうでないとUser script must define callable: …で実行が失敗します)。アクティブな.pyタブがon_init/on_bar/on_finishを定義していると、IDE の Test、Compile、Debug Test ボタンが表示されます。on_tickがない場合は、バックテストの開始時に失敗します。on_timerは任意で、タイマー間隔付きのフォワード実行でのみ使われます。 - 使う任意引数だけを追加してください。
calendar、symbols(on_init)、on_finishの引数は名前で対応付けられます。fundamentalsとsymbolは一般的な形を順に試して対応付けられるため、ドキュメントの順序を保ってください。最も安全な完全なシグネチャはdef on_bar(bar_index, bar, fundamentals=None, calendar=None, symbol=None)です。宣言されたcalendarには、必ずカレンダーハンドルかNoneが渡され、ファンダメンタルデータのオブジェクトが渡されることはありません。 - コールバック内では、
accountを通じて注文を出し、chart_indicators(インジケーター系列)を通じてインジケーターの値を記録します。どちらもquantcraft.process.runtimeから読み取ります。
ランタイムのグローバル
エンジンは、コールバックが実行される前に、quantcraft.process.runtime から次のものを公開します。
| 名前 | 目的 |
|---|---|
account | Simulation タブ(残高+手数料/スリッページ)から構成された PaperAccount インスタンス。フォワード実行では、同じ API を持ち実際の注文を出す実ブローカーアカウントです — アカウントモデルを参照 |
chart_indicators | 結果にマージされる ChartIndicatorSeries コレクター — 実行がバーを進める間に値を収集し、on_finish で系列を出力する |
qc_fundamentals | ファンダメンタルデータが読み込まれている場合の、任意の QcFundamentals ハンドル |
qc_fama_french | グローバルな Fama-French ファクターが読み込まれている場合の、任意の QcFamaFrench。コールバックでは fama_french.qc を推奨 — Fama-French ファクターを参照 |
qc_calendar | BacktestCalendar | None — Include calendar data が有効なときのカレンダーデータで、取引されるすべてのシンボルをカバーします。バックテスト専用 — フォワード実行では常に None。コールバックでは calendar 引数を指定することで同じハンドルを受け取れます — カレンダーデータを参照 |
qc_ohlcv_by_tf | 時間枠の文字列をキーとする、任意の副時間枠バッファ(上級者向け。get_bar を推奨) |
get_bar(tf, shift=1) | プライマリと追加の時間枠向けの推奨 API — BarSnapshot または None を返します。import quantcraft.runtime as rt でインポート |
全系列の計算には on_finish(bars, ...) の bars 引数を使ってください。IDE のストラテジーでは、モジュールレベルの qc_ohlcv に頼らないでください(インポート時に None であることが多く、スクリーナーターゲットの実行では None で、それ以外ではウォームアップを含む読み込み範囲全体に及びます)。コールバックでは bar オブジェクトから価格を読んでください。isinstance(bar, dict) は False です — OhlcvBarView は Mapping です(dict 形式のアクセスは引き続き機能します)。
これらはモジュール属性として読み取ってください。各ハンドルはセッションごとの値に支えられているため、
bt.account/bt.chart_indicatorsは常にこの実行を指します。from quantcraft.process.runtime import accountはスナップショットを束縛します。バックテストではルートのアルゴファイルであれば問題ありません(エンジンが読み込み後にそれらの名前を再束縛するため。フォワード実行はaccount、qc_fundamentals、qc_fama_frenchのみを再束縛するので、そこではbt.chart_indicatorsとrt.qc_ohlcv*を使ってください)。ただし、sys.modulesにキャッシュされたままで最初のセッションのハンドルを保持してしまう分割されたヘルパーモジュールでは誤りです。モジュールハンドルとインポート名の違いを参照してください。
副時間枠
実行のプライマリ時間枠は、常に rt.get_bar で利用できます。Run backtest ダイアログの Symbol & data にある Additional timeframes ドロップダウンで追加のバーサイズを選択し(バックテスト)、任意のコールバックから読み取ります。
from quantcraft import Timeframe
import quantcraft.runtime as rt
def on_bar(bar_index, bar, fundamentals=None, calendar=None, symbol=None):
daily = rt.get_bar(Timeframe.DAILY, shift=1)
if daily and bar.close[0] > daily.close:
account.open_trade(...)shift の完全な意味、Timeframe 列挙型、フォワード実行の注意点は、OHLCV とバーデータにあります。
約定のタイミング
on_barのbar["close"]は、現在の実行バーの終値です。account.open_trade/close_tradeは要求価格を受け取り、その実行のスリッページと手数料のモデルを適用します。保存されるentry_price/exit_priceは約定価格で、要求した価格はraw_entry_price/raw_exit_priceとして保持されます。デフォルトの出来高参加率スリッページでは、約定は通常、渡した価格と異なります。on_barからバーの終値を狙うには、float(bar["close"])を渡します(引き続きスリッページ/手数料の対象)。- バー内シミュレーション中に約定させるには、
tick_in_bar == 3のときにon_tickから呼び出します —priceはその行におけるそのシンボルの終値です。
on_init — 準備する
エンジンがアカウントを準備し、市場データを読み込み、ランタイムを構築した直後、かつバーが処理される前に実行されます。
次の目的で使います。
- バー間でストラテジーが保持する変数(カウンター、ステートマシン、パラメータ値)を初期化する。
- インジケーターやウォームアップのバッファを設定する。
- 実行の残りを制御するために、入力パラメータを読み取る。
def on_init(symbols=None):
global lookback, position_size
lookback = 20
position_size = 100on_init では現在のバーにアクセスできません。この時点ではシミュレーションがまだ始まっていないためです。
symbols パラメータ(スクリーナー実行のみ):バックテストがスクリーナーのシンボルソースを使う場合、新しいユニバースの symbols=<ティッカーのリスト> を伴って、リバランスのたびに on_init が再度発火します。これにより、ストラテジーはシミュレーションを再起動することなく、新しいティッカーの集合に対応できます。
- 実行開始時、
on_initは短時間のうちに2 回発火します。1 回目はsymbols=None(標準のセットアップ呼び出し)、2 回目はsymbols=<最初のティッカー>です。 - 再発火では、モジュールレベルの状態はリセットされません。最初の呼び出しで設定した変数はそのまま残ります。
def on_init():(パラメータなし)を定義したストラテジーは、これまでとまったく同じように動作します。エンジンは引数を名前で対応付けるため、存在しないパラメータは単に無視されます。
on_bar — バーごとに 1 回
実行バーごとに 1 回、そのバーの 4 回のバー内ティックの前に実行されます。ほとんどのストラテジーは、ここで判断を行います。
def on_bar(bar_index, bar, fundamentals=None, calendar=None, symbol=None):
if bar.bars_back < 1:
return
if bar.close[0] > bar.close[1]:
account.open_trade(...)パラメータ:
bar_index— 現在の実行バーの 0 始まりのインデックス。bar— 現在のバーのビュー(OhlcvBarView)。読み取り専用のマッピング(bar["open"]、bar["high"]、bar["low"]、bar["close"]、bar["volume"]、bar["t"])のように動作し、オフセットによる履歴も公開します。bar.close[0]は現在のバーの終値、bar.close[1]は 1 つ前のバー、というようになります。len(bar.close)とbar.bars_backは、利用できる履歴の量を示します。通常のdictではありません(isinstance(bar, dict)はFalse)。fundamentals— このバーのファンダメンタルデータのスナップショット。設定されていない場合はNone。calendar— Include calendar data が有効な場合はBacktestCalendarハンドル、それ以外はNone(フォワード実行では常にNone)。完全な API はカレンダーデータを参照してください。引数は名前で対応付けられるため、このパラメータを省略してもコールバックは変わりません。symbol— 大文字のティッカー文字列。複数シンボルのストラテジーで便利で、1 つのシンボルだけ読み込む場合は省略できます。
一般的なパターン:
- バーの終値でエントリー/エグジットを判断する:
bar.close[0](またはfloat(bar["close"]))をaccount.open_trade/account.close_tradeに渡します。 bar.close[0]、bar.close[1]、… のようなオフセットを読んで、単純なインジケーターを計算する。- 履歴が足りない序盤のバーをスキップする(
if bar.bars_back < N: return)。
ヒント:
on_barは、同じバーのティックのコールバックより常に前に実行されます。
on_tick — バー内およびライブの価格更新
on_tick はバックテストでもフォワード実行でも同じシグネチャですが、いつ発火するか、price が何を意味するかは実行モードによって異なります。
バックテスト(Test)
on_bar の後、バー内の動きをシミュレートするために1 バーあたり最大 4 回実行されます。エンジンは各バーの OHLC を次の順に進めます。
tick_in_bar | 意味 |
|---|---|
0 | バーの始値 |
1 | バーの高値 |
2 | バーの安値 |
3 | バーの終値 |
バックテストには実際のティックデータはありません。過去の実行ではブローカーのティック履歴を読み込みません。1 バーあたり 4 回の呼び出しは、各バーの OHLC だけから導き出された合成価格を使います。シミュレーションでバー内の約定を近似することはできますが、バックテストの
on_tickの動作は、ライブのフォワードティックとは一致しません。バーの終値での判断にはon_barで十分です。tick_in_bar == 3のon_tickは、シミュレーションではバーの終値で行動するのと同じです。
def on_tick(bar_index, tick_in_bar, price, bar, fundamentals=None, calendar=None, symbol=None):
if tick_in_bar == 3:
# Backtest simulation: closing tick fill
account.close_trade(position_id, price)シミュレーションでバー内のタイミングが重要なとき、たとえば終値のティックで約定させる、バー内のストップをシミュレートする、バーの高値や安値へのタッチを評価する、といった場合にバックテストの on_tick を使います。
フォワード実行(チャートフォワード、バルクフォワード/QuantCloud)
フォワード実行では、アルゴがアタッチされている間、プラットフォームが接続済みブローカーから価格を受け取るたびに on_tick が実行されます。固定の 4 ティックの OHLC ウォークはなく、各呼び出しはライブの気配を反映します。
def on_tick(bar_index, tick_in_bar, price, bar, fundamentals=None, calendar=None, symbol=None):
# Forward run: react to each broker price update
if price <= stop_level:
account.close_trade(position_id, price)price— このシンボルの最新のブローカー気配。bar— 履歴用の現在のバーのビュー(バックテストと同じ形)。tick_in_bar— シグネチャ互換性のために残っている場合があります。フォワード実行では、合成 OHLC のフェーズではなくpriceで分岐してください。
フォワードの on_tick は、ストップ、トレーリングエグジット、バーの終値の合間のマークへの反応など、ライブのバー内ロジックに使います。
パラメータ(両モード共通)
bar_index—on_barと同じ。tick_in_bar— バックテストでは0、1、2、3(合成 OHLC のステップ)。フォワード実行では、バーのフェーズのロジックに頼らないでください。price— バックテストでは合成 OHLC のステップ、フォワード実行ではライブのブローカー気配。bar—on_barと同じバービュー。fundamentals、calendar、symbol—on_barと同じ意味。
バックテストでバーの終値での判断だけが必要なら、
on_tickを完全に無視して、ロジックをon_barに置くことができます。
on_timer — 任意の定期コールバック(フォワード実行のみ)
IDE のバックテスト: Run backtest ダイアログは、タイマー間隔を設定しません。ストラテジーファイルに
on_timerを定義しても、IDE のバックテスト中には呼び出されません。バックテストでは、定期的または時間ベースのロジックをon_barまたはon_tickに置いてください。
フォワード実行(チャートフォワードテスト、バルクフォワード/QuantCloud)では、実行ダイアログで timerIntervalMs を設定できます。設定されている場合、on_timer はティックの合間に一定の時間間隔で実行されますが、2 つの条件がそろったときだけです。
- その実行に
timer_interval_ms/timerIntervalMs(ミリ秒単位の正の整数)を設定している。 - ファイルに
on_timer関数が定義されている。
def on_timer(bar_index, tick_in_bar, price, bar, fundamentals=None, calendar=None, symbol=None):
passシグネチャは on_tick と同じ形です。on_timer は、時間ベースの動作(たとえば、動きの遅いシグナルを毎分更新する、バー数とは無関係にチェックをスケジュールする)に使います。timer_interval_ms が設定されていない場合は、定義されていても on_timer は呼び出されません。
コールバック内のカレンダーデータ
Symbol & data タブで Include calendar data が有効な場合、calendar パラメータを追加すれば、どのコールバックでも BacktestCalendar ハンドルを受け取れます。
def on_bar(bar_index, bar, calendar=None, symbol=None):
if calendar is None: # always guard — not every run enables calendar data
return
days = calendar.days_to_earnings(bar["t"], symbol)
if days is not None and days <= 2:
return # avoid trading into an earnings print
divs = calendar.dividends(bar["t"], symbol)
if divs and (divs[-1].amount or 0) > 0.5: # amount can be None
...同じハンドルは on_tick と on_timer でも利用できますが、カレンダーデータはバックテスト専用なので、フォワード実行では calendar は常に None です。すべての参照はポイントインタイムです。dividends(bar["t"], symbol) は、そのバーの時点で公表されていた配当だけを返し、将来のものは返しません。
主なメソッド:
| メソッド | 戻り値 |
|---|---|
dividends(bar_time, symbol=None) | そのバーの時点で公表されていた配当(古い順) |
splits(bar_time, symbol=None) | そのバーまでに発表された株式分割(古い順) |
next_earnings(bar_time, symbol=None) | {date, basis, estimated, …} または None |
days_to_earnings(bar_time, symbol=None) | int または None |
has(symbol=None) | このシンボルにカレンダーデータがあるかどうか |
リストは古い順です — [-1] が最新のイベントです。これは、インデックス 0 が最新の期間であるファンダメンタルデータとは逆です。
コールバック引数の代わりに quantcraft.process.runtime から読み取りたい場合は、ランタイムのグローバル qc_calendar が同じハンドルを保持しています。
イベントのフィールドスキーマ、コーポレートアクションのシミュレーションの詳細、トラブルシューティングについては、カレンダーデータを参照してください。
on_finish — 締めくくる
1 回だけ、最後のバーの後に実行されます。最終的な出力の計算、チャートインジケーターの構築、サマリーの出力に使います。
def on_finish(bars, symbol=None, bars_by_symbol=None):
closes = [b["close"] for b in bars]
bt.chart_indicators.add(...)パラメータ:
bars— クロックシンボルの OHLC 辞書の時系列リスト(古い順)。実行されたウィンドウであり、Test Results のチャートが描画するのと同じ行です。各エントリーにはt、open、high、low、close、volumeと、任意でfundamentalsが含まれます。これはon_finishにおける標準的な全履歴の系列です。symbol— クロックシンボルの大文字のティッカー。- Screener の実行にはクロックシンボルがなく、
barsは[]、symbolは""なので、bars_by_symbolを使ってください。 bars_by_symbol— 複数シンボルの実行では、各ティッカーをその時系列のバーのリスト(実行クロックに整列済み)に対応付けた dict。単一シンボルの実行では無視できます。
一般的な使い方:
- インジケーター系列を
chart_indicators.add(...)に渡して、結果チャートに表示する。実行がバーを進める間にシンボルごとに蓄積した値を優先し、バーごとに追跡しなかったインジケーターに限りbarsから再計算してください — インジケーター系列を参照。 - 最終的なサマリーを出力パネルに表示またはログ出力する。
- 結果ペイロード用にポートフォリオ指標を更新する。
**
on_finish内のすべてのreturnは無言です。**実行は成功し、Test Results も描画されます。早期の return は、系列や指標が添付されなかったことを意味するだけで、どこにもエラーは出ません。デバッグ中は各ガードでsys.stderrに出力してください。メッセージは出力パネルに表示されます。
複数シンボルの基本
QuantCraft のストラテジーは、1 つのシンボルでも複数を同時にでも実行できます。ライフサイクルは同じで、コールバックのシグネチャとそれが受け取るデータだけがわずかに変わります。
複数シンボルの設定
Run backtest の Symbol & data タブで、大文字のティッカーを 1 つ以上選択します。リストされたすべてのシンボルが一緒に読み込まれ、シミュレートされます。
クロックシンボル
複数のシンボルを実行する場合、そのうち 1 つがクロックを駆動します。そのバーのタイムスタンプが、コールバックがいつ実行されるかを決めます。Run backtest ダイアログには、プライマリシンボルの設定項目はありません。解決の順序:
- ストラテジーファイルのトップレベルにある
SYMBOL = "AAPL"(そのティッカーが読み込まれたリストにある場合)。 - そうでなければ、最初に読み込まれたシンボル。
Screener のシンボルソースでは、クロックシンボルはまったくありません。実行中にシンボルの集合が変化するためです。rt.get_bar は常にクロックシンボルのバーを読みます(Screener の実行では None を返します)。別のティッカーの履歴には、そのティッカー自身の bar.close[i] オフセットを使ってください。
クロックシンボルは、on_finish(bars, symbol=...) が bars と symbol 引数に使うシンボルでもあります。
シンボルごとのコールバック
実行バーごとに、エンジンはリストの順にシンボルを進めます。
on_barは、このバーの時刻に読み込まれたティッカーごとに 1 回実行されます(そのバーのティックより前)。- バックテストのみ:
tick_in_bar(0= 始値、1= 高値、2= 安値、3= 終値)ごとに、on_tickが合成 OHLC に対してティッカーごとに 1 回実行されます(実際のティックデータなし)。 - バックテストのみ: 4 回のティックラウンドの後、最終的なアカウントの
tick(close_map)が終値で資産を評価します — 5 回目のon_tickではありません。 - フォワード実行: 接続済みブローカーから気配が届くたびに、シンボルごとにブローカーの価格更新ごとに
on_tickが実行されます(1 バーあたり 4 回に限られません)。日中の境界ではしばしばon_tickの後にon_barが発火します — バックテストの順序を前提にしないでください。 on_timer(timerIntervalMsが設定されたフォワード実行のみ — IDE のバックテストではなし)は、ティックの合間に同じティッカーごとのパターンに従います。
つまり、3 つのシンボルを読み込んでいる場合、複数シンボルの on_bar は、たとえば 1 バーあたり 3 回呼び出されます。symbol パラメータを使って、その呼び出しがどのティッカー向けかを判別します。
def on_bar(bar_index, bar, fundamentals=None, calendar=None, symbol=None):
if symbol == "AAPL":
...
elif symbol == "MSFT":
...bar ビューは、常にコールバックが呼び出されたシンボルのものです。on_tick の price も同様です。
As-of 整列。実行バーの時刻ごとに、各ティッカーはその時刻以前の最新のバーを受け取ります。その正確な時刻にバーのないティッカー(取引停止、分の欠落、取引カレンダーの違い)は、前のバーをもう一度受け取ります。ティッカーごとにバーやシグナルを数える場合は、そのシンボルで最後に見た bar["t"] を覚えておくことで、重複をスキップします。
_last_t = {}
def on_bar(bar_index, bar, fundamentals=None, calendar=None, symbol=None):
if _last_t.get(symbol) == bar["t"]:
return # same bar as last step for this ticker
_last_t[symbol] = bar["t"]
...複数シンボルの on_finish
on_finish は最後に 1 回だけ実行されます。すべてのシンボルの履歴を扱うには bars_by_symbol を使います。
def on_finish(bars, symbol=None, bars_by_symbol=None):
if bars_by_symbol:
for ticker, ticker_bars in bars_by_symbol.items():
...まとめ
典型的な単一シンボルのストラテジー:
on_initで入力パラメータやその他の状態を設定する。- 取引判断のために、
on_barでbar.close[0]、bar.close[1]、… を通じて価格履歴(OHLCV)を読み、account.open_trade/account.close_tradeを呼び出す。 - 必要に応じて
on_tickを使う — バックテストでは合成のバー内約定(例:終値ティックならtick_in_bar == 3)、フォワード実行ではバーの終値の合間のライブブローカー価格への反応。 - 必要に応じて、
timerIntervalMsが設定されたフォワード実行でon_timerを使う(IDE の Run backtest では利用不可)。 on_finishで、barsを使ってチャートインジケーターを構築し、結果を仕上げる。
複数シンボルのストラテジーも同じコールバックを使い、その内部で symbol 引数に基づいて分岐し、受け取った bar を通じてティッカーごとのデータを読みます。on_finish では bars_by_symbol を使います。
さらに詳しくは、アカウントモデル、ファンダメンタルデータ、Fama-French ファクター、OHLCV とバーデータ、インジケーター系列、または右サイドバーのアプリ内 Documentation タブ(ワークスペースのレイアウト)を参照してください。

