> ## Documentation Index
> Fetch the complete documentation index at: https://uncoded.ch/docs/llms.txt
> Use this file to discover all available pages before exploring further.

# Engine Specification

> Deterministic execution semantics of the uncoded.ch backtest engine — bar-loop, intra-bar fill rules, fees, position sizing, risk management, full configuration schema (§20).

<Note>
  This is the **single source of truth** for how the backtest engine behaves. Every backtest report on [uncoded.ch/backtesting](https://uncoded.ch/backtesting) pins which revision applied via the `engine_version` field in its frontmatter — the per-report MDX no longer inlines this spec to keep exports lean.
</Note>

<CardGroup cols={2}>
  <Card title="Engine version" icon="tag">
    `1.1` — see [§23 Changelog](#23-changelog) for history. Behaviour changes bump this number; older revisions are archived as `ENGINE-v1.x.md`.
  </Card>

  <Card title="Scope" icon="crosshairs">
    Spot-only, single-symbol-per-subprocess, USDT/FDUSD pairs, Binance-style fills. No futures, no margin, no indicators — pure price-distance triggers.
  </Card>

  <Card title="Order types" icon="list-check">
    Limit-Buy, Limit-TP-Sell, Market-Stop-Loss, Market-Trailing-SL. See [§3 Order Types](#3-order-typen).
  </Card>

  <Card title="Reproducibility" icon="check-double">
    Deterministic given identical (candles, config, engine\_version). See [§16 Determinism](#16-determinismus--reproduzierbarkeit).
  </Card>
</CardGroup>

## How to read this document

Sections are numbered. Per-backtest reports cite this spec by section (e.g. *"see ENGINE.md §4.5 for TSL-skips-TP"*) so any LLM consuming a report can resolve behaviour questions without leaving the documentation set.

The document is mirrored from [`ENGINE.md` in the source repo](https://github.com/aureumvictoria/uncoded); this Mintlify page is the **canonical published version**. If the two ever diverge, the in-repo file wins — open a PR to update this page.

***

> Statisches Verhalten der Backtest-Engine. Per `engine_version` in MDX-Reports referenziert. Verhaltensänderung → Version bumpen, alte Spec archivieren (`ENGINE-v1.x.md` etc.). Diese Spec beschreibt **was die Engine tut**, nicht **wie sie implementiert ist**.
>
> Quelle: `BacktestingTest-main/backtester_numba.py` (numba-jit'd Python, 1894 Zeilen), Orchestration via `backtest-api/app/services/backtest_runner.py`, Mode-Configs unter `trading-bot/modes/*.json`. Werte ohne Marker sind aus dem Code verifiziert. `?` markiert verbleibende Unklarheiten.

**engine\_version:** `1.1` **last\_updated:** `2026-05-04` **maintainer:** `aureumvictoria` **code\_revision:** `backtester_numba.py — ULTRA++ EDITION (array-of-lists orders)`

***

### 0. Geltungsbereich

Diese Spec gilt für: **Spot-Krypto-Backtests** (Binance-Style, USDT/FDUSD-Pairs), **Single-Symbol pro Subprocess** (ein Run = ein Symbol = ein eigener Cash-Pool).

**Unterstützte Order-Typen:** Limit-Buy, Limit-TP-Sell, Market-SL, Market-Trailing-SL.

**Strategie-Logik:** rein preisbasiert über prozentuale Distanz zu `lastBuyPrice` — **keine Indikatoren** (kein RSI, MACD, MA, etc.). Modes sind reine Parameter-Presets.

**Nicht abgedeckt:** Futures/Perpetuals, Options, Margin/Leverage, Funding, Borrowing, Stop-Limit, OCO, Iceberg, Multi-Symbol-Portfolios mit gemeinsamem Cash, indikator-basierte Strategien.

***

### 1. Zeit- und Datenmodell

#### 1.1 Candle-Definition

* **Bar-Stempel:** Open-Time (Bar-Beginn). Aus dem Input-DataFrame durchgereicht.
* **Timezone:** UTC, ISO-8601 mit Offset (`pd.to_datetime(..., utc=True)`).
* **Granularität:** vom Provider abhängig (1s nativ unterstützt — Header-Kommentar nennt "1s candles").
* **OHLCV-Reihenfolge innerhalb einer Bar:** wird über `intrabar_mode` modelliert (siehe §4.4) — Engine nimmt **keine** Annahme über tatsächlichen intra-bar Pfad, sondern segmentiert deterministisch in 3 monotone Abschnitte.

#### 1.2 Datenquellen

* **Preisquelle (API-Pfad):** Postgres-Tabelle `backtest_candles`, exportiert pro Run via `export_to_csv` in `tmp/candles.csv`.
* **Auflösungen:** `1s, 1m, 3m, 5m, 15m, 30m, 1h, 2h, 4h, 6h, 8h, 12h, 1d`. Default `1m` wenn nicht erkannt.
* **Resampling:** keines — die DB hält die Auflösung nativ pro Symbol.
* **DataFrame-Spalten:** `timestamp, open, high, low, close` (volume optional, nicht ausgewertet).

#### 1.3 Look-Ahead-Schutz

* **Garantie:** Signal aus Bar T fillt **innerhalb von Bar T**, aber **niemals zu einem Preis jenseits des aktuellen Segments**. Es gibt **keine** Verzögerung "Signal T → Fill T+1".
* **Mechanismus:** Jede Bar wird in 3 monotone Segmente zerlegt; Fills nur innerhalb des durchlaufenen Preisbereichs (`in_range(a, b, price)`).
* **Indikatoren:** Engine berechnet **keine** Indikatoren — Strategie ist rein preisbasiert (Trigger via prozentuale Distanz zu `lastBuyPrice`).

#### 1.4 State-Initialisierung

* **`lastBuyPrice`:** auf `df['open'].iloc[0]` — also den **Open der ersten Bar**. Nicht Close, nicht Mid.
* **`base_bal`:** `0.0`.
* **`quote_bal`:** `config.get('startBal', 10000.0)`.
* **`lastTriggerIndex`:** `-10**9` (sentinel "noch nie getriggert").

#### 1.5 Daten-Lücken / fehlende Bars

* **Missing Bar:** keine Sonderbehandlung. Bars werden as-is verarbeitet.
* **Zero-Volume-Bar:** wird normal prozessiert; falls `O=H=L=C`, fillt `in_range` nur, wenn Order exakt auf diesem Preis sitzt.
* **Stale-Data-Schwelle:** keine.
* **Empty DataFrame:** Engine wirft `ValueError("Backtester received empty DataFrame…")` und beendet.

#### 1.6 Tote / unbenutzte Felder

* **`order_latency_seconds`** (Default `0` engine, `2` runner): wird gelesen, aber **niemals ausgewertet**. Alle Orders sind sofort aktiv ab `placed_idx` (`bo_active_from = placed_idx`). Kommentar im Code: "limit orders on Binance. They fill immediately when price hits — no latency."
* **`start`** (Default `True`): wird gelesen (`safe_bool`), aber im Engine-Loop nicht weiter verwendet. Vermutlich Live-Bot-only.

***

### 2. Signal-Pipeline (Bar-Loop)

Pro Bar `i` in dieser Reihenfolge:

```text theme={null}
1. Cooldown-Check         (i - lastTriggerIndex < triggerCoolDown → canBuy=False für diese Bar)
2. SellActivation-Update  (auf Bar-Open — falls aktiviert)
3. SellTimeCurves-Update  (alle N Bars — Re-Pricing nach Zeit)
4. Segmentierung (3 Segmente nach intrabar_mode)
   Pro Segment:
     Phase 1: Catch-Up-Cascade (lastBuyPrice → segment-start)
     Phase 1.5: SellActivation-Update (auf Segment-Start-Preis)
     Phase 2: Fills auf [segment-start, segment-end]:
       a) TP-Sells (Limit, maker fee)
       b) Stop-Loss (Market, taker fee)
       c) Trailing-SL (Market, taker fee)
       d) Buy-Fills (Limit, maker fee)
     Phase 3: Movement-Cascade (lastBuyPrice → segment-end)
5. canBuy-Restore         (falls Cooldown aktiv war)
6. Equity-Snapshot        (zum Bar-Close, reason="tick")
```

#### 2.1 Segmentierung

**Default `intrabar_mode = "OLHC"`** (im API-Runner; Engine-internes Default ist `"OHLC"`):

1. Segment A: `O → L`
2. Segment B: `L → H`
3. Segment C: `H → C`

**Alternative `intrabar_mode = "OHLC"`:**

1. Segment A: `O → H`
2. Segment B: `H → L`
3. Segment C: `L → C`

#### 2.2 Symbol-Reihenfolge bei Multi-Asset

Nicht relevant — die Engine läuft pro Subprocess auf genau einem Symbol. Multi-Symbol = mehrere parallele Subprocesses.

***

### 3. Order-Typen

| Typ                | Unterstützt  | Trigger                                           | Fill-Preis (Effektiv)                                |
| ------------------ | ------------ | ------------------------------------------------- | ---------------------------------------------------- |
| Limit Buy          | ja           | `Low ≤ Limit` im Segment                          | `Limit × (1 + half_spread)`                          |
| Limit TP-Sell      | ja           | `High ≥ TP` im Segment                            | `TP × (1 − half_spread)`                             |
| Market Stop-Loss   | ja           | Preis durchläuft SL-Level im Segment              | `SL × (1 − 2·half_spread)` (Bid-Side, voller Spread) |
| Market Trailing-SL | ja           | Preis ≤ TSL-Level (nach Aktivierung)              | `TSL × (1 − 2·half_spread)`                          |
| Stop-Limit         | nein         | —                                                 | —                                                    |
| OCO                | nein         | —                                                 | —                                                    |
| Iceberg            | nein         | —                                                 | —                                                    |
| Time-in-Force      | implizit GTC | bleiben aktiv bis Fill, Cancel oder Backtest-Ende | —                                                    |

`half_spread = assumed_spread_bps / 10000 / 2` (siehe §6.3).

**Order-Aktivierung:** alle Orders sind sofort ab `placed_idx` aktiv (`active_from = placed_idx`). Es gibt keine "pending" oder "untriggered" Phase, abgesehen von `sellActivation` (siehe §12).

***

### 4. Candle-interne Ausführungslogik

#### 4.1 Auswertungsreihenfolge pro Segment

1. **Phase 1 — Catch-Up-Cascade:** Trigger-Generierung von `lastBuyPrice` zum Segment-Start-Preis (handhabt Gaps zwischen Bars).
2. **SellActivation-Refresh:** falls aktiviert, basierend auf Segment-Start.
3. **Phase 2a — TP-Limit-Sells** (`maker_fee` falls nicht `onlyMakerSell`-blockiert; TSL-Orders skippen TP-Fills komplett, siehe §4.5).
4. **Phase 2b — Stop-Loss** (`taker_fee`, falls `stopLoss=True`).
5. **Phase 2c — Trailing-SL** (`taker_fee`, sofern aktiviert; falls `onlyMakerSell` → `maker_rate` als Override).
6. **Phase 2d — Limit-Buys** (`maker_fee` falls nicht `onlyMakerBuy`-blockiert).
7. **Phase 3 — Movement-Cascade:** Trigger-Generierung von `lastBuyPrice` zum Segment-End-Preis.

#### 4.2 SL und TP in derselben Bar

* **Regel:** TP-Phase läuft vor SL-Phase im selben Segment (Phase 2a vor 2b).
* **Konsequenz mit OLHC (Default):** In Segment A (O→L) liegt typischerweise nur der SL. TP-Sieg-Regel greift hier nicht, weil TP gar nicht im Segment ist. In Segment B (L→H) wird TP getroffen, falls High es erreicht — hier gewinnt TP über SL.
* **Faustregel OLHC:** Wenn SL und TP beide in derselben Bar liegen, **verliert der Trade strukturell** — der SL feuert in Segment A, bevor das High kommt.
* **Mit `OHLC`:** umgekehrt — TP gewinnt strukturell, weil High vor Low erreicht wird.
* Nach Fill wird sowohl der TP-Eintrag als auch der zugehörige SL-Eintrag aus den Order-Indizes entfernt.

#### 4.3 Entry und Exit in derselben Bar

* **Same-Bar-Round-Trip ist erlaubt.** Buy in Segment A → Sell in Segment B oder C möglich.
* **Min-Hold-Time:** keine.
* **Within-Segment-Schutz:** ein Sell kann **nicht** im selben Segment fillen, in dem der Buy gefillt wurde (`if so_placed_idx == bar_index AND segment_index <= so_created_seg: continue`).

#### 4.4 Intra-Bar-Pfad-Annahme

* **Engine-Default:** `intrabar_mode = "OHLC"` (optimistisch).
* **API-Runner-Default:** `intrabar_mode = "OLHC"` (pessimistisch im ersten Segment).
* Innerhalb eines Segments wird `in_range(a, b, price)` benutzt — Order fillt sobald Segment den Preis berührt, *unabhängig* von der tatsächlichen Tick-Reihenfolge im Segment.

#### 4.5 TSL-Order skippt TP-Fills

**Wichtig:** Sobald ein Sell-Order eine TSL-Komponente hat (`tsl_drop_pct > 0`), **wird er von der TP-Fill-Logik komplett übersprungen** (`if tsl_enabled and so_tsl_drop_pct > 0: continue`). Der einzige Exit-Pfad für diesen Split ist:

1. TSL feuert (Preis ≤ TSL-Level nach Aktivierung)
2. SL feuert (falls `stopLoss=True`)

Konsequenz: Ein Split mit aktivem TSL verkauft **niemals zum nominalen TP**. Der TP-Preis dient nur als **Aktivierungs-Schwelle** für das TSL-System (siehe §8.3).

***

### 5. Gap-Behandlung

* **Limit-Order durch Gap:** fillt am **Limit-Level**, nicht am Open. **Kein Price-Improvement.**
* **Stop-Loss durch Gap:** fillt am **SL-Level** mit Bid-Side-Spread. **Kein Gap-Loss-Modell** — der reale Gap-Verlust wird nicht abgebildet.
* **TSL durch Gap:** fillt am **TSL-Level** mit Bid-Side-Spread. ATH wird auf `tslActivation` gesetzt (nicht auf `seg_high`), um zu vermeiden, dass ein Gap die TSL-Referenz künstlich nach oben treibt.
* **Catch-Up-Cascade bei großen Lücken:** Phase 1 jedes Segments triggert nachträglich alle ausgelassenen Buy-Trigger zwischen `lastBuyPrice` und Segment-Start (bis zum Cap von 10000, siehe §10.4).
* **Trading-Halt / Daten-Lücke:** nicht modelliert.

> ⚠ Realistisch betrachtet eine **optimistische Annahme** bei großen Gaps. Für Krypto-24/7-Pairs auf 1m–1h selten relevant.

***

### 6. Gebühren, Slippage, Funding

#### 6.1 Fees

* **Modell:** `maker_fee_bps` / `taker_fee_bps`.
* **Engine-Default:** `maker=1.0`, `taker=7.5` bps.
* **API-Runner-Default:** `maker=7.5`, `taker=7.5` bps (überschreibt Engine).
* **Klassifizierung:**
  * **Maker:** Limit-Buys, Limit-TP-Sells.
  * **Taker:** Market-Stop-Loss, Market-Trailing-SL.
  * **Override:** `onlyMakerBuy=True`/`onlyMakerSell=True` siehe §6.6.
* **Anwendung:** beidseitig (Entry und Exit) auf Notional.
* **Mindestgebühr:** keine.
* **Konvertierung:** `bps_to_frac(bps) = bps / 10000`.

#### 6.2 `fees_in_quote` — exakte Formeln

**`True` (Default):**

* **Buy:** `fee_quote = price × qty × maker_rate`; `cost = price × qty + fee_quote`; Cash-Abzug `quote_bal -= cost`; `base_received = qty`.
* **Sell (TP):** `gross_quote = price × qty`; `fee_quote = gross_quote × maker_rate`; `net_quote = gross_quote − fee_quote`; `quote_bal += net_quote`.
* **Sell (SL/TSL):** identisch zu TP, aber `taker_rate` und Bid-Cross.

**`False`:**

* **Buy:** `fee_base = qty × maker_rate`; `base_received_raw = qty − fee_base`; `base_received = floor_to_step(base_received_raw)`; `cost = price × qty` (Fee in Base); `quote_bal -= cost`.
* **Sell (TP/SL/TSL):** `fee_base = qty × fee_rate`; `qty_after_fee = max(qty − fee_base, 0)`; `quote_bal += price × qty_after_fee`.

#### 6.3 Slippage / Spread

* **`assumed_spread_bps` (Default `1.5`)** wirkt als halbierter Spread auf Fill-Preise.
* `half_spread_frac = (assumed_spread_bps / 10000) / 2`.
* **Effektiv-Preise:**
  * `_effective_buy_price(p) = p × (1 + half_spread_frac)`
  * `_effective_sell_price(p) = p × (1 − half_spread_frac)` ← Limit-Sell (Maker)
  * `_market_sell_price(p) = p × (1 − 2 × half_spread_frac)` ← Market-Sell (voller Bid-Ask-Cross)
* **Keine zusätzliche Slippage-Komponente** (kein Volumen-Impact, kein Order-Book).

#### 6.4 Funding — nicht modelliert (Spot-Engine).

#### 6.5 Borrowing / Margin — nicht modelliert. `1×` Spot only.

#### 6.6 `onlyMakerBuy` / `onlyMakerSell`

* **`onlyMakerBuy=True`:** Wenn `price_rounded ≥ current_price_at_trigger`, wird der Buy **stillschweigend übersprungen** (würde Liquidität nehmen).
* **`onlyMakerSell=True`:** Bei Market-Sells (SL/TSL) wird `maker_rate` statt `taker_rate` angewendet — die Order **wird trotzdem gefillt**, aber zum Maker-Tarif. Kein analoger Skip-Mechanismus wie bei Buys.

***

### 7. Position-Sizing

#### 7.1 `buyPercentage` — Trigger, nicht Sizing

* **Bedeutung:** Prozentuale **Preis-Distanz** zu `lastBuyPrice`. Wenn `price ≥ lastBuyPrice × (1 + pct)`, feuert ein **UP-Trigger**; wenn `price ≤ lastBuyPrice × (1 − pct)`, ein **DOWN-Trigger**.
* **Cascade:** in einer einzelnen Cascade-Auswertung können bis zu **10000 Trigger nacheinander** feuern (Cap, siehe §10.4).
* **Richtung:** UP-Trigger nur wenn `canBuyUp=True`; DOWN-Trigger nur wenn `canBuyDown=True`.
* **`lastBuyPrice` Update:** nach jedem Trigger auf den **aktuellen Preis** (nicht die Trigger-Schwelle).

#### 7.2 Splits & Volumen

* **`buyVolumes`:** Liste von Prozentsätzen. Werden zu Gewichten normalisiert: `buyWeights[i] = buyVolumes[i] / 100` (NICHT `/sum(buyVolumes)` — Live-Bot-Match).
* **`buySplits`:** Anzahl Splits (= `len(buyVolumes)`). Engine-Default `7`.
* **Validierung:** `len(buyVolumes) == buySplits == len(sellPercentages)` — sonst `ValueError`. `sum(buyVolumes) > 0` — sonst `ValueError`.
* **`investmentPerBuy`:** fixe Quote-Menge pro Trigger; pro Split: `inv_split = investmentPerBuy × buyWeights[i]`.
* **`investmentPercentMode=True`:** ersetzt fixe Menge durch `pool = (quote_bal − locked_quote) × investmentPerFreeQuotePercent / 100`; dann `inv_split = max(pool × buyWeights[i], minInvestmentPerQuote × buyWeights[i])`.
* **`locked_quote`:** Σ `bo_price × bo_qty` aller offenen Buy-Orders.
* **`minInvestmentPerQuote`:** untere Schranke im Percent-Mode.

#### 7.3 Hard-Limits (Binance-Filter & Engine-Caps)

* **`tickSize`:** Preis-Rundung — Buy `floor`, TP-Sell `ceil`, SL/TSL `floor`. Default `1e-8`.
* **`stepSize`:** Quantity-Rundung. Buy-Qty wird **`ceil`** zur Step-Size gerundet (Live-Bot-Match), Sell-Qty wird `floor`. Default `1`.
* **`minNotional`:** Orders mit `price × qty < minNotional` werden **stillschweigend übersprungen**. API-Runner-Default `1.0`.
* **`dontBuyBelowQuoteAssetBalance`:** wenn `quote_bal < dontBuyBelowQuoteAssetBalance`, blockiert kompletten Trigger.
* **PERCENT\_PRICE\_BY\_SIDE-Filter (Binance, hart einkodiert):** Buys mit `|price_rounded − current_price| / current_price > 5%` werden übersprungen. **Kein Config-Feld** — Hard-Cap.
* **Cascade-Cap:** maximal 10000 Trigger pro `_cascade_to_price`-Call.
* **Max offene Positionen:** kein hartes Limit; begrenzt durch Cash, Distance-Check und PERCENT\_PRICE-Filter.

#### 7.4 Cash-Knappheit

* **`cost > quote_bal`:** Split wird übersprungen, kein Fehler, kein Log.
* **Parallele Triggers:** sequenziell pro Bar — wer zuerst kommt, kriegt zuerst Cash.

#### 7.5 Hebel / Liquidation — Hebel 1× Spot, fix. Liquidation nicht modelliert.

#### 7.6 Duplikat-Prevention

* `_should_place_new_order_based_on_existing`: pro Split-Index prüft die Engine, ob bereits eine offene oder gefüllte Buy-Order in `min_distance` existiert.
* `min_distance = max(buyPercentage / 100 × current_price, tickSize)` — der `tickSize`-Floor ist wichtig für Low-Price-Tokens (PEPE, SHIB).
* Falls duplikat-nahe: Split wird übersprungen.

***

### 8. Risk-Management (SL / TP / Trailing)

#### 8.1 Stop-Loss

* **Aktivierung:** `stopLoss=True` (parsed via `safe_bool`) + `stopLossPercentage`. Engine-Default `stopLoss=False`, `stopLossPercentage=3.0`. Modes typisch `5.0` aber `stopLoss="false"` (also inaktiv).
* **Setzpunkt:** **Filled-Buy-Preis**: `sl_price = filled_price × (1 − stopLossPercentage / 100)`.
* **Pro Buy ein eigener SL.** Bei Multi-Split: jeder Split hat eigenen, unabhängigen SL.
* **Bewegt sich nach Entry?** Nein — statisch.
* **Fill:** Market-Sell, `taker_fee` (oder `maker_fee` falls `onlyMakerSell=True`), Bid-Cross.

#### 8.2 Take-Profit

* **`sellPercentages`:** Liste — **ein TP pro Split** (siehe §9).
* **Setzpunkt:** `sell_price_raw = filled_price × (1 + sellPercentages[split_index] / 100)`; `sell_price = ceil_to_tick(sell_price_raw)`.
* **Fill:** Limit-Sell, `maker_fee`, Effektiv-Preis `TP × (1 − half_spread)`.
* **TSL-Override:** Ist `tsl_drop_pct > 0`, wird der TP **nicht** als reguläre Limit-Order behandelt — nur als **Aktivierungs-Schwelle** für TSL (§8.3).

#### 8.3 Trailing-Stop

* **`trailingStopLossPercentages`:** Liste — **per Split** ein eigener Trail-Drop-Prozentsatz. Engine padded/truncated auf `buySplits` Länge.
* **TSL-Clamp bei Konflikt:** Wenn `tsl_drop_pct >= sellPercentages[i]`, wird `tsl_drop_pct` automatisch auf `sellPercentages[i] / 2` reduziert.
* **Aktivierungs-Preis:** `tsl_activation = sell_price` — also **identisch zum TP-Level** dieses Splits. TSL aktiviert sich, sobald `seg_high >= tsl_activation`.
* **Initial-ATH bei Aktivierung:** `tslAth = tslActivation` (NICHT `seg_high`).
* **TSL-Level Update:** pro Segment, falls `seg_high > current_ATH`: `ATH = seg_high`, `tsl_level = ATH × (1 − tsl_drop / 100)`, gerundet auf `floor_tick`.
* **Safety-Floor:** `tsl_level = max(tsl_level, floor_tick(buy_price) + tickSize)` — TSL darf **niemals unter den Buy-Preis** fallen.
* **Trigger:** TSL feuert, wenn `seg_low ≤ current_tsl_level`.
* **Vorwärts-only:** TSL-Level wandert nur nach oben (engt sich an), nie zurück.
* **TSL ersetzt TP:** Splits mit aktivem TSL skippen den nominalen TP-Fill (§4.5).
* **Fee:** `taker_rate` (oder `maker_rate` falls `onlyMakerSell=True`), Market-Sell mit Bid-Cross.

#### 8.4 Time-Stop

* Direkter Time-Stop existiert nicht — aber `sellTimeCurves` (§11) erlaubt zeit-basierte TP-Adjustierung.

#### 8.5 Cool-down nach Trigger

* **`triggerCoolDown` (Default `0`):** Einheit = **Bars** (Index-Distanz). Wenn `i − lastTriggerIndex < triggerCoolDown`, wird `canBuy = False` für diese Bar.
* **Scope:** alle Buy-Triggers (UP und DOWN). Sells unbetroffen.
* **Restoration:** `canBuy` wird am Bar-Ende auf den ursprünglichen Wert zurückgesetzt.

***

### 9. Multi-Level-Sells (`sellPercentages`)

**Wichtig — diese Engine hat *keine* Multi-Level-Exits pro Position.**

Stattdessen erzeugt jeder Trigger **N parallele unabhängige Splits** (= N getrennte Buy-Orders), und jeder Split hat **genau einen** TP.

* **`sellPercentages[i]`:** TP-Distanz für **Split i** (nicht TP-Level Nr. i einer Position).
* **Anteils-Bezug:** jeder Split ist eine eigene Position; `sellPercentages[i]` wird relativ zum **Filled-Buy-Preis von Split i** angewendet.
* **Beispiel:** `buyVolumes=[40, 30, 30]`, `sellPercentages=[1.5, 3.0, 5.0]` → drei unabhängige Buys mit TPs bei +1.5 %, +3.0 %, +5.0 % über *jeweils eigenem* Buy-Preis.
* **Reihenfolge der TP-Erfüllung:** richtet sich nach Preis-Erreichen.
* **SL nach Partial-Exit:** keiner im klassischen Sinn — jeder Split hat eigenen SL, unbeeinflusst von anderen Splits. Kein Break-Even-Move.

***

### 10. Multi-Entry & Cascade

#### 10.1 UP-Trigger cancelt alle offenen Buys

**Kritisches Verhalten:** Wenn ein UP-Trigger feuert, **werden ALLE offenen Buy-Orders gecancelt** (`_cancel_all_open_buys`), bevor neue Splits zur neuen Schwelle platziert werden.

* Konsequenz: Nicht-gefüllte Buy-Limits aus früheren DOWN-Triggern verschwinden, sobald der Preis nach oben dreht.
* Implementierung: Canceled Buys werden **physisch aus den Arrays entfernt** (Pop-and-Swap), nicht nur als "canceled" markiert.

#### 10.2 DOWN-Trigger

* **`canBuyDown=True`:** ein DOWN-Trigger platziert **alle Splits** simultan.
* **Kein Cancel** offener Buy-Orders bei DOWN-Triggern — sie bleiben aktiv (kann zu Stapeln führen).

#### 10.3 Trigger-Logik & lastBuyPrice

* **Trigger-Schwellen:** `up_thr = lastBuyPrice × (1 + pct)`, `dn_thr = lastBuyPrice × (1 − pct)`.
* **`lastBuyPrice` Update nach Trigger:** auf den **aktuellen Preis** (`current_price`), nicht die Schwelle.
* **Falls `canBuyUp=False` und UP-Schwelle gerissen:** `lastBuyPrice` wird trotzdem auf `current_price` aktualisiert, aber **kein Trigger gefeuert**. Identisch für `canBuyDown=False`.

#### 10.4 Cascade-Mechanik

* Pro Segment werden **alle ausgelassenen Trigger** zwischen `lastBuyPrice` und Segment-Start (Phase 1) bzw. Segment-End (Phase 3) nachträglich gefeuert.
* **Cap:** maximal `10000` Trigger pro `_cascade_to_price`-Call. Nach 10000 wird stillschweigend abgebrochen.
* **Average-Entry:** **NEIN** — keine Wieder-Berechnung von SL/TP. Jeder Buy ist eine getrennte Position.

#### 10.5 Globale Buy/Sell-Schalter

* **`canBuy=False`:** komplette Cascade-Auswertung wird übersprungen.
* **`canSell=False`:** nach einem Buy-Fill wird **kein** Sell-Order erzeugt — Hodl-Modus.

***

### 11. Zeit-basierte Sell-Repricing (`sellTimeCurves`)

> Komplexe, aber optionale Komponente. Aktiv nur wenn `sellTimeCurves` non-empty Dict ist.

#### 11.1 Schema

```jsonc theme={null}
"sellTimeCurves": {
  "0.0025": [
    {"after": "5m",  "percent": 0.005, "tslPercent": 0.002},
    {"after": "10m", "percent": 0.0075, "tslPercent": 0.003},
    {"after": "200m","percent": 0.25,  "tslPercent": 0.1}
  ]
}
```

#### 11.2 Key-Lookup

* Engine normalisiert via `_normalize_percent_key(n)`: `f'{n:.10f}'.rstrip('0').rstrip('.')` → z.B. `2.5` → `"2.5"`, `0.0025` → `"0.0025"`.
* Fallback auf Underscore-Variante: `"2_5"` für Filesystem-Kompatibilität.
* Falls Key nicht gefunden: Sell behält ursprüngliche `sellPercentages[i]`.

#### 11.3 `after`-Format

* Akzeptiert: `"5m"`, `"1h"`, `"1d"`, `"30s"`, `"1w"` oder rohe Millisekunden.
* Multiplikatoren: `s=1000, m=60000, h=3600000, d=86400000, w=604800000`.
* Auch alternativer Key `"afterMs"` wird gelesen.

#### 11.4 Re-Pricing-Loop

* **Frequenz:** alle `sellTimeCurveCheckIntervalMs / 1000` Bars (bei 1m-Bars und Default 60000ms = alle 60 Bars ≈ 1h).
* **Logik:** für jeden offenen Sell:
  1. `elapsed = current_time_ms − buy_time_ms`
  2. Letzten passenden Tier auswählen (höchstes `after_ms` mit `<= elapsed`).
  3. `new_pct = tier.percent` → `new_sell_price = buy_fill_price × (1 + new_pct / 100)`, gerundet auf tickSize (ceil).
  4. Falls `tier.tslPercent` gesetzt: `tsl_drop_pct` ebenfalls auf neuen Wert.
  5. Sell-Order wird re-priced (alter Preis aus Index entfernt, neuer eingefügt).

#### 11.5 Tier-Felder

* **`percent` / `percentage` / `pct`:** alle drei Keys werden geprüft (Live-Bot-Kompatibilität).
* **`tslPercent` / `tslPercentage` / `tsl`:** alle drei Keys werden geprüft (optional).

***

### 12. Sell-Aktivierungs-Logik (`sellActivateDistancePercentage` / `sellCancelDistancePercentage`)

> Modelliert "Sell-Order nur an Exchange schicken, wenn Preis nahe genug ist". Aktiv wenn beide Felder > 0.

#### 12.1 Aktivierungs-Range

* Sell-Order ist "active on exchange" wenn:
  * `price_diff_pct ∈ [sellActivateDistancePercentage, sellCancelDistancePercentage]` ODER
  * Preis bereits **über** Sell-Preis (`current_price >= sell_price`).
* `price_diff_pct = |current_price − sell_price| / sell_price × 100`.
* Falls außerhalb: Order wird **deaktiviert** (`is_active_on_exchange = False`), TP-Fill geblockt.

#### 12.2 TSL-Bypass

* TSL-Orders (`tsl_drop_pct > 0`) **umgehen die Aktivierungs-Logik komplett** — sind immer aktiv.

#### 12.3 Cumulative-Balance-Cap (Live-Bot-Match)

* Engine summiert `qty` aller bereits aktiven Sells als "reserved base".
* Kandidaten zum Aktivieren werden nach **Distanz aufsteigend** (closest first) sortiert.
* Nur so viele werden aktiviert, wie in `base_bal − reserved` passen. Bei Überschreitung: **Early-Break**.

#### 12.4 Re-Evaluation

* **Pro Bar:** auf Bar-Open.
* **Pro Segment:** auf Segment-Start-Preis.

***

### 13. Portfolio- und Equity-Buchhaltung

#### 13.1 Equity-Spalten (in `assetbalanceshistorie.csv`)

| Spalte                      | Formel                                                | Verwendung                    |
| --------------------------- | ----------------------------------------------------- | ----------------------------- |
| `timestamp`                 | ISO-8601                                              | Zeit                          |
| `quoteAssetBal`             | freier Cash inkl. Reservierungen für offene Buys      | Cash-Saldo                    |
| `baseAssetBal`              | offene Base-Menge                                     | Position                      |
| `baseValueInQuote_mid`      | `base × close` (kein Spread, keine Fees)              | "Mark"-Wert                   |
| `baseValueInQuote_exitNet`  | `base × close × (1 − half_spread) × (1 − taker_rate)` | konservativ                   |
| `totalValueInQuote_mid`     | `quote + baseValueInQuote_mid`                        | Equity-mid                    |
| `totalValueInQuote_exitNet` | `quote + baseValueInQuote_exitNet`                    | Equity-net (für `return_pct`) |
| `totalValueInQuote`         | identisch zu `totalValueInQuote_mid`                  | Legacy-Alias                  |
| `baseCostInQuote`           | Σ Cost-Basis aller offenen Buys                       | Cost-Basis                    |
| `unrealizedPnL_exitNet`     | `baseValueInQuote_exitNet − baseCostInQuote`          | Unrealized                    |
| `reason`                    | konstant `"tick"`                                     | Snapshot-Typ                  |

#### 13.2 Equity-Curve-Sampling

* **Frequenz:** **pro Bar** zum Close, exakt ein Snapshot mit `reason="tick"`.
* **Keine zusätzlichen Snapshots** auf Fill-Events. Realized-PnL wird in `quote_bal` reflektiert, das im nächsten Tick-Snapshot sichtbar ist.

#### 13.3 Drawdown

* **Wird in der Engine NICHT berechnet.** Equity-Curve wird gespeichert; Drawdown-Berechnung erfolgt downstream.

#### 13.4 `baseCostInQuote` — exakte Definition

* Wenn `fees_in_quote=True`: `cost = filled_price × bo_qty + bo_fee_q` für jeden noch lebenden Buy.
* Wenn `fees_in_quote=False`: `cost = filled_price × bo_qty` (ohne Fee — die wurde in Base bezahlt).

#### 13.5 Initial-Capital

* **`startBal` (Default `10000`).**
* **Reinvest:** ja — realisierter Profit wandert in `quote_bal` und steht für nächste Buys zur Verfügung.

***

### 14. Multi-Symbol-Koordination

* **Cash-Pool:** **nicht gemeinsam.** Jedes Symbol läuft in eigenem Subprocess mit eigenem `startBal`.
* **Reihenfolge bei gleichzeitigen Signalen:** entfällt (Single-Symbol pro Run).
* **Korrelation:** nicht modelliert.
* **Portfolio-Limits:** existieren auf Engine-Level nicht.

***

### 15. Metriken & Statistiken

#### 15.1 Trade-Definition & Profit-Formel

* **Round-Trip:** ein Trade = ein Buy + sein zugehöriger Sell. Multi-Split-Trigger erzeugen N getrennte Trades.
* **Trade-PnL (Live-Bot-Match):**
  ```text theme={null}
  proportion = sell_qty / actual_received_qty
  buy_fee_allocated = buy_fee_in_quote × proportion
  total_buy_cost = buy_fill_price × sell_qty                     // OHNE Fee
  total_sell_value = effective_sell_price × sell_qty
  profit = total_sell_value − total_buy_cost − buy_fee_allocated − sell_fee_in_quote
  profit_pct = profit / total_buy_cost × 100                     // Nenner OHNE Fee
  ```

#### 15.2 In Engine berechnet

* `total_profit`, `avg_profit`, `min_profit`, `max_profit`
* `win_rate` = `(profit > 0).count / total × 100`
* `final_value` = `totalValueInQuote_exitNet` der letzten Bar
* `return_pct` = `(final_value − start_balance) / start_balance × 100`
* `fulfilled_trades`, `active_orders`

#### 15.3 Nicht in Engine berechnet (Downstream)

* Sharpe-Ratio, Sortino-Ratio
* Drawdown / Max-Drawdown
* Annualisierte Returns
* Profit-Factor, Expectancy
* Calmar-Ratio, Recovery-Factor

#### 15.4 `fillType`-Werte (in `fulfilledorders.csv`)

* `"TP"` — Take-Profit-Limit-Sell gefüllt
* `"SL"` — Stop-Loss-Market-Sell gefüllt
* `"TSL"` — Trailing-Stop-Loss-Market-Sell gefüllt

Buy-Trades haben **kein** `fillType` (leer/NULL).

#### 15.5 `trades_records`-Type-Werte (intern)

* `"BUY"` — Buy-Fill
* `"SELL"` — TP-Fill (Limit)
* `"SELL_SL"` — SL-Fill
* `"SELL_TSL"` — TSL-Fill

***

### 16. Determinismus & Reproduzierbarkeit

* **RNG-Seed:** keiner — Engine nutzt **kein** `random.*`. Vollständig deterministisch.
* **Tie-Breaking:** Order-IDs sind sequentielle Integer-Counters (`buy_id`, `sell_id`).
* **Floating-Point:** `float64` durchgehend; keine Decimal-Library.
* **Reihenfolge-Stabilität:** identische Inputs → bit-identische Outputs (gleicher `config_hash`).
* **Numba-JIT:** nur für `in_range`-Helper; `safe no-op` falls Numba unavailable. Keine Verhaltensänderung.

***

### 17. Edge-Cases & Fehlerbehandlung

| Szenario                                       | Verhalten                                      |
| ---------------------------------------------- | ---------------------------------------------- |
| Insufficient Cash für Buy                      | Split stillschweigend übersprungen             |
| `notional < minNotional`                       | Order übersprungen                             |
| `quote_bal < dontBuyBelowQuoteAssetBalance`    | Trigger übersprungen                           |
| `qty == 0` nach Step-Rounding                  | Order übersprungen                             |
| Buy-Preis > 5 % von current\_price             | Order übersprungen (PERCENT\_PRICE\_BY\_SIDE)  |
| `onlyMakerBuy` und Buy-Preis ≥ Markt           | Order übersprungen                             |
| Cascade > 10000 Trigger                        | abgebrochen, weitere Trigger ignoriert         |
| Symbol delisted mitten im Run                  | nicht modelliert                               |
| OHLC ungültig (`H < L`)                        | keine Validierung                              |
| Zero-Volume-Bar                                | normal prozessiert                             |
| Negativer Preis                                | nicht unterstützt                              |
| Position-Größe rundet auf 0                    | Order übersprungen via `minNotional`           |
| End-of-Data mit offener Position               | Position **bleibt offen**; **kein Auto-Close** |
| Subprocess-OOM (RLIMIT\_AS)                    | non-zero Exit, Backtest als `failed`           |
| Empty DataFrame                                | `ValueError` → Backtest failed                 |
| `len(buyVolumes) != buySplits`                 | `ValueError`                                   |
| `sum(buyVolumes) <= 0`                         | `ValueError`                                   |
| `tsl_drop >= sellPercentages[i]`               | auto-clamped auf `sellPercentages[i] / 2`      |
| `len(trailingStopLossPercentages) < buySplits` | auto-padded mit `0.0`                          |
| `len(trailingStopLossPercentages) > buySplits` | auto-truncated                                 |

***

### 18. Boolean-Parsing (Mode-JSON-Quirk)

Modes sind historisch JavaScript-kompatibel (Live-Bot ist Node.js) und enthalten **Booleans als Strings**:

```jsonc theme={null}
{
  "stopLoss": "false",     // String, nicht boolean false
  "canBuyUp": "true",
  "onlyMakerBuy": "false"
}
```

Engine parsed via `safe_bool(val, default)`:

1. Falls `isinstance(val, bool)` → direkt zurück
2. Falls `isinstance(val, str)` → `val.strip().lower() == 'true'`
3. Falls `val is None` → `default`
4. Sonst → `bool(val)` (truthy/falsy)

**Konsequenz:** Sowohl JSON-Booleans (`true`/`false`) als auch String-Booleans (`"true"`/`"false"`) funktionieren. Andere Strings (`"yes"`, `"1"`, `"on"`) werden als `False` gewertet.

***

### 19. Binance-Filter / Hard-Caps (nicht-konfigurierbar)

| Constraint                       | Wert                               | Code-Ort                              | Bedeutung                                                 |
| -------------------------------- | ---------------------------------- | ------------------------------------- | --------------------------------------------------------- |
| `PERCENT_PRICE_BY_SIDE`          | `5.0` %                            | `_place_split_buy_orders`             | Buy-Limit darf max. 5 % vom aktuellen Markt entfernt sein |
| Cascade-Trigger-Cap              | `10000`                            | `_cascade_to_price`                   | Schutz gegen Endlos-Cascade                               |
| TSL-Safety-Floor                 | `floor_tick(buy_price) + tickSize` | `_try_tsl_on_segment`, `_add_sell`    | TSL niemals unter Buy-Preis                               |
| TSL-Drop-Clamp                   | `sell_pct × 0.5`                   | `_place_split_buy_orders`, `__init__` | Falls `tsl_drop ≥ sell_pct`                               |
| Within-Segment-Same-Order-Schutz | implizit                           | `_try_fill_*_on_segment`              | Sell aus Segment N fillt nicht im selben Segment          |

***

### 20. Konfigurations-Inputs (vollständig)

Felder, die das Engine-Verhalten ändern. Defaults aus drei Schichten — Engine-Code → MARKET\_DEFAULTS (API-Runner) → Mode-JSON. Spätere Schicht überschreibt frühere.

#### 20.1 Markt-Konstanten

| Feld          | Typ   | Engine-Default | Runner-Default | Wirkung             |
| ------------- | ----- | -------------- | -------------- | ------------------- |
| `symbol`      | str   | `"PEPEUSDC"`   | `"PEPEUSDT"`   | Trading-Pair        |
| `tickSize`    | float | `1e-8`         | `1e-8`         | Preis-Rundung       |
| `stepSize`    | float | `1`            | `1`            | Qty-Rundung         |
| `minNotional` | float | `0.0`          | `1.0`          | Order-Skip-Schwelle |
| `startBal`    | float | `10000`        | `10000`        | Initial-Cash        |

#### 20.2 Fees & Spread

| Feld                 | Typ   | Engine-Default | Runner-Default | Wirkung |
| -------------------- | ----- | -------------- | -------------- | ------- |
| `maker_fee_bps`      | float | `1.0`          | `7.5`          | §6.1    |
| `taker_fee_bps`      | float | `7.5`          | `7.5`          | §6.1    |
| `fees_in_quote`      | bool  | `True`         | `True`         | §6.2    |
| `assumed_spread_bps` | float | `1.5`          | `1.5`          | §6.3    |

#### 20.3 Engine-Modi

| Feld                    | Typ       | Default                           | Wirkung            |
| ----------------------- | --------- | --------------------------------- | ------------------ |
| `intrabar_mode`         | str       | `"OHLC"` engine / `"OLHC"` runner | §4.4               |
| `triggerCoolDown`       | int(bars) | `0`                               | §8.5               |
| `order_latency_seconds` | int(s)    | `0` engine / `2` runner           | **unbenutzt** §1.6 |
| `start`                 | bool-str  | `True`                            | **unbenutzt** §1.6 |

#### 20.4 Steuer-Flags

| Feld            | Typ      | Default                                           | Wirkung |
| --------------- | -------- | ------------------------------------------------- | ------- |
| `canBuy`        | bool-str | `True`                                            | §10.5   |
| `canSell`       | bool-str | `True`                                            | §10.5   |
| `canBuyUp`      | bool-str | `True`                                            | §10.3   |
| `canBuyDown`    | bool-str | `True` engine / Mode-spezifisch (typisch `False`) | §10.2   |
| `onlyMakerBuy`  | bool-str | `False`                                           | §6.6    |
| `onlyMakerSell` | bool-str | `False`                                           | §6.6    |

#### 20.5 Position-Sizing

| Feld                            | Typ          | Default                        | Wirkung |
| ------------------------------- | ------------ | ------------------------------ | ------- |
| `buyPercentage`                 | float (%)    | `0.1`                          | §7.1    |
| `buySplits`                     | int          | `7` engine / `len(buyVolumes)` | §7.2    |
| `buyVolumes`                    | list\[float] | `[25,15,15,15,10,10,10]`       | §7.2    |
| `investmentPerBuy`              | float        | `20`                           | §7.2    |
| `investmentPercentMode`         | bool         | `False`                        | §7.2    |
| `investmentPerFreeQuotePercent` | float (%)    | `0.0`                          | §7.2    |
| `minInvestmentPerQuote`         | float        | `0.0`                          | §7.2    |
| `dontBuyBelowQuoteAssetBalance` | float        | `0.0`                          | §7.3    |

#### 20.6 Risk-Management

| Feld                          | Typ          | Default                        | Wirkung   |
| ----------------------------- | ------------ | ------------------------------ | --------- |
| `stopLoss`                    | bool-str     | `False`                        | §8.1      |
| `stopLossPercentage`          | float (%)    | `3.0` engine / `5.0` modes     | §8.1      |
| `sellPercentages`             | list\[float] | `[0.25,0.35,0.5,0.75,1,2.5,5]` | §8.2 / §9 |
| `trailingStopLossPercentages` | list\[float] | `[0]*buySplits`                | §8.3      |

#### 20.7 Sell-Aktivierung & Time-Curves

| Feld                             | Typ       | Default | Wirkung |
| -------------------------------- | --------- | ------- | ------- |
| `sellActivateDistancePercentage` | float (%) | `0.0`   | §12.1   |
| `sellCancelDistancePercentage`   | float (%) | `0.0`   | §12.1   |
| `sellTimeCurves`                 | dict      | `{}`    | §11     |
| `sellTimeCurveCheckIntervalMs`   | int (ms)  | `60000` | §11.4   |

#### 20.8 Daten-Range

| Feld   | Typ        | Default  | Wirkung            |
| ------ | ---------- | -------- | ------------------ |
| `from` | ISO-string | optional | Daten-Range-Beginn |
| `to`   | ISO-string | optional | Daten-Range-Ende   |

***

### 21. CSV-Output-Schemas

#### 21.1 `fulfilledorders.csv`

| Spalte                | Typ     | Beschreibung                                    |
| --------------------- | ------- | ----------------------------------------------- |
| `buyOrderId`          | int     | sequenzielle Buy-ID                             |
| `sellOrderId`         | int     | sequenzielle Sell-ID                            |
| `buyPrice`            | float   | Effektiver Buy-Preis (post-spread)              |
| `sellPrice`           | float   | Effektiver Sell-Preis                           |
| `buyQuantity`         | float   | Original Buy-Qty (vor Fee falls fees\_in\_base) |
| `sellQuantity`        | float   | Sell-Qty (= base\_received vom Buy)             |
| `buyFeeInQuoteAsset`  | float   | Buy-Fee, immer in Quote                         |
| `sellFeeInQuoteAsset` | float   | Sell-Fee, immer in Quote                        |
| `profit`              | float   | siehe §15.1                                     |
| `profitPercentage`    | float   | siehe §15.1                                     |
| `buyTime`             | ISO-str | Buy-Fill-Zeit                                   |
| `sellTime`            | ISO-str | Sell-Fill-Zeit                                  |
| `symbol`              | str     | Trading-Pair                                    |
| `baseAsset`           | str     | Base-Currency                                   |
| `quoteAsset`          | str     | Quote-Currency                                  |
| `fillType`            | str     | `"TP"` / `"SL"` / `"TSL"`                       |

#### 21.2 `assetbalanceshistorie.csv`

Siehe §13.1 für die 11 Spalten.

#### 21.3 `activeorders.csv`

| Spalte                              | Typ       | Beschreibung                 |
| ----------------------------------- | --------- | ---------------------------- |
| `symbol`, `baseAsset`, `quoteAsset` | str       | Trading-Pair                 |
| `type`                              | str       | `"buyOrder"` / `"sellOrder"` |
| `id`                                | int       | Order-ID                     |
| `status`                            | str       | `"open"` / `"canceled"`      |
| `orderPlacedAtIndex`                | int       | Bar-Index der Platzierung    |
| `activeFromIndex`                   | int       | Bar-Index ab Aktivierung     |
| `buyPrice`/`sellPrice`              | float-str | je nach `type`               |
| `quantity`                          | float-str | Order-Qty                    |
| `sellPercentage`                    | float-str | nur Buys, der zugehörige TP  |
| `buyOrderId`                        | int       | nur Sells, Verweis auf Buy   |
| `tslDropPct`                        | float-str | nur Sells, TSL-Drop          |
| `tslActive`                         | bool      | nur Sells                    |
| `tslAth`                            | float-str | nur Sells                    |
| `tslPrice`                          | float-str | nur Sells                    |
| `isActiveOnExchange`                | bool      | nur Sells                    |

***

### 22. Bekannte Limitationen / Nicht modelliert

* **Order-Book / Market-Impact:** keine Tiefe; Slippage rein über `assumed_spread_bps` (konstant).
* **Liquidations-Mechanik:** entfällt (Spot-Only).
* **Borrowing-Kosten / Margin-Zinsen:** nicht modelliert.
* **Funding-Rates:** nicht modelliert (Spot).
* **Multi-Symbol-Portfolio:** kein gemeinsamer Cash-Pool.
* **Stress-Events / Halts:** nur insofern abgebildet, als sie in den OHLCV-Daten als Lücke auftauchen.
* **Stop-Limit / OCO / Iceberg:** nicht unterstützt.
* **`order_latency_seconds`:** Config-Feld vorhanden, aber nicht angewandt.
* **`start`-Flag:** Config-Feld vorhanden, aber nicht angewandt.
* **Optimistisches Gap-Handling für Limits:** Limits fillen am Limit-Level statt am Open jenseits.
* **Maker-Reklassifizierung bei `onlyMakerSell`:** Order fillt trotzdem als Market-Sell, aber zum Maker-Tarif.
* **PERCENT\_PRICE\_BY\_SIDE 5 %:** hart einkodiert, nicht konfigurierbar.
* **Cascade-Cap 10000:** bei extrem volatilen Phasen (>10000 buyPercentage-Steps) gehen weitere Trigger verloren.
* **Indikator-basierte Strategien:** nicht möglich; Engine ist preis-/distanz-basiert.
* **Drawdown / Sharpe / Sortino:** nicht in Engine, nur downstream.

***

### 23. Changelog

| Version | Datum        | Änderung                                                                                                                                                                                                                                                                                                                                                                        |
| ------- | ------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `1.0`   | `2026-05-04` | Initial-Spec — Audit von `backtester_numba.py`.                                                                                                                                                                                                                                                                                                                                 |
| `1.1`   | `2026-05-04` | Massiv erweitert: 6 Verifikations-Punkte aus v1.0 aufgelöst; neue §11 (sellTimeCurves), §12 (sellActivation), §18 (Boolean-Parsing), §19 (Hard-Caps), §21 (CSV-Schemas); §10 erweitert (UP-Cancel, Cascade-Cap); §8.3 (TSL) komplett neu mit Safety-Floor, Activation-Quelle, ATH-Init; §17 ergänzt um Validation-Errors. §4.2 präzisiert (TP-vs-SL ehrlich, OLHC-strukturell). |
