ページアカウントモデル · 9 / 29
アカウントモデル
すべての QuantCraft ストラテジーは、組み込みのペーパートレーディングアカウントに対して実行されます。このアカウントは、現金を追跡し、建玉を保持し、取引を決済したときに実現損益を計上し、バックテストの終了時にパフォーマンス指標を計算します。
通常の利用では自分で構築する必要はありません。バックテストエンジンが Run backtest ダイアログの Simulation タブ(初期残高、手数料/スリッページ)から作成し、on_init() が実行される前に quantcraft.process.runtime.account に割り当てます。任意のデフォルト SL/TP とリスクベースのサイジングは、存在する場合は設定/.qcs キーで接続できます。ダイアログは現在 SL/TP/リスクの入力を公開していません。コールバックは、ストラテジーのライフサイクルと OHLCV とバーデータで説明されているとおりに市場データを受け取り、指標はテスト結果に表示されます。
クイックスタート
import quantcraft.process.runtime as rt
def on_init():
pass
def on_bar(bar_index, bar, fundamentals=None, symbol=None):
account = rt.account
px = float(bar["close"])
snap = account.open_trade("AAPL", "long", 10, px)
if snap["open_positions"]:
pid = snap["open_positions"][0]["id"]
account.close_trade(pid, px + 1.0)
def on_finish(bars, symbol=None, bars_by_symbol=None):
last_px = float(bars[-1]["close"])
metrics = rt.account.refresh_metrics({"AAPL": last_px})これが基本パターンのすべてです。取引を開き、ポジション ID を保存し、後で決済し、(任意で)最後に指標を更新します。
インスタンスから読み取れるもの: account.cash、account.balance、account.realized_pnl、account.closed_trades、account.open_positions_payload()。account.open_positions という属性はありません。建玉のレッグはスナップショット(snap["open_positions"])または open_positions_payload() から取得します。
フォワード実行では実際の注文が出されます。 チャートフォワードテストやバルク実行では、
runtime.accountは実際のブローカーアカウントです。同じ API を持つPaperAccountのサブクラスで、接続済みのブローカーアカウント(ペーパーまたはライブ)に実際の注文を送信します。株式向けのブラケット SL/TP 注文も含まれます。そこでのopen_trade/close_tradeは実際の取引として扱ってください。取引には損失のリスクが伴います。
PaperAccount の役割
このアカウントは、シンプルな現物取引口座をシミュレートします。
- 初期残高、現金、残高、資産(equity) を追跡します。
- 同じアカウントで買い・売り両方のポジションを保持します。
- 取引を決済したときに実現損益を計上し、建玉の含み損益を追跡します。
- 価格が更新されるたびに追記される資産履歴を保持します。
- 任意で、パフォーマンス指標一式(シャープ、ソルティノ、ドローダウン、CAGR など)を計算します。
モデル化しないもの: 証拠金、借入コスト、SL/TP の自動執行。保存された SL/TP の値は単なる数値です。価格を確認し、ストップや目標に達したら close_trade を呼ぶのはストラテジーコードの責任です。手数料とスリッページは、Simulation タブでモデルを設定した場合に適用されます(下記参照)。
実行でカレンダーデータが有効な場合、配当と株式分割がモデル化されます。 分割の効力発生日に、株数に比率が掛けられ、取得原価はその比率で割られます(資産曲線は連続のままです)。権利落ち日には、買いポジションでは qty × amount が現金に加算され、売りポジションでは差し引かれます。配当は balance と資産に反映されますが、realized_pnl には反映されません。account.refresh_metrics() の total_dividend_income と total_splits_applied を読み取ってください。カレンダーデータを参照してください。
手数料とスリッページは、Run backtest ダイアログの Simulation タブで設定します。有効にすると、約定は自動的に調整されます。Python で設定する必要はありません。Run ダイアログのデフォルトは、1 株あたり $0.001 の1 株あたり手数料(最低額 $0)と、出来高参加率スリッページです。(自分で構築した PaperAccount は、デフォルトでは手数料もなし、スリッページもなしです。)
アカウントの設定
アカウントは Run backtest の Simulation タブで設定します。
| 設定 | 動作 |
|---|---|
starting_balance | アカウントの開始時の現金。 |
| 手数料/スリッページモデル | 約定がどう調整されるか — 下記参照。 |
任意のデフォルト SL/TP とリスクサイジング(sl_pct / sl_dollars、tp_pct / tp_dollars、risk_pct_per_trade / risk_fixed_per_trade)は、存在する場合は .qcs/設定ヘルパーから適用できますが、Run backtest ダイアログは現在これらの入力を公開していません。設定した場合:
- パーセント欄は、
2のような整数のパーセント値、または0.02のような小数のどちらも受け付けます。意味は同じです。 - 買いポジションでは、ストップはエントリーの下、目標は上になります。売りポジションでは逆です。
- デフォルトは、呼び出しごとに上書きしない限り、すべての新規取引に適用されます。
ノートブックまたはテスト専用(IDE のバックテストフローの外)では、アカウントを手動で構築できます。
account = PaperAccount(
100_000,
sl_pct=2,
tp_pct=4,
risk_pct_per_trade=1,
slippage_model=None, # optional; defaults to no slippage (IDE engine wires from Simulation tab)
commission_model=None, # optional; defaults to zero commission (IDE engine wires from Simulation tab)
equity_history_cap=None, # optional; keep only the newest N equity_history points
)手数料とスリッページ(Simulation タブ)
Run backtest ダイアログの Simulation タブでは、約定を取引コストでどう調整するかを設定します。設定は runtime.account に自動的に適用されます。Python で設定する必要はありません。これらのオプションの保存については、バックテストと実行プリセット(.qcs)を参照してください。
手数料モデル
| モデル | パラメータ | 動作 |
|---|---|---|
| 1 株あたり(デフォルト) | 1 株あたりのコスト($)、1 取引あたりの最低額($) | 最低額 > 0 の場合 max(qty × cost, min) |
| 1 取引あたり | 1 注文あたりの固定コスト($) | 数量にかかわらず同じ手数料 |
| ゼロ | — | 手数料なし — 純粋なアルファのテストに便利 |
スリッページモデル
| モデル | パラメータ | 動作 |
|---|---|---|
| 出来高参加率(デフォルト) | 出来高上限(デフォルト 0.025)、価格インパクト(デフォルト 0.1) | インパクトは (qty / bar_volume)² に比例してスケールし、買いはより高く、売りはより低く約定します。新規建てのみ、volume_limit は数量も volume_limit × bar_volume まで黙って縮小します — 実際に得られた数量は、返されたスナップショットで確認してください。(何も残らない場合、open_trade は qty after volume_limit cap is not positive を送出します。)決済が制限されることはありません。バーの出来高がゼロ/欠損の場合、インパクトはありません。 |
| 固定スプレッド | 1 株あたりの合計スプレッド($) | 各約定レッグに半分のスプレッドを適用 |
| ゼロ | — | 要求した価格どおりに約定 |
決済済み取引のレコードには、commission、slippage_cost、raw_entry_price、raw_exit_price が含まれます。保存される entry_price / exit_price は、スリッページ後の約定価格です。account.refresh_metrics() は total_commission と total_slippage_cost を公開します。
ポジション
ポジションは、単一のシンボルにおける 1 件の建玉取引を表します。ポジションを直接構築することはありません。open_trade(...) で作成され、close_trade(...) で削除されます。close_trade(...) はポジションの一部を決済し、残りをそのまま残すこともできます。
取引を開く
snap = account.open_trade(
symbol, # "AAPL"
side, # "long" or "short"
qty, # integer or float quantity
entry_price, # float, the requested price (slippage/commission applied)
sl_price=None, # optional stop price (overrides default sl_pct/sl_dollars)
tp_price=None, # optional target price (overrides default tp_pct/tp_dollars)
position_id=None,
)この呼び出しはアカウントのスナップショット(下記参照)を返します。新しく開かれたポジションは snap["open_positions"] に表示されます。後で ID で決済したい場合は、その id を控えておきます。保存される entry_price はスリッページ後の約定価格で、要求した価格は raw_entry_price として保持されます。
qty × fill_price + commission(スリッページ後の約定価格)が現金を超える場合、買いの新規建ては insufficient cash for long で拒否されます。返されるスナップショットは、そのシンボルを約定価格で評価します。
リスクによるサイジング
リスク予算(risk_pct_per_trade または risk_fixed_per_trade のいずれか)とストップ距離に基づいてアカウントに取引サイズを決めさせたい場合は、次を使います。
qty = account.position_size_for_risk(entry_price, stop_price)これには、リスクモードがちょうど 1 つ設定されていて、ストップ距離がゼロでないことが必要です。デフォルトでは、risk_pct_per_trade は現金の割合を使います。資産に対してサイジングするには equity_for_risk=... を渡します。
取引を決済する
account.close_trade(position_id, exit_price) # close the whole position
account.close_trade(position_id, exit_price, qty=4) # scale out 4 units, keep the rest完全なシグネチャは close_trade(position_id, exit_price, *, qty=None) で、qty はキーワード専用です。
open_trade / close_trade は要求価格を受け取り、その実行のスリッページと手数料のモデルを適用します。保存される exit_price は約定価格で、要求した価格は raw_exit_price として保持されます。バックテストのシミュレーションでよくあるパターンは、on_tick からバーの終値の合成ティックを要求することです。
def on_tick(bar_index, tick_in_bar, price, bar, fundamentals=None, calendar=None, symbol=None):
if tick_in_bar == 3:
account.close_trade(pid, price)フォワード実行では、tick_in_bar を確認する代わりに、on_tick でブローカーの各気配からのライブの price を使います。
部分決済(qty=)
qty を渡すと、その数量だけが決済され、残りはそのまま建玉として残ります。ポジション ID、entry_price、entry_time は同じなので、残りのポジションは取得原価と保有期間を維持します。
pid = account.open_positions_payload()[0]["id"]
account.close_trade(pid, px, qty=4) # scale out 4 units, keep the rest部分決済の closed_trades 内のレコードには、partial: True と remaining_qty が付きます。
- コストは実際に取引された数量に対して課され、ロットの建て時手数料は比例配分されるため、部分決済を繰り返しても、1 回の全決済とまったく同じ額が課されます。
- 全決済にするには、
qtyを省略するか、ポジションサイズ以上の値を渡します。 - 残りは通常の建玉のままです。ステップごとに評価され続け、同じ ID で
snap["open_positions"]に表示されるため、後でもう一度縮小できます。
シンボル単位で一括決済することもできます。複数シンボルの実行では、すべてのティッカーのポジションを誤って一度に決済しないよう、symbol= が必須です。
account.close_all_longs(exit_price, symbol="AAPL")
account.close_all_shorts(exit_price, symbol="AAPL")その他のヘルパー
| メソッド | 目的 |
|---|---|
open_positions_payload(prices=None) | 建玉のレッグを dict として返します。各 dict に prices 時点の last_price と unrealized_pnl が含まれます(デフォルト:直近の tick)。 |
open_position_symbols() | 建玉のレッグが 1 つ以上あるシンボルの集合。 |
position_unrealized_pnl(pos, prices) | 1 つの OpenPosition の含み損益。 |
apply_split(symbol, ratio, *, when=None) / apply_dividend(symbol, amount_per_share, *, estimated=False, when=None) | コーポレートアクション — カレンダーデータがオンのとき、エンジンがこれらを呼び出します。カレンダーデータを参照。 |
corporate_actions | これまでに適用された分割/配当の記録。 |
set_bar_volume(symbol, volume) | 出来高参加率スリッページが使うバーの出来高。エンジンがバーごとに設定します。 |
出口価格とインジケーター: 狙う価格(たとえば
on_barのfloat(bar["close"]))を渡してください。エンジンは引き続きスリッページ/手数料を適用します。要求を SMA などのインジケーターに書き換えることはありません。バックテストでは、on_tickのtick_in_bar == 3は合成の終値ティックです(バーの OHLC シミュレーションであり、実際のティックデータではありません)。フォワード実行では、ブローカーの更新のたびにon_tickのライブのpriceを使って決済します。
現金、残高、資産、損益
これらは、アカウントおよびすべてのスナップショットで利用できる、金額追跡の中核フィールドです。
| フィールド | 意味 |
|---|---|
starting_balance | アカウントが開始したときの現金。 |
cash / balance | 新規建て/決済後の確定した現金。買いの新規建ては qty × fill_price + commission を引き落とし、売りは qty × fill_price − commission を入金します。保存される entry_price / exit_price は、スリッページ後の約定価格です。 |
equity | 時価評価の合計:cash + Σ(long qty × mark) − Σ(short qty × mark)。 |
unrealized_pnl | 建玉の損益を現在のマークで評価した合計。 |
realized_pnl | 決済済み取引の累計損益。 |
マークの更新方法:
- エンジンは、シミュレーション上の価格ステップごとにアカウントの
tick(prices, time=...)を呼び出すため、equity、unrealized_pnl、資産履歴は自動的に最新に保たれます。 - シンボルのマーク価格が
pricesマップにない場合、そのポジションはエントリー価格で評価されます。実際の気配を渡すまで、そのレッグの含み損益は 0 です。 close_tradeは以前のマークを引き継ぎ、決済したシンボルを出口の約定価格でマークするため、建玉のまま残るポジション(部分決済の残りを含む)もマークされた状態が保たれます。引数なしのrefresh_metrics()は、直近のスナップショット(open_trade/close_trade/tick)のマークを再利用し、そのマップが空のときだけ最終の資産/現金にフォールバックします。
on_barとon_tickの中では、account.tick(...)を自分で呼ぶ必要はありません。エンジンが代わりに実行します。エンジンは各on_tickの前に tick しますが、on_barはそのステップのティックより前に実行されるため、on_bar内の最新のマークは前のバーの終値のままです。不要なtickはequity_historyに余分な点を追加します。何かがまだ建玉のまま残っている可能性がある場合は、on_finishでrefresh_metrics({symbol: last_px})に明示的なlast_pricesを渡すことをおすすめします。
売り取引と現金
同じアカウントで買いと売りの両方がサポートされています。
- 売りの新規建て — 代金により現金が増えます(手数料が設定されている場合はその分を差し引き)。
- 売りの決済(買い戻し) — 現金が減ります。
証拠金モデルも借入コストもないため、売りは純粋な現金とマークのシミュレーションです(モデルが設定されている場合は手数料/スリッページが加わります)。
スナップショット
open_trade、close_trade、tick を呼び出すたびに、呼び出し直後のアカウントの状態を記述した同じスナップショットの辞書が返されます。便利なキー:
| キー | 内容 |
|---|---|
starting_balance | 初期の現金。 |
cash / balance | 現在の現金。 |
equity | 現在の時価評価の資産。 |
unrealized_pnl | 現在のマークでの建玉の損益。 |
realized_pnl | 累計の実現損益。 |
open_positions | 建玉の dict のリスト:id、symbol、side、qty、entry_price、sl_price、tp_price、raw_entry_price、commission_at_open、entry_time(null の場合あり)。建玉の行にレッグごとの損益フィールドはありません。集計した含み損益はスナップショットの unrealized_pnl を読んでください。 |
closed_trades | 決済済み取引のリスト:position_id、symbol、side、qty、エントリー/出口価格、実現損益、commission(この決済が負担する建て時手数料の分を含む)、slippage_cost(エントリー+出口のスリッページ)、raw_exit_price、任意の raw_entry_price、entry_time / exit_time。部分決済には partial と remaining_qty が加わります。インスタンスの account.closed_trades からも参照できます。 |
tick_prices | そのスナップショットで使われた価格マップのコピー。close_trade は以前のマークを引き継ぎ、決済したシンボルを出口の約定価格でマークするため、建玉のレッグは決済をまたいでもマークされたままです。 |
account.get_open_positions(symbol) という API はありません。snap["open_positions"](または account.open_positions_payload())を大文字の symbol で絞り込むか、決済済みの履歴は account.closed_trades を読んでください。
これにより、ストラテジー内でアカウントを簡単に確認できます。
snap = account.open_trade("AAPL", "long", 10, px)
print("equity now:", snap["equity"])
print("open positions:", len(snap["open_positions"]))パフォーマンス指標
実行の終了時に、アカウントへ指標一式の計算を依頼できます。
def on_finish(bars, symbol=None, bars_by_symbol=None):
last_px = float(bars[-1]["close"])
metrics = account.refresh_metrics({"AAPL": last_px})引数は、建玉を正しく評価するための最終マークとして使われる {symbol: last_price} のマップです。on_finish に渡される bars リスト(古い順)を使って最後の終値を取得してください。これが全履歴の標準的な系列です。
任意の引数を 2 つ渡すこともできます。
risk_free_rate=0— シャープ/ソルティノ系のレシオで使われます。calmar_annualized_return=None— カルマーが使う年率リターンを上書きします。
利用できる指標
refresh_metrics(...) は、次を含む辞書を返します。
| グループ | フィールド |
|---|---|
| リターン | total_pnl、total_return、cagr |
| リスク | max_drawdown、volatility、risk_adjusted_return |
| レシオ | sharpe_ratio、sortino_ratio、calmar_ratio、profit_factor |
| 取引統計 | average_trade_expectancy、average_win、average_loss、total_commission、total_slippage_cost |
| エクスポージャー | current_exposure、average_exposure、max_exposure |
| コーポレートアクション | total_dividend_income、total_splits_applied |
注記:
- 取引ベースのレシオは、
realized_pnl / starting_balanceと定義される取引ごとのリターンを使います。 total_returnとcagrは現在の資産と初期残高の比較を使います。- 意味のある CAGR / カルマーには、エンジンにカレンダー情報が必要です。エンジンはアカウントを tick するときにすでにタイムスタンプを渡しているため、通常は特別なことをする必要はありません。
- 分母がゼロでパフォーマンスが良好な場合(たとえば負け取引がない場合)、一部のレシオは
infを返すことがあります。
指標は、エディターの Test Results ビューと、実行の構造化された結果ペイロードにも表示されます。
まとめ
ストラテジー内での典型的な使い方:
- Simulation タブで初期残高と手数料/スリッページを設定します(任意の SL/TP/リスクは、対応している場合は
.qcs/コードで)。 on_bar(バー内の約定ならon_tick)からaccount.open_trade(...)で取引を開き、必要になる場合は返されたidを保存します。account.close_trade(position_id, exit_price)で取引を決済します。縮小してロットの残りを建玉のままにするにはqty=を使います。- アカウントを確認したいときはいつでも、
snap["equity"]、snap["cash"]、snap["unrealized_pnl"]などを読み取ります。 on_finishでaccount.refresh_metrics({symbol: last_price})を呼び出し、結果のパフォーマンス指標を埋めます。
これがアカウントモデルのすべてです。小さな API 面ですが、ライフサイクルのコールバックで表現できるあらゆるストラテジーについて、買い、売り、キャッシュフロー、損益、完全な指標セットをシミュレートするのに十分です。

