SeitenKontomodell · 9 / 29
Kontomodell
Jede QuantCraft-Strategie läuft gegen ein eingebautes Papierhandelskonto. Das Konto verfolgt Ihr Bargeld, hält Ihre offenen Positionen, bucht realisierten Gewinn und Verlust, wenn Sie Trades schließen, und berechnet am Ende eines Backtests die Performance-Kennzahlen.
Im Normalfall bauen Sie es nicht selbst: Die Backtest-Engine erzeugt es aus dem Tab Simulation des Dialogs Run backtest (Startguthaben, Provision / Slippage) und weist es quantcraft.process.runtime.account zu, bevor on_init() läuft. Optionale Standard-SL/TP und risikobasierte Positionsgrößen können über Konfiguration / .qcs-Schlüssel eingebunden werden, sofern vorhanden; der Dialog bietet derzeit keine SL/TP-/Risiko-Eingaben. Callbacks erhalten Marktdaten wie in Strategie-Lebenszyklus und OHLCV- und Balkendaten beschrieben; Kennzahlen erscheinen unter Testergebnisse.
Schnellstart
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})Das ist das vollständige Muster: einen Trade eröffnen, die Positions-ID speichern, später schließen und (optional) am Ende die Kennzahlen aktualisieren.
Was Sie von der Instanz lesen: account.cash, account.balance, account.realized_pnl, account.closed_trades und account.open_positions_payload(). Es gibt kein Attribut account.open_positions — offene Legs kommen aus dem Snapshot (snap["open_positions"]) oder aus open_positions_payload().
Forward-Läufe platzieren echte Orders. Bei einem Chart-Forward-Test oder Bulk-Lauf ist
runtime.accountein echtes Broker-Konto — einePaperAccount-Unterklasse mit derselben API, die echte Orders an Ihr verbundenes Broker-Konto (Paper oder Live) sendet, einschließlich Bracket-SL/TP-Orders für Aktien. Behandeln Sieopen_trade/close_tradedort als echte Trades. Handel birgt das Risiko von Verlusten.
Was PaperAccount tut
Das Konto simuliert ein einfaches Kassa-Handelskonto:
- Verfolgt Startguthaben, Bargeld, Saldo und Eigenkapital (Equity).
- Hält Long- und Short-Positionen im selben Konto.
- Bucht realisierten G/V, wenn Sie einen Trade schließen, und verfolgt den unrealisierten G/V offener Positionen.
- Führt eine Eigenkapital-Historie, die bei jeder Kursaktualisierung fortgeschrieben wird.
- Berechnet optional einen vollständigen Satz von Performance-Kennzahlen (Sharpe, Sortino, Drawdown, CAGR usw.).
Was es nicht modelliert: Margin, Leihkosten oder automatische SL/TP-Ausführung. Gespeicherte SL/TP-Werte sind nur Zahlen — Ihr Strategiecode ist dafür verantwortlich, Kurse zu prüfen und close_trade aufzurufen, wenn ein Stop oder Ziel erreicht ist. Provision und Slippage gelten, wenn im Tab Simulation Modelle konfiguriert sind — siehe unten.
Dividenden und Splits werden modelliert, wenn Kalenderdaten für den Lauf aktiviert sind. Am Wirksamkeitsdatum des Splits wird die Aktienanzahl mit dem Verhältnis multipliziert und die Kostenbasis dadurch geteilt (die Eigenkapitalkurve bleibt stetig). Am Ex-Tag wird qty × amount bei Long-Positionen dem Bargeld gutgeschrieben und bei Short-Positionen belastet. Die Dividende fließt in balance und Eigenkapital, aber nicht in realized_pnl — lesen Sie total_dividend_income und total_splits_applied aus account.refresh_metrics(). Siehe Kalenderdaten.
Provision und Slippage werden im Tab Simulation des Dialogs Run backtest konfiguriert. Sind sie aktiviert, werden Ausführungen automatisch angepasst; Sie setzen sie nicht in Python. Der Dialog Run verwendet standardmäßig Provision pro Aktie mit 0,001 $ pro Aktie bei 0 $ Minimum sowie Volumenanteil-Slippage. (Ein selbst konstruiertes PaperAccount hat standardmäßig keine Provision und keinen Slippage.)
Das Konto konfigurieren
Sie konfigurieren das Konto im Tab Simulation von Run backtest:
| Einstellung | Was sie tut |
|---|---|
starting_balance | Bargeld, mit dem das Konto startet. |
| Provisions-/Slippage-Modelle | Wie Ausführungen angepasst werden — siehe unten. |
Optionale Standard-SL/TP und Risiko-Sizing (sl_pct / sl_dollars, tp_pct / tp_dollars, risk_pct_per_trade / risk_fixed_per_trade) können weiterhin über .qcs-/Konfigurations-Hilfsfunktionen angewandt werden, sofern vorhanden, aber der Dialog Run backtest bietet diese Eingaben derzeit nicht. Wenn sie gesetzt sind:
- Prozentfelder akzeptieren entweder ganze Prozentwerte wie
2oder Bruchwerte wie0.02— beides bedeutet dasselbe. - Bei Long-Positionen liegt der Stop unter dem Einstieg und das Ziel darüber. Bei Short ist es umgekehrt.
- Standardwerte gelten für jeden neuen Trade, sofern Sie sie nicht pro Aufruf überschreiben.
Nur für Notebooks oder Tests (außerhalb des IDE-Backtest-Ablaufs) können Sie ein Konto manuell konstruieren:
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
)Provision und Slippage (Tab Simulation)
Der Tab Simulation im Dialog Run backtest konfiguriert, wie Ausführungen um Handelskosten angepasst werden. Die Einstellungen gelten automatisch für runtime.account — Sie konfigurieren sie nicht in Python. Siehe Backtests und Ausführungsvorlagen (.qcs), um diese Optionen zu speichern.
Provisionsmodelle
| Modell | Parameter | Verhalten |
|---|---|---|
| Pro Aktie (Standard) | Kosten pro Aktie ($), Minimum pro Trade ($) | max(qty × cost, min), wenn Minimum > 0 |
| Pro Trade | Pauschale pro Order ($) | Gleiche Gebühr unabhängig von der Menge |
| Null | — | Keine Provision — nützlich, um reines Alpha zu testen |
Slippage-Modelle
| Modell | Parameter | Verhalten |
|---|---|---|
| Volumenanteil (Standard) | Volumenlimit (Standard 0,025), Preiseinfluss (Standard 0,1) | Der Einfluss skaliert mit (qty / bar_volume)²; Käufe werden höher, Verkäufe niedriger ausgeführt. Nur bei Eröffnungen verkleinert volume_limit die Menge außerdem stillschweigend auf volume_limit × bar_volume — prüfen Sie den zurückgegebenen Snapshot auf die tatsächlich erhaltene Größe. (Bleibt nichts übrig, löst open_trade qty after volume_limit cap is not positive aus.) Schließungen werden nie begrenzt. Balkenvolumen null / fehlend → kein Einfluss. |
| Fester Spread | Gesamt-Spread pro Aktie ($) | Halber Spread wird auf jedes Ausführungs-Leg angewandt |
| Null | — | Ausführung zum exakt angeforderten Kurs |
Datensätze geschlossener Trades enthalten commission, slippage_cost, raw_entry_price und raw_exit_price. Die gespeicherten entry_price / exit_price sind Ausführungskurse nach Slippage. account.refresh_metrics() liefert total_commission und total_slippage_cost.
Positionen
Eine Position stellt einen offenen Trade in einem einzelnen Symbol dar. Sie konstruieren Positionen nicht direkt — sie werden von open_trade(...) erzeugt und von close_trade(...) entfernt, das auch einen Teil einer Position schließen und den Rest bestehen lassen kann.
Einen Trade eröffnen
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,
)Der Aufruf gibt einen Snapshot des Kontos zurück (siehe unten). Die neu eröffnete Position erscheint in snap["open_positions"] — greifen Sie ihre id ab, wenn Sie sie später per ID schließen möchten. Der gespeicherte entry_price ist die Ausführung nach Slippage; der angeforderte Kurs bleibt als raw_entry_price erhalten.
Eine Long-Eröffnung wird mit insufficient cash for long abgelehnt, wenn qty × fill_price + commission (Ausführungskurs nach Slippage) das Bargeld übersteigt. Der zurückgegebene Snapshot bewertet dieses Symbol zum Ausführungskurs.
Positionsgröße nach Risiko
Wenn das Konto den Trade anhand Ihres Risikobudgets (entweder risk_pct_per_trade oder risk_fixed_per_trade) und eines Stop-Abstands dimensionieren soll, nutzen Sie:
qty = account.position_size_for_risk(entry_price, stop_price)Dafür muss genau ein Risikomodus konfiguriert sein, und der Stop-Abstand darf nicht null sein. Standardmäßig verwendet risk_pct_per_trade einen Prozentsatz des Bargelds; übergeben Sie equity_for_risk=..., um stattdessen am Eigenkapital zu dimensionieren.
Einen Trade schließen
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 restDie vollständige Signatur lautet close_trade(position_id, exit_price, *, qty=None) — qty ist nur per Schlüsselwort übergebbar.
open_trade / close_trade nehmen einen angeforderten Kurs entgegen und wenden dann die Slippage- und Provisionsmodelle des Laufs an. Der gespeicherte exit_price ist die Ausführung; der angeforderte Kurs bleibt als raw_exit_price erhalten. Ein gängiges Muster der Backtest-Simulation: den synthetischen Schluss-Tick des Balkens aus on_tick anfordern:
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)Bei Forward-Läufen nutzen Sie in on_tick den Live-price jeder Broker-Notierung, statt tick_in_bar zu prüfen.
Teilschließungen (qty=)
Wird qty übergeben, schließt das nur diese Stückzahl und lässt den Rest an Ort und Stelle offen — gleiche Positions-ID, gleicher entry_price, gleiche entry_time — sodass die verbleibende Position Kostenbasis und Haltedauer behält:
pid = account.open_positions_payload()[0]["id"]
account.close_trade(pid, px, qty=4) # scale out 4 units, keep the restDer Datensatz einer Teilschließung in closed_trades trägt partial: True und remaining_qty.
- Kosten werden auf die Einheiten berechnet, die tatsächlich gehandelt wurden, und die Eröffnungsprovision des Lots wird anteilig aufgeteilt, sodass eine Folge von Teilschließungen genau das berechnet, was eine einzelne vollständige Schließung berechnen würde.
- Lassen Sie
qtyweg — oder übergeben Sie einen Wert in Höhe der Positionsgröße oder darüber — für eine vollständige Schließung. - Der Rest bleibt eine normale offene Position: Sie wird weiter in jedem Schritt bewertet und erscheint unter derselben ID in
snap["open_positions"], sodass Sie später erneut reduzieren können.
Sie können auch gesammelt pro Symbol schließen. symbol= ist bei Multi-Symbol-Läufen Pflicht, damit Sie nicht versehentlich Positionen in jedem Ticker gleichzeitig schließen:
account.close_all_longs(exit_price, symbol="AAPL")
account.close_all_shorts(exit_price, symbol="AAPL")Weitere Hilfsfunktionen
| Methode | Zweck |
|---|---|
open_positions_payload(prices=None) | Offene Legs als Dicts, jeweils mit last_price und unrealized_pnl bei prices (Standard: letzter tick). |
open_position_symbols() | Menge der Symbole mit mindestens einem offenen Leg. |
position_unrealized_pnl(pos, prices) | Offener G/V einer OpenPosition. |
apply_split(symbol, ratio, *, when=None) / apply_dividend(symbol, amount_per_share, *, estimated=False, when=None) | Kapitalmaßnahmen — die Engine ruft diese auf, wenn Kalenderdaten aktiv sind; siehe Kalenderdaten. |
corporate_actions | Aufzeichnung der bisher angewandten Splits / Dividenden. |
set_bar_volume(symbol, volume) | Balkenvolumen für die Volumenanteil-Slippage; die Engine setzt es in jedem Balken. |
Ausstiegskurs vs. Indikatoren: Übergeben Sie den Kurs, den Sie anpeilen (z. B.
float(bar["close"])auson_bar); die Engine wendet dennoch Slippage/Provision an. Sie schreibt Ihre Anforderung nicht auf einen SMA oder einen anderen Indikator um. Im Backtest isttick_in_bar == 3inon_tickein synthetischer Schluss-Tick (OHLC-Simulation des Balkens, keine echten Tickdaten). Bei Forward-Läufen schließen Sie mit dem Live-priceauson_tickbei jedem Broker-Update.
Bargeld, Saldo, Eigenkapital und PnL
Dies sind die zentralen Geldfelder, die auf dem Konto und in jedem Snapshot verfügbar sind:
| Feld | Bedeutung |
|---|---|
starting_balance | Das Bargeld, mit dem das Konto startete. |
cash / balance | Abgerechnetes Bargeld nach Eröffnungen/Schließungen. Long-Eröffnungen belasten qty × fill_price + commission; Shorts schreiben gut qty × fill_price − commission. Die gespeicherten entry_price / exit_price sind Ausführungskurse nach Slippage. |
equity | Mark-to-Market-Summe: cash + Σ(long qty × mark) − Σ(short qty × mark). |
unrealized_pnl | Summe des G/V der offenen Positionen, bewertet zu den aktuellen Marks. |
realized_pnl | Kumulierter Gewinn/Verlust aus geschlossenen Trades. |
Wie Marks aktualisiert werden:
- Die Engine ruft bei jedem simulierten Kursschritt
tick(prices, time=...)auf dem Konto auf, sodassequity,unrealized_pnlund die Eigenkapital-Historie automatisch aktuell bleiben. - Fehlt der Mark-Kurs eines Symbols in der
prices-Zuordnung, wird diese Position zu ihrem Einstiegskurs bewertet — der unrealisierte G/V dieses Legs ist 0, bis Sie eine echte Notierung übergeben. close_tradeführt die vorherigen Marks fort und setzt das geschlossene Symbol auf seine Ausstiegsausführung, sodass offen gelassene Positionen — einschließlich des Rests einer Teilschließung — bewertet bleiben.refresh_metrics()ohne Argument verwendet die Marks aus dem jüngsten Snapshot (open_trade/close_trade/tick) und fällt nur dann auf das letzte Eigenkapital / Bargeld zurück, wenn diese Zuordnung leer ist.
In
on_barundon_tickmüssen Sieaccount.tick(...)nicht selbst aufrufen — die Engine übernimmt das. Sie tickt vor jedemon_tick, aberon_barläuft vor den Ticks dieses Schritts, sodass inon_bardie neuesten Marks noch der Schlusskurs des vorherigen Balkens sind. Ein überflüssigertickhängt einen zusätzlichen Punkt anequity_historyan. Übergeben Sie inon_finishbevorzugt explizitelast_pricesanrefresh_metrics({symbol: last_px}), wenn noch etwas offen sein könnte.
Short-Trades und Bargeld
Long- und Short-Positionen werden im selben Konto unterstützt:
- Short-Eröffnung — der Erlös erhöht das Bargeld (abzüglich Provision, wenn konfiguriert).
- Short-Schließung (Kauf zur Eindeckung) — verringert das Bargeld.
Es gibt kein Margin-Modell und keine Leihkosten, daher sind Shorts eine reine Bargeld-und-Mark-Simulation (plus Provision/Slippage, wenn Modelle gesetzt sind).
Snapshots
Jeder Aufruf von open_trade, close_trade und tick gibt dasselbe Snapshot-Dictionary zurück, das den Zustand des Kontos unmittelbar nach dem Aufruf beschreibt. Nützliche Schlüssel:
| Schlüssel | Inhalt |
|---|---|
starting_balance | Anfängliches Bargeld. |
cash / balance | Aktuelles Bargeld. |
equity | Aktuelles Mark-to-Market-Eigenkapital. |
unrealized_pnl | G/V der offenen Positionen zu den aktuellen Marks. |
realized_pnl | Kumulierter realisierter G/V. |
open_positions | Liste von Dicts offener Positionen: id, symbol, side, qty, entry_price, sl_price, tp_price, raw_entry_price, commission_at_open, entry_time (kann null sein). In den Zeilen offener Positionen gibt es kein G/V-Feld pro Leg — lesen Sie unrealized_pnl aus dem Snapshot für den aggregierten schwebenden G/V. |
closed_trades | Liste geschlossener Trades: position_id, symbol, side, qty, Ein-/Ausstiegskurse, realisierter G/V, commission (enthält den Anteil dieser Schließung an der Eröffnungsprovision), slippage_cost (Slippage bei Einstieg + Ausstieg), raw_exit_price, optional raw_entry_price sowie entry_time / exit_time; Teilschließungen ergänzen partial und remaining_qty. Auch als account.closed_trades auf der Instanz verfügbar. |
tick_prices | Kopie der für diesen Snapshot verwendeten Kurszuordnung. close_trade führt die vorherigen Marks fort und setzt das geschlossene Symbol auf seine Ausstiegsausführung, sodass offene Legs über eine Schließung hinweg bewertet bleiben. |
Eine API account.get_open_positions(symbol) gibt es nicht. Filtern Sie snap["open_positions"] (oder account.open_positions_payload()) nach dem großgeschriebenen symbol, oder lesen Sie account.closed_trades für die geschlossene Historie.
So lässt sich das Konto in Ihrer Strategie leicht inspizieren:
snap = account.open_trade("AAPL", "long", 10, px)
print("equity now:", snap["equity"])
print("open positions:", len(snap["open_positions"]))Performance-Kennzahlen
Wenn der Lauf endet, können Sie das Konto einen vollständigen Satz Kennzahlen berechnen lassen:
def on_finish(bars, symbol=None, bars_by_symbol=None):
last_px = float(bars[-1]["close"])
metrics = account.refresh_metrics({"AAPL": last_px})Das Argument ist eine Zuordnung {symbol: last_price}, die als finaler Mark dient, damit offene Positionen korrekt bewertet werden. Verwenden Sie die an on_finish übergebene Liste bars (älteste zuerst), um den letzten Schlusskurs zu erhalten — das ist die kanonische Serie über die gesamte Historie.
Sie können außerdem zwei optionale Argumente übergeben:
risk_free_rate=0— wird von Sharpe-/Sortino-artigen Kennzahlen genutzt.calmar_annualized_return=None— überschreibt die annualisierte Rendite, die Calmar verwendet.
Verfügbare Kennzahlen
refresh_metrics(...) gibt ein Dictionary zurück, das Folgendes enthält:
| Gruppe | Felder |
|---|---|
| Renditen | total_pnl, total_return, cagr |
| Risiko | max_drawdown, volatility, risk_adjusted_return |
| Kennzahlen (Ratios) | sharpe_ratio, sortino_ratio, calmar_ratio, profit_factor |
| Trade-Statistik | average_trade_expectancy, average_win, average_loss, total_commission, total_slippage_cost |
| Exposure | current_exposure, average_exposure, max_exposure |
| Kapitalmaßnahmen | total_dividend_income, total_splits_applied |
Hinweise:
- Trade-basierte Ratios verwenden die Rendite pro Trade, definiert als
realized_pnl / starting_balance. total_returnundcagrverwenden das aktuelle Eigenkapital im Vergleich zum Startguthaben.- Für aussagekräftige CAGR / Calmar benötigt die Engine Kalenderinformationen — sie übergibt beim Ticken des Kontos bereits Zeitstempel, sodass Sie in der Regel nichts Besonderes tun müssen.
- Einige Ratios können
infliefern, wenn der Nenner null ist und die Performance günstig ist (z. B. keine Verlust-Trades).
Die Kennzahlen werden auch in der Ansicht Test Results des Editors und in der strukturierten Ergebnis-Nutzlast des Laufs angezeigt.
Alles zusammen
Typische Nutzung innerhalb einer Strategie:
- Konfigurieren Sie Startguthaben und Provision / Slippage im Tab Simulation (optionale SL/TP / Risiko über
.qcs/ Code, sofern unterstützt). - Eröffnen Sie Trades aus
on_bar(oder auson_tickfür Intrabar-Ausführungen) mitaccount.open_trade(...)— speichern Sie die zurückgegebeneid, falls Sie sie brauchen. - Schließen Sie Trades mit
account.close_trade(position_id, exit_price)— oder mitqty=, um zu reduzieren und den Rest des Lots offen zu lassen. - Lesen Sie
snap["equity"],snap["cash"],snap["unrealized_pnl"]usw., wann immer Sie das Konto inspizieren möchten. - Rufen Sie in
on_finishaccount.refresh_metrics({symbol: last_price})auf, um die Performance-Kennzahlen für das Ergebnis zu füllen.
Das ist das gesamte Kontomodell — eine kleine Oberfläche, aber ausreichend, um Longs, Shorts, Cashflows, G/V und einen vollständigen Kennzahlensatz für jede Strategie zu simulieren, die sich mit den Lebenszyklus-Callbacks ausdrücken lässt.

