d36ed142cf
Drei Erweiterungen aus der vorangegangenen Analyse. Lerngeschwindigkeit - background_sample_every_n_bars von 10 auf 5. Gemessen stammen nur rund 3 % der Beobachtungen aus echten Trades; diese Stichproben sind der wirksamste Hebel. Nicht weiter gesenkt, weil benachbarte Kerzen stark korreliert sind und sich die Label-Fenster überlappen - mehr Gradientenschritte heißt dort nicht mehr Information. Funding Rate und Open Interest als Merkmale (strategy.derivatives, standardmäßig aus) - Vier zusätzliche Merkmale vom Perpetual zum jeweiligen Spot-Paar. Gehandelt wird weiterhin Spot, die Kennzahlen kommen über einen zweiten ccxt-Client mit defaultType=future. - Die Zuordnung ist lookahead-frei: Für jede Kerze gilt nur der Wert, der zu diesem Zeitpunkt bereits veröffentlicht war. - Fällt eine Quelle aus oder deckt sie weniger als min_coverage ab, bleiben die Spalten neutral. Die Modelldimension bleibt dabei stabil. - Gemessene API-Grenzen bei Binance: Open Interest reicht 30 Tage zurück, 500 Zeilen je Abruf; Funding Rate über ein Jahr. Beide Merkmale sind deshalb einzeln abschaltbar. ERGEBNIS: kein Nutzen. Zwei Backtests mit identischen Kerzen und Seed - 5m/20 Tage: Rendite -1,79 % auf -1,90 %, Accuracy 51,2 % auf 50,4 %; 15m/28 Tage: Rendite -2,22 % auf -2,67 %, LogLoss praktisch unverändert. Die Anbindung arbeitet einwandfrei (100 % Datenabdeckung), das Modell gewichtet die neuen Merkmale aber nur mit 0,01 bis 0,09 gegenüber 0,39 für ema_spread. Die Funktion bleibt aus und ist dafür da, das auf anderen Zeiträumen selbst zu prüfen - nicht weil sie sich bewährt hätte. Ollama-Erklärungen (llm, standardmäßig aus) - Neuer Dashboard-Bereich und POST /control/explain. Das Modell bekommt den Zustand als Text und gibt Text zurück; es entscheidet nichts, beeinflusst keine Order und wird nie aus dem Handels-Loop heraus aufgerufen. Der System-Prompt untersagt Anlageempfehlungen und Kursprognosen. - Bewusst nicht als Entscheider: nicht reproduzierbar, kaum backtestbar, und es würde die Nachvollziehbarkeit des linearen Modells zerstören. - Beim Test an qwen3.8:27b zeigte sich ein echter Fehler: Reasoning-Modelle legen ihre Denkschritte in ein eigenes Antwortfeld und verbrauchten dafür das gesamte Token-Budget, response blieb leer. llm.think ist jetzt standardmäßig false, die Fehlermeldung nennt Ursache und Ausweg statt nur "leere Antwort", und für ältere Ollama-Versionen ohne das Feld gibt es einen Wiederholungsversuch ohne es. Bewusst nicht enthalten: News- und Google-Trends-Sentiment. Es fehlt eine Quelle mit Point-in-Time-Historie; ohne die lässt sich das Merkmal nicht backtesten. Nach dem Ergebnis oben wäre ein unvalidiertes Merkmal der falsche Schritt. test_stop_loss_bounds_the_worst_trade läuft jetzt mit strategy.name: rules. Er hing über die geänderte Voreinstellung an zufälligem Modellverhalten, geprüft werden soll aber die Stop-Logik. 270 Tests (36 neue), ruff sauber. Gegen echte Binance-Daten und eine laufende Ollama-Instanz geprüft, Dashboard-Bereich im Browser bedient.
621 lines
27 KiB
Markdown
621 lines
27 KiB
Markdown
# TradeMind
|
||
|
||
Ein per Podman deploybarer Krypto-Trading-Bot mit drei Betriebsarten:
|
||
|
||
| Modus | Kurse | Orders | Lernen | Zweck |
|
||
|------------|--------------|------------------|--------|------------------------------------------|
|
||
| `paper` | live | simuliert | ja | Dauerbetrieb ohne Risiko — **Standard** |
|
||
| `backtest` | historisch | simuliert | ja | Strategie und Parameter bewerten |
|
||
| `live` | live | echt | optional | Echtgeldhandel |
|
||
|
||
Die Börsenanbindung läuft über [ccxt](https://github.com/ccxt/ccxt) — Binance, Kraken, Coinbase,
|
||
Bybit, OKX, KuCoin, Bitget, Gate.io, MEXC und rund 100 weitere sind allein über den
|
||
Konfigurationsschlüssel `exchange.id` erreichbar (`trademind exchanges` listet alle auf).
|
||
|
||
> **Hinweis zum Risiko.** Der Bot ist ein Werkzeug, keine Ertragsgarantie und keine
|
||
> Anlageberatung. Die mitgelieferte Strategie ist eine funktionsfähige Grundlage, kein
|
||
> erprobtes Handelssystem — im Backtest unten liegt sie deutlich hinter Buy & Hold.
|
||
> Vor jedem Live-Einsatz gehören eigene Backtests, ein langer Paper-Lauf und eine bewusste
|
||
> Entscheidung über den Kapitaleinsatz. Der Live-Modus ist doppelt abgesichert und muss
|
||
> ausdrücklich freigeschaltet werden.
|
||
|
||
---
|
||
|
||
## Schnellstart
|
||
|
||
```bash
|
||
git clone <repo> && cd Trademind_Claude
|
||
cp config/config.example.yaml config/config.yaml
|
||
cp config/trademind.env.example config/trademind.env
|
||
podman build --format docker -t trademind:latest -f Containerfile .
|
||
podman-compose up -d
|
||
```
|
||
|
||
`--format docker` sorgt dafür, dass der eingebaute HEALTHCHECK ins Image übernommen wird —
|
||
das OCI-Format kennt die Anweisung nicht. Compose und Quadlet bringen ihren eigenen
|
||
Healthcheck mit, dort spielt das Format keine Rolle.
|
||
|
||
Dashboard: `http://<host>:8080/` · Logs: `podman-compose logs -f`
|
||
|
||
Ohne Zugangsdaten läuft der Bot im Paper-Modus auf echten Live-Kursen und beginnt sofort zu
|
||
lernen. Für den Start werden nur öffentliche Marktdaten gelesen — kein API-Schlüssel nötig.
|
||
|
||
### Ohne Container
|
||
|
||
```bash
|
||
python -m venv .venv && . .venv/bin/activate # Windows: .venv\Scripts\activate
|
||
pip install -e ".[dev]"
|
||
trademind backtest --config config/config.yaml --bars 6000 --fresh-model
|
||
trademind run --config config/config.yaml
|
||
```
|
||
|
||
---
|
||
|
||
## Deployment mit Podman
|
||
|
||
### Variante 1 — podman-compose
|
||
|
||
```bash
|
||
podman-compose up -d # starten
|
||
podman-compose logs -f # verfolgen
|
||
podman-compose down # stoppen
|
||
```
|
||
|
||
Port 8080 wird auf allen Interfaces veröffentlicht — das Dashboard ist damit standardmäßig
|
||
von anderen Rechnern erreichbar.
|
||
|
||
> **Sicherheit.** Die lesenden Endpunkte sind **nicht authentifiziert** — wer den Port
|
||
> erreicht, sieht Kontostand, offene Positionen und die gesamte Trade-Historie. Dazu kommen
|
||
> die Steuerbefehle unter `/control/…` (Training anstoßen, Lernen umschalten), die sich mit
|
||
> `server.control_token` schützen lassen und das auch sollten. Gehandelt oder konfiguriert
|
||
> werden kann über HTTP in keinem Fall. In offenen Netzen deshalb einen Reverse Proxy mit
|
||
> Authentifizierung davorsetzen, den Zugriff per Firewall auf bekannte Quell-IPs begrenzen
|
||
> oder auf rein lokalen Zugriff zurückstellen:
|
||
>
|
||
> | Datei | lokal statt offen |
|
||
> |------------------------------|----------------------------------|
|
||
> | `podman-compose.yml` | `- "127.0.0.1:8080:8080"` |
|
||
> | `deploy/trademind.container` | `PublishPort=127.0.0.1:8080:8080`|
|
||
>
|
||
> Ganz abschalten lässt sich der Server über `server.enabled: false` in der Konfiguration
|
||
> oder `trademind run --no-server`; der Container-Healthcheck braucht ihn allerdings.
|
||
|
||
> **Windows und macOS.** Dort läuft Podman in einer VM (`podman machine`), die
|
||
> veröffentlichte Ports nicht in jeder Konstellation an den Host weiterreicht. Ist das
|
||
> Dashboard nicht erreichbar, mit `podman machine ssh curl -s localhost:8080/health`
|
||
> gegenprüfen — antwortet das, liegt es an der VM-Weiterleitung, nicht am Bot. Auf Linux —
|
||
> dem Ziel für den Dauerbetrieb — greift die Veröffentlichung unmittelbar.
|
||
|
||
### Variante 2 — einzelner Container
|
||
|
||
```bash
|
||
podman volume create trademind-data
|
||
podman run -d --name trademind \
|
||
--restart=unless-stopped \
|
||
--env-file config/trademind.env \
|
||
-v ./config:/config:ro,Z \
|
||
-v trademind-data:/data:Z \
|
||
-p 8080:8080 \
|
||
--security-opt no-new-privileges \
|
||
trademind:latest
|
||
```
|
||
|
||
### Variante 3 — systemd über Quadlet (empfohlen für Dauerbetrieb)
|
||
|
||
```bash
|
||
mkdir -p ~/.config/containers/systemd ~/.config/trademind
|
||
cp deploy/trademind.container deploy/trademind-data.volume ~/.config/containers/systemd/
|
||
cp config/config.yaml config/trademind.env ~/.config/trademind/
|
||
loginctl enable-linger $USER
|
||
systemctl --user daemon-reload
|
||
systemctl --user start trademind
|
||
journalctl --user -u trademind -f
|
||
```
|
||
|
||
Der Container läuft als UID 10001 ohne zusätzliche Rechte. `podman stop` sendet SIGTERM; die
|
||
Engine beendet den laufenden Durchlauf, sichert Modell und Zustand und fährt sauber herunter.
|
||
|
||
### Persistenz
|
||
|
||
Alles Wichtige liegt im Volume `/data`:
|
||
|
||
| Pfad | Inhalt |
|
||
|-----------------------------|-----------------------------------------------------------|
|
||
| `/data/trademind.sqlite3` | Trades, Equity-Kurve, Laufzeitzustand |
|
||
| `/data/models/adaptive.npz` | Modellgewichte, Normalisierung, Erfahrungsspeicher |
|
||
|
||
Ohne dieses Volume geht bei jedem Neustart der gesamte Lernfortschritt verloren.
|
||
|
||
---
|
||
|
||
## Konfiguration
|
||
|
||
Alles läuft über `config/config.yaml`. Geheimnisse kommen über Umgebungsvariablen dazu:
|
||
|
||
```yaml
|
||
exchange:
|
||
id: ${TRADEMIND_EXCHANGE:-binance}
|
||
api_key: ${TRADEMIND_API_KEY}
|
||
api_secret: ${TRADEMIND_API_SECRET}
|
||
password: ${TRADEMIND_API_PASSWORD} # OKX, KuCoin, Coinbase Advanced, Bitget
|
||
sandbox: true # Testnet der Börse
|
||
```
|
||
|
||
Drei Ebenen, in dieser Reihenfolge:
|
||
|
||
1. Werte in der YAML-Datei
|
||
2. `${VAR}` / `${VAR:-fallback}` — aus der Umgebung eingesetzt
|
||
3. `TRADEMIND__ABSCHNITT__SCHLUESSEL` — überschreibt einzelne Werte punktuell,
|
||
z. B. `TRADEMIND__RISK__MAX_OPEN_POSITIONS=5`
|
||
|
||
Die vollständig kommentierte Vorlage steht in
|
||
[config/config.example.yaml](config/config.example.yaml).
|
||
|
||
### Börse wechseln
|
||
|
||
```bash
|
||
trademind exchanges --search kraken
|
||
```
|
||
|
||
Dann `exchange.id` setzen. Handelspaare in ccxt-Schreibweise (`BTC/USDT`, `ETH/EUR`);
|
||
alle Symbole müssen dieselbe Quote-Währung haben, sonst bricht der Start mit einer
|
||
verständlichen Meldung ab.
|
||
|
||
### Live-Modus freischalten
|
||
|
||
Zwei Bedingungen müssen erfüllt sein, sonst startet der Bot nicht:
|
||
|
||
```yaml
|
||
mode: live
|
||
live_confirmation: I_UNDERSTAND_THE_RISK
|
||
exchange:
|
||
api_key: ...
|
||
api_secret: ...
|
||
```
|
||
|
||
API-Schlüssel bitte ausschließlich mit **Handelsrecht** anlegen — Auszahlungen niemals
|
||
erlauben — und wenn die Börse es anbietet auf die Server-IP beschränken. Zuerst mit
|
||
`sandbox: true` gegen das Testnet fahren.
|
||
|
||
---
|
||
|
||
## Wie der Bot lernt
|
||
|
||
Die Strategie besteht aus zwei Schichten:
|
||
|
||
**1. Regelwerk** (`strategy.rules`) — erzeugt Kandidaten für Einstiege: EMA-Kreuzung nach
|
||
oben mit RSI- und Trendfilter, dazu Rücksetzer-Einstiege bei überverkauftem RSI im
|
||
Aufwärtstrend. Ausstiege über EMA-Kreuzung nach unten oder nachlassendes Momentum, dazu
|
||
immer ATR-basierter Stop und Kursziel.
|
||
|
||
**2. Lernmodell** (`strategy.learner`) — eine online trainierte logistische Regression über
|
||
18 skalenfreie Merkmale (EMA-Abstände, RSI und dessen Steigung, MACD-Histogramm, ATR in
|
||
Prozent, Volatilitätsverhältnis, Momentum, Donchian-Lage, Volumen-z-Score, Kerzenform,
|
||
Tageszeit). Sie schätzt für jeden Kandidaten die Gewinnwahrscheinlichkeit und lässt nur
|
||
Signale oberhalb von `entry_threshold` durch.
|
||
|
||
Trainiert wird aus drei Quellen:
|
||
|
||
| Quelle | Gewicht | Wofür |
|
||
|-----------------------|---------|--------------------------------------------------------------|
|
||
| Reale Trade-Ergebnisse| 3,0 | Das eigentliche Ziel: hat sich der Trade gelohnt? |
|
||
| Signal-Shadow-Labels | 1,0 | **Jeder** Kandidat wird nachträglich bewertet — auch abgelehnte |
|
||
| Hintergrund-Stichproben | 0,5 | Regelmäßige Marktzustände, damit genug Daten zusammenkommen |
|
||
|
||
Gelabelt wird nach der Triple-Barrier-Methode: Ein Signal gilt als Treffer, wenn der Kurs
|
||
innerhalb von `label_horizon_bars` das Ziel (`label_target_bps`) erreicht, ohne vorher um
|
||
denselben Betrag zu fallen. Läuft das Fenster ohne Berührung ab, entscheidet der Schlusskurs.
|
||
|
||
Zwei Details sind dabei wichtig:
|
||
|
||
- **Off-Policy-Lernen** — auch abgelehnte Signale werden gelabelt. Der Bot lernt also aus
|
||
Trades, die er *nicht* gemacht hat, und kann eine zu strenge Schwelle selbst korrigieren.
|
||
- **Exploration** — `exploration_rate` (Standard 5 %) handelt gelegentlich bewusst gegen das
|
||
Modell. Ohne das würde es seine eigenen Vorurteile nie widerlegen.
|
||
|
||
**Kaltstart.** Ein frisches Modell würde bei 5-Minuten-Kerzen Tage brauchen, um die
|
||
Aufwärmphase zu durchlaufen. Deshalb lernt der Bot beim ersten Start automatisch aus
|
||
`bootstrap_bars` historischen Kerzen vor — in der Praxis rund 3 Sekunden statt einer Woche:
|
||
|
||
```
|
||
Modell ist untrainiert – lerne aus bis zu 3000 historischen Kerzen vor …
|
||
Vorlernen abgeschlossen: 662 neue Beobachtungen (gesamt 662), Modell einsatzbereit
|
||
```
|
||
|
||
Nachtrainieren lässt sich jederzeit — per Knopf im Dashboard oder über
|
||
`trademind backtest --save-model`, siehe [Training aus dem Dashboard anstoßen](#training-aus-dem-dashboard-anstoßen).
|
||
|
||
Solange das Modell nicht warm ist (`warmup_samples`), entscheidet allein das Regelwerk.
|
||
Die Qualität lässt sich im Status unter `strategy.learner.online_accuracy` verfolgen — das ist
|
||
eine *prequentielle* Messung: erst vorhersagen, dann lernen, also keine Selbstbewertung auf
|
||
bereits gesehenen Daten.
|
||
|
||
Im Live-Modus lässt sich das Weiterlernen mit `freeze_in_live: true` einfrieren, wenn ein
|
||
im Paper-Betrieb gereiftes Modell unverändert bleiben soll.
|
||
|
||
---
|
||
|
||
## Terminmarktdaten als Zusatzmerkmale
|
||
|
||
Optional fließen **Funding Rate** und **Open Interest** vom zugehörigen Perpetual
|
||
(`BTC/USDT` → `BTC/USDT:USDT`) als vier weitere Merkmale ein. Gehandelt wird weiterhin Spot.
|
||
|
||
```yaml
|
||
strategy:
|
||
derivatives:
|
||
enabled: true
|
||
```
|
||
|
||
Die Zuordnung ist lookahead-frei: Für jede Kerze gilt nur der Wert, der zu diesem Zeitpunkt
|
||
**bereits veröffentlicht** war. Fällt eine Quelle aus oder deckt sie weniger als
|
||
`min_coverage` der Kerzen ab, bleiben die Spalten neutral — die Modelldimension ändert sich
|
||
nicht, der Bot läuft weiter.
|
||
|
||
> **Gemessenes Ergebnis: kein Nutzen.** Zwei Backtests mit identischen Kerzen und Seed:
|
||
>
|
||
> | Lauf | Rendite | Trefferquote | Modell-Accuracy | LogLoss |
|
||
> |---|---|---|---|---|
|
||
> | 5m, 20 Tage, ohne | −1,79 % | 18,9 % | 51,2 % | 0,7262 |
|
||
> | 5m, 20 Tage, mit | −1,90 % | 21,4 % | 50,4 % | 0,7445 |
|
||
> | 15m, 28 Tage, ohne | −2,22 % | 20,7 % | 46,8 % | 0,7856 |
|
||
> | 15m, 28 Tage, mit | −2,67 % | 18,8 % | 48,3 % | 0,7831 |
|
||
>
|
||
> Die Datenanbindung funktioniert (100 % Abdeckung in beiden Läufen), aber das Modell
|
||
> gewichtet die neuen Merkmale schwach (0,01–0,09 gegenüber 0,39 für `ema_spread`). Deshalb
|
||
> ist die Funktion **standardmäßig aus**. Sie ist da, damit du es auf deinen Zeiträumen
|
||
> selbst prüfen kannst — nicht, weil sie sich bewährt hätte.
|
||
|
||
**Grenzen der Börsen-API** (gemessen an Binance): Open Interest reicht nur **30 Tage**
|
||
zurück, 500 Zeilen je Abruf. Funding Rate reicht über ein Jahr. Längere Backtests deshalb
|
||
mit `open_interest: false` fahren.
|
||
|
||
Ein- und Ausschalten ändert die Anzahl der Merkmale (18 ↔ 22). Ein gespeichertes Modell mit
|
||
abweichender Dimension wird verworfen und das Training beginnt von vorn — die Schalter sind
|
||
darum als neustartpflichtig markiert.
|
||
|
||
---
|
||
|
||
## Erklärungen über ein lokales Sprachmodell
|
||
|
||
Mit einer laufenden [Ollama](https://ollama.com)-Instanz erscheint im Dashboard der Bereich
|
||
**Erklärung**: Auf Knopfdruck fasst ein lokales Modell zusammen, was die Zahlen zeigen —
|
||
Merkmalsgewichte, Trefferquote, Risikolage, offene Positionen.
|
||
|
||
```yaml
|
||
llm:
|
||
enabled: true
|
||
model: llama3.2
|
||
```
|
||
|
||
**Das Modell entscheidet nichts.** Es bekommt den Zustand als Text und gibt Text zurück; es
|
||
wird nie aus dem Handels-Loop heraus aufgerufen und beeinflusst keine Order. Bewusst so:
|
||
Ein Sprachmodell je Kerze entscheiden zu lassen wäre nicht reproduzierbar, kaum backtestbar
|
||
und würde die Nachvollziehbarkeit zerstören, die das lineare Modell heute bietet.
|
||
|
||
Der System-Prompt untersagt Anlageempfehlungen und Kursprognosen. Beispielausgabe:
|
||
|
||
> Der Bot befindet sich im simulierten Modus und hat bislang keine Handelsaktivität
|
||
> entfaltet […] Da die Trefferquote knapp über dem Zufallswert liegt und keine realen
|
||
> Handelsdaten vorliegen, ist die Belastbarkeit des Modells unter realen Marktbedingungen
|
||
> noch nicht nachgewiesen.
|
||
|
||
> **Reasoning-Modelle.** `qwen3`, `deepseek-r1` und Verwandte legen ihre Denkschritte in ein
|
||
> eigenes Antwortfeld und verbrauchen dafür das gesamte Token-Budget — die eigentliche
|
||
> Antwort bleibt leer. `llm.think: false` (Standard) schaltet das ab; getestet mit
|
||
> `qwen3.8:27b`, das damit in rund 20 Sekunden antwortet. Reicht das Budget trotzdem nicht,
|
||
> sagt die Fehlermeldung genau das und nennt den Schalter.
|
||
|
||
**Nicht enthalten:** News- oder Google-Trends-Sentiment als Merkmal. Der Grund ist nicht
|
||
technischer Natur — es fehlt eine Quelle mit Point-in-Time-Historie. Ohne die lässt sich ein
|
||
solches Merkmal nicht backtesten, und ein unvalidiertes Merkmal in ein Modell zu geben, das
|
||
bei 51 % Accuracy steht, macht es eher schlechter. Google Trends kommt zusätzlich mit
|
||
täglicher Auflösung und pro Abfrage neu skalierten Werten — für 5-Minuten-Kerzen unbrauchbar.
|
||
|
||
---
|
||
|
||
## Risikomanagement
|
||
|
||
Vor jeder Order greifen mehrere unabhängige Grenzen:
|
||
|
||
- `max_position_pct` — Anteil der Equity je Position
|
||
- `max_total_exposure_pct` — Summe aller Positionen
|
||
- `max_open_positions` — parallele Positionen
|
||
- ATR-basierter Stop-Loss und Take-Profit, optional nachziehender Stop
|
||
- `cooldown_bars_after_exit` — Pause pro Symbol nach einem Ausstieg
|
||
- `min_notional` sowie die Mindestgrößen und Rundungsschritte der Börse
|
||
|
||
Dazu zwei Notbremsen:
|
||
|
||
- **`max_daily_loss_pct`** — Handel pausiert bis zum nächsten UTC-Tag.
|
||
- **`max_drawdown_pct`** — dauerhafter Stopp bis zum Neustart; offene Positionen werden
|
||
geschlossen.
|
||
|
||
Ausstiege werden nie durch die Liquiditätsgrenze gedrosselt: Das Risikomanagement muss
|
||
jederzeit vollständig aus einer Position herauskommen.
|
||
|
||
---
|
||
|
||
## Simulation
|
||
|
||
Der Paper-Broker bildet die Kosten nach, an denen Strategien in der Praxis scheitern:
|
||
|
||
```yaml
|
||
paper:
|
||
fee_rate: 0.001 # 0,1 % je Seite
|
||
slippage_bps: 5 # Ausführung 5 bps schlechter als der Referenzkurs
|
||
max_volume_participation: 0.1 # höchstens 10 % des Kerzenvolumens
|
||
```
|
||
|
||
Zusätzlich werden Mengen auf die Präzision der Börse abgerundet, Käufe auf das verfügbare
|
||
Guthaben begrenzt und Verkäufe auf den tatsächlichen Bestand. Ein Round-Trip ohne
|
||
Kursbewegung kostet damit realistisch ~0,3 %.
|
||
|
||
---
|
||
|
||
## Kommandos
|
||
|
||
```bash
|
||
trademind run --config config/config.yaml # Dauerbetrieb (paper oder live)
|
||
trademind backtest --config config/config.yaml # historischer Durchlauf
|
||
trademind validate --config config/config.yaml # Konfiguration + Börsenverbindung prüfen
|
||
trademind report --config config/config.yaml # Ergebnisse aus der Datenbank
|
||
trademind exchanges --search kraken # verfügbare Börsen
|
||
```
|
||
|
||
Im Container davor `podman exec -it trademind` setzen, z. B.:
|
||
|
||
```bash
|
||
podman exec -it trademind trademind report --config /config/config.yaml
|
||
```
|
||
|
||
### Backtest
|
||
|
||
```bash
|
||
trademind backtest -c config/config.yaml --bars 20000 --fresh-model --save-model --out-dir out/
|
||
trademind backtest -c config/config.yaml --start 2025-01-01T00:00:00Z --end 2025-06-30T23:59:59Z
|
||
trademind backtest -c config/config.yaml --csv-dir data/csv --json
|
||
```
|
||
|
||
| Option | Wirkung |
|
||
|------------------|--------------------------------------------------------------------|
|
||
| `--fresh-model` | untrainiertes Modell — sonst wird auf dem gespeicherten aufgesetzt |
|
||
| `--save-model` | Ergebnis nach `learner.model_path` schreiben (Vortraining) |
|
||
| `--out-dir` | `trades.csv`, `equity.csv` und `report.json` ablegen |
|
||
| `--csv-dir` | eigene OHLCV-Daten statt Börsenabruf |
|
||
| `--seed` | reproduzierbare Läufe (Exploration ist zufällig) |
|
||
|
||
CSV-Format: `timestamp,open,high,low,close,volume`, eine Datei je Symbol
|
||
(`BTC_USDT.csv`). Der Zeitstempel darf in Sekunden, Millisekunden oder ISO-8601 stehen.
|
||
|
||
Der Backtest ist ein Walk-Forward-Lauf: Das Modell trainiert währenddessen ganz normal
|
||
weiter, es gibt also keine getrennte Trainings- und Testphase.
|
||
|
||
Ein Beispiellauf über 20 Tage BTC/USDT und ETH/USDT (5m, frisches Modell):
|
||
|
||
```
|
||
Endkapital 9818.92 USDT Trades 29 (4/25)
|
||
Gesamtrendite -1.81 % Trefferquote 13.8 %
|
||
Max. Drawdown 2.23 % Profit-Faktor 0.17
|
||
Buy & Hold BTC +23.81 % Gebühren 114.84 USDT
|
||
```
|
||
|
||
Genau dafür ist der Backtest da: Die Standardparameter schlagen in einem starken
|
||
Aufwärtstrend kein Buy & Hold, und der Bot sagt das offen. Kürzere Zeiträume, andere
|
||
Timeframes und Parameter gehören ausprobiert, bevor auch nur ein Paper-Euro fließt.
|
||
|
||
---
|
||
|
||
## Monitoring
|
||
|
||
| Endpunkt | Inhalt |
|
||
|--------------|-------------------------------------------------------------|
|
||
| `/` | Dashboard: Equity, Positionen, Trades, Modellzustand, Training |
|
||
| `/health` | Liveness — nutzt der Container-Healthcheck |
|
||
| `/ready` | Readiness (503, solange der Bot nicht sauber läuft) |
|
||
| `/status` | vollständiger Zustand als JSON |
|
||
| `/positions` | offene Positionen |
|
||
| `/trades` | letzte Trades (`?limit=100`) |
|
||
| `/metrics` | Prometheus-Textformat |
|
||
|
||
```bash
|
||
curl -s localhost:8080/status | jq '.portfolio, .strategy.learner'
|
||
curl -s localhost:8080/metrics | grep trademind_portfolio
|
||
```
|
||
|
||
Optionale Benachrichtigungen über `notifications.webhook_url` — ein Payload bedient sowohl
|
||
Slack als auch Discord.
|
||
|
||
---
|
||
|
||
## Handel und Training aus dem Dashboard steuern
|
||
|
||
### Automatisierter Handel
|
||
|
||
Der Abschnitt **Automatisierter Handel** zeigt den Zustand und schaltet ihn um — in beiden
|
||
Modi, simuliert wie echt. Standardmäßig startet der Bot handelnd (`trading.autostart: true`);
|
||
mit `autostart: false` sammelt er zunächst nur Daten und wartet auf die Freigabe.
|
||
|
||
Pausiert bedeutet **nur: keine neuen Einstiege**. Alles andere läuft weiter:
|
||
|
||
| Auch pausiert | Pausiert ausgesetzt |
|
||
|---|---|
|
||
| Marktdaten werden abgerufen | Neue Positionen eröffnen |
|
||
| Signale werden ausgewertet und gelabelt | |
|
||
| Das Modell trainiert weiter | |
|
||
| Offene Positionen bleiben unter Stop-/Ziel-Überwachung | |
|
||
|
||
Der Bot lernt also durchgehend weiter, auch wenn er nicht handelt. Auf identischen Daten:
|
||
|
||
```
|
||
Handel aktiv Trades=9 Beobachtungen=99 Shadow=90 Trade-Labels=9 nur-gelernte Signale=0
|
||
Handel pausiert Trades=0 Beobachtungen=90 Shadow=90 Trade-Labels=0 nur-gelernte Signale=9
|
||
```
|
||
|
||
Der Unterschied sind genau die neun Trade-Labels, die ohne Handel nicht entstehen können —
|
||
die Shadow-Labels aus dem Marktgeschehen laufen unverändert weiter.
|
||
|
||
> **Live-Modus.** Das Starten verlangt zusätzlich die Bestätigung `START_LIVE_TRADING`
|
||
> (das Dashboard fragt sie ab, per HTTP als `"confirm"` im Body). **Pausieren** geht immer
|
||
> ohne Bestätigung — im Zweifel muss man schnell anhalten können. Abschalten lässt sich die
|
||
> Rückfrage mit `trading.require_confirmation_for_live: false`.
|
||
>
|
||
> Die Kombination *Live-Modus + Port nicht nur lokal + kein `control_token`* wird **nicht
|
||
> bedient**: Der Bot schaltet die Steuerung dann beim Start ab und sagt das im Log. Sonst
|
||
> könnte jeder im Netz echten Handel starten. Mit Token oder auf `127.0.0.1` steht sie
|
||
> wieder zur Verfügung.
|
||
|
||
### Konfiguration
|
||
|
||
Der Abschnitt **Konfiguration** zeigt alle 76 Felder, nach Bereichen gruppiert, mit
|
||
Kurzbeschreibung und den Grenzen aus dem Schema. 70 davon sind direkt änderbar.
|
||
|
||
Weil `config.yaml` im Container read-only eingehängt ist, landen Änderungen als Overlay in
|
||
`/data/config.overrides.yaml` und werden beim Start über die Basiskonfiguration gelegt.
|
||
Die Rangfolge, von schwach nach stark:
|
||
|
||
1. `config.yaml`
|
||
2. `${ENV}`-Platzhalter darin
|
||
3. `TRADEMIND__ABSCHNITT__SCHLUESSEL`-Umgebungsvariablen
|
||
4. Overlay aus dem Dashboard
|
||
|
||
Gespeichert wird nur, was vom Basiswert abweicht — stellst du ein Feld auf seinen
|
||
Ausgangswert zurück, verschwindet es wieder aus dem Overlay und spätere Änderungen an
|
||
`config.yaml` schlagen dort wieder durch. Einzelne Felder oder alles auf einmal lassen sich
|
||
über die Knöpfe zurücksetzen; die Overlay-Datei zu löschen hat denselben Effekt.
|
||
|
||
Jedes Feld ist markiert, wenn es **einen Neustart braucht** (31 Felder — Börsenclient,
|
||
Symbole, Timeframe, Datenbank, Socket und alles andere, was nur beim Aufbau ausgewertet
|
||
wird). Der Rest greift sofort: Risikoregeln, Strategie-Parameter, Lernmodell, Gebühren und
|
||
Slippage der Simulation, Abfrageintervall, Benachrichtigungen, Log-Level.
|
||
|
||
Ungültige Eingaben werden abgelehnt, bevor irgendetwas übernommen wird — inklusive
|
||
feldübergreifender Regeln:
|
||
|
||
```
|
||
HTTP 400 — Ungültige Werte – strategy.rules: strategy.rules.fast_ema muss kleiner als slow_ema sein
|
||
```
|
||
|
||
> **Drei Felder sind bewusst ausgenommen** und bleiben der Konfigurationsdatei
|
||
> beziehungsweise der Umgebung vorbehalten:
|
||
>
|
||
> | Feld | Grund |
|
||
> |---|---|
|
||
> | `exchange.api_key` / `api_secret` / `password` / `uid` | Werden nie ausgeliefert (maskiert als „gesetzt"/„nicht gesetzt") und nicht entgegengenommen. Sonst könnte jeder mit Zugriff auf den Port die Börsenschlüssel auslesen oder austauschen. |
|
||
> | `mode: live` | Wechsel zwischen `paper` und `backtest` geht; auf Echtgeld umstellen nicht. |
|
||
> | `live_confirmation` | Sonst wäre die Live-Freigabe aus dem Netz setzbar. |
|
||
>
|
||
> Zusammen verhindert das, dass jemand über das Dashboard schrittweise auf Echtgeldhandel
|
||
> umstellt.
|
||
|
||
Per HTTP:
|
||
|
||
```bash
|
||
curl -X POST localhost:8080/control/config -H 'Content-Type: application/json' -H "X-TradeMind-Token: $TOKEN" -d '{"risk.max_open_positions": 5, "strategy.learner.entry_threshold": 0.6}'
|
||
```
|
||
|
||
### Training
|
||
|
||
Im Abschnitt **Training** stehen zwei Bedienelemente:
|
||
|
||
**Historisch nachtrainieren.** Anzahl Kerzen wählen (500 – 50 000 je Symbol), Knopf drücken.
|
||
Der Bot lädt die Historie, läuft sie mit derselben Logik wie ein Backtest durch — **ohne zu
|
||
handeln** — und speichert das Modell danach. Der Fortschritt erscheint direkt darunter:
|
||
|
||
```
|
||
Fertig in 4.3s: +1099 Beobachtungen (gesamt 1753), Modell einsatzbereit, gespeichert
|
||
```
|
||
|
||
Ein zweiter Start wird abgelehnt, solange einer läuft. Handelsdurchlauf und Nachtraining
|
||
schließen sich gegenseitig aus, der Live-Betrieb pausiert also für die paar Sekunden.
|
||
Die Labels des laufenden Betriebs bleiben davon unberührt.
|
||
|
||
**Kontinuierliches Lernen.** Schaltet das Online-Lernen im laufenden Betrieb an und aus.
|
||
Ausgeschaltet handelt der Bot weiter, verändert das Modell aber nicht mehr — praktisch, um
|
||
einen erreichten Stand einzufrieren, ohne den Bot anzuhalten.
|
||
|
||
Dieselben Aktionen per HTTP:
|
||
|
||
```bash
|
||
curl -X POST localhost:8080/control/train/history -H 'Content-Type: application/json' -H "X-TradeMind-Token: $TOKEN" -d '{"bars": 5000}'
|
||
```
|
||
|
||
| Endpunkt | Methode | Wirkung |
|
||
|--------------------------------|---------|--------------------------------------------|
|
||
| `/control/explain` | POST | Erklärung des aktuellen Zustands (braucht `llm.enabled`) |
|
||
| `/control/config` | GET | Alle Felder mit Wert, Typ, Grenzen und Markierungen |
|
||
| `/control/config` | POST | `{"risk.max_open_positions": 5}` — ändern und sichern |
|
||
| `/control/config/reset` | POST | `{}` oder `{"paths": [...]}` — Overlay verwerfen |
|
||
| `/control/trading` | GET | Zustand des automatisierten Handels |
|
||
| `/control/trading` | POST | `{"enabled": true}` — Handel starten/pausieren (live zusätzlich `"confirm"`) |
|
||
| `/control/training` | GET | Zustand des letzten/laufenden Trainings |
|
||
| `/control/train/history` | POST | `{"bars": 5000}` — Nachtraining anstoßen |
|
||
| `/control/train/live` | POST | `{"enabled": false}` — Lernen ein/aus |
|
||
|
||
> **Absicherung.** Anders als die lesenden Endpunkte verändern diese den Zustand des Bots.
|
||
> Setze deshalb `server.control_token` (bzw. `TRADEMIND_CONTROL_TOKEN`), sobald der Port
|
||
> nicht nur lokal erreichbar ist — die Steuerbefehle verlangen ihn dann im Header
|
||
> `X-TradeMind-Token`, während `/status` und Co. offen bleiben. Ohne Token warnt der Bot
|
||
> beim Start. Komplett abschalten lässt sich die Steuerung mit `server.enable_control: false`;
|
||
> die Routen sind dann nicht vorhanden (404).
|
||
>
|
||
> ```bash
|
||
> openssl rand -hex 24
|
||
> ```
|
||
|
||
---
|
||
|
||
## Aufbau
|
||
|
||
```
|
||
src/trademind/
|
||
├── cli.py Kommandozeile
|
||
├── app.py Zusammenbau aus der Konfiguration
|
||
├── config.py Konfigurationsmodell (pydantic) + ${ENV}-Auflösung
|
||
├── engine.py Handelsschleife und Bar-Verarbeitung
|
||
├── backtest.py Walk-Forward-Durchlauf und Auswertung
|
||
├── strategy.py Regelwerk und lernende Strategie
|
||
├── learner.py Online-Logistikregression, Replay-Buffer, Persistenz
|
||
├── features.py Merkmalsvektoren (vektorisiert)
|
||
├── indicators.py EMA, RSI, ATR, MACD, Bollinger, Donchian, ROC
|
||
├── risk.py Positionsgröße, Stops, Notbremsen
|
||
├── portfolio.py Positionen, Equity, Kennzahlen
|
||
├── broker.py Paper- und Live-Ausführung
|
||
├── exchange.py ccxt-Client und Börsen-Metadaten
|
||
├── data.py Marktdaten: live, CSV, Replay
|
||
├── storage.py SQLite-Persistenz
|
||
├── server.py HTTP-Status, Metriken, Dashboard
|
||
└── notify.py Webhook-Benachrichtigungen
|
||
```
|
||
|
||
Die Bar-Verarbeitung in `engine.process_bar` ist für alle drei Modi identisch — ausgetauscht
|
||
werden nur Datenquelle und Broker. Was im Backtest passiert, passiert live genauso.
|
||
|
||
### Entwicklung
|
||
|
||
```bash
|
||
pip install -e ".[dev]"
|
||
pytest -q # 128 Tests
|
||
ruff check .
|
||
```
|
||
|
||
---
|
||
|
||
## Bekannte Grenzen
|
||
|
||
- **Nur Long, nur Spot.** Keine Short-Positionen, keine Hebel, keine Futures.
|
||
- **Bar-getaktet.** Entscheidungen fallen bei Kerzenschluss, nicht tick-genau. Bei `1m` ist
|
||
die Latenz zwischen Signal und Ausführung spürbar.
|
||
- **Kein Order-Abgleich im Live-Modus.** Der Bot führt seine Positionsbuchhaltung intern;
|
||
manuelle Trades auf demselben Konto bringen sie durcheinander. Ein eigenes (Sub-)Konto
|
||
verwenden.
|
||
- **Lineares Modell.** Bewusst so gewählt: nachvollziehbar (`/status` zeigt alle Gewichte),
|
||
robust bei wenig Daten, kein Overfitting-Zoo. Es findet keine nichtlinearen Muster.
|
||
- **Marktrisiko bleibt.** Backtests sagen wenig über die Zukunft, und ein lernender Filter
|
||
macht aus einer schwachen Strategie keine starke.
|
||
|
||
---
|
||
|
||
## Lizenz
|
||
|
||
MIT
|