Type/to search
2
Follow
490
Followers
Zündschnur: Was für ein Versuch ist es, „Nachrichten“ in einen Kerzenchart einzuzeichnen?
Discussions
Created 2026-06-12 09:56:54  Updated 2026-06-16 14:26:06
 0
 158

img

I. Der Ausgangspunkt: Trump zeichnet Candlesticks von Hand

Die Aktualität von Nachrichten steht außer Frage. Nach der Meldung über den US-Luftangriff auf den Iran schoss der Ölpreis innerhalb kurzer Zeit in die Höhe. Dabei wirkten Faktoren wie die Stellungnahmen Trumps und des Irans ständig aufeinander ein und verstärkten sich gegenseitig, wodurch die Kurse immer wieder in neue Bereiche getrieben wurden.

img

Wir scherzen oft: „Trump zeichnet Candlesticks von Hand" – viele heftige Kursbewegungen entstehen gar nicht durch technische Indikatoren, sondern werden direkt durch einen Tweet, eine Rede oder eine politische Aussage ausgelöst. Die technische Analyse sagt dir, „wo du jetzt bist", aber die Nachrichten sind oft der entscheidende Faktor für die Frage: „Warum sind wir hier, und wohin könnte es als Nächstes gehen?"

img

Die Bedeutung von Nachrichten ist unbestritten, aber das Problem ist real: Ein Mensch kann nicht rund um die Uhr sowohl die Kurse als auch die Nachrichten verfolgen und noch viel schwerer in der Informationsflut als Erster diejenige Meldung erfassen, die wirklich einen Kursausschlag auslöst. Daher kommt ein schlichter Gedanke auf: Könnte man die Nachrichten direkt auf den Candlestick-Chart „zeichnen", sodass Kurse und Meldungen in derselben Ansicht gleichzeitig erscheinen? Zumindest soll zunächst das Problem des „Sehens" gelöst werden.

img

II. Wahl der Nachrichtenquelle: Anbindung von Jin10 über MCP

Um Nachrichten anzubinden, ist der erste Schritt eine Quelle zu finden, die ausreichend schnell aktualisiert wird und eine relativ standardisierte Struktur aufweist. Dieses Mal verwenden wir Jin10-Daten (金十数据) und binden sie über das MCP (Model Context Protocol) an, wobei wir die beiden Schnittstellen list_flash (Eilmeldungen) und list_news (Nachrichten) aufrufen.

Ich möchte hier nicht zu viel über Jin10 selbst verlieren – es ist nur eine derzeit verwendete Option; der Ansatz und die konkrete Nachrichtenquelle sind entkoppelt. Solange eine Quelle titel/Text mit Zeitstempel liefert und auf ähnliche Standardweise wie MCP aufgerufen werden kann, kann sie ausgetauscht werden. Der Schwerpunkt liegt auf dem Design dieser Anbindungsebene, nicht auf der Bindung an ein bestimmtes Tool.

Die MCP-Verbindung und Sitzungsverwaltung sind ein eher „unterer" aber dennoch wichtiger Teil dieses Systems:

python
def _mcp_post(payload, is_notification=False): global _mcp_session_id, _mcp_req_id if not is_notification: _mcp_req_id += 1 payload["id"] = _mcp_req_id body = json.dumps(payload, ensure_ascii=False).encode() req = urllib.request.Request( JIN10_MCP_URL, data=body, headers=_mcp_headers(), method="POST" ) try: with urllib.request.urlopen(req, timeout=30) as resp: sid = resp.headers.get("Mcp-Session-Id") if sid: _mcp_session_id = sid if resp.status == 202: return {} text = resp.read().decode("utf-8", errors="replace") except urllib.error.HTTPError as e: raise RuntimeError("HTTP %d: %s" % (e.code, e.read().decode()[:400])) except urllib.error.URLError as e: raise RuntimeError("Network: " + str(e)) return _mcp_parse(text) def mcp_init(): global _mcp_ready mcp_rpc("initialize", { "protocolVersion": "2025-11-25", "capabilities": {}, "clientInfo": {"name": "fuse-fmz", "version": "1.0"}, }) mcp_notify("notifications/initialized") _mcp_ready = True Log("MCP ready session_id=" + (_mcp_session_id or "(none)"))

Nachdem die Sitzung eingerichtet ist, erfolgt das Abrufen der Nachrichten über zwei Tool-Aufrufe, gefolgt von einer einheitlichen Formatnormalisierung und Deduplizierung:

python
def refresh_news(): global _cached_news, _last_news_at, _mcp_ready if not JIN10_MCP_TOKEN: return now = int(time.time()) if now - _last_news_at < NEWS_REFRESH_SEC and _cached_news: return _last_news_at = now try: if not _mcp_ready: mcp_init() flash_raw = mcp_call_tool("list_flash") news_raw = mcp_call_tool("list_news") combined = ( _normalize(_extract_items(flash_raw), "flash") + _normalize(_extract_items(news_raw), "news") ) combined.sort(key=lambda x: x["ts"], reverse=True) _cached_news = combined[:80] Log("News updated: %d items" % len(_cached_news)) except Exception as e: Log("News refresh failed: " + str(e)) _mcp_ready = False

Die von verschiedenen Nachrichtenquellen zurückgegebenen Feldnamen sind sehr unterschiedlich (title/content/introduction, time/ts/created_at …) . Daher wird eine Zwischenschicht _extract_items + _normalize eingefügt, die alle Formate in die einheitliche Struktur {ts, time, title, source, full_text} umwandelt. Die spätere Chart- und Filterlogik muss sich dann nicht mehr darum kümmern, von welcher Schnittstelle die Daten stammen.

Hinweis: Für die Nutzung muss die MCP-API beantragt werden.

img

III. Kernfunktion: Nachrichten auf Candlesticks „wachsen" lassen

Kommen wir zum wirklich interessanten Teil dieses Tools – das Zusammenbringen von Nachrichten und Candlesticks in einem Chart.

img

Wir haben eine zweite Reihe zum Chart hinzugefügt, vom Typ flags, die an der Candlestick-Reihe hängt und als „Nachrichtenmarkierungs-Ebene" fungiert:

python
def init_chart(symbol): global _chart _chart = Chart({ "__isStock": True, "chart": {"style": {"fontFamily": "Microsoft YaHei, SimHei, Arial, sans-serif"}}, "title": {"text": "FUSE " + symbol}, "xAxis": {"type": "datetime"}, "series": [ { "id": "kline", "type": "candlestick", "name": symbol, "data": [], }, { "type": "flags", "name": "News", "onSeries": "kline", "shape": "circlepin", "color": "#F59E0B", "fillColor": "#F59E0B", "width": 16, "data": [], }, ], }) _chart.reset()

Bei jeder Aktualisierung werden zuerst die Candlestick-Daten inkrementell aktualisiert, dann werden die "wichtigen" Nachrichten anhand von Schlüsselwörtern gefiltert und zeitlich auf die entsprechenden Kerzen-Balken abgebildet:

python
def draw_chart(records): global _last_bar_time, _last_news_hash, _flagged_news_ts if not _chart or not records: return # Prüfen, ob es Neuigkeiten gibt, wenn ja, Diagramm zurücksetzen und neu zeichnen news_hash = hash(tuple(n.get("ts", 0) for n in _cached_news[:10])) news_changed = (news_hash != _last_news_hash) if news_changed: _chart.reset() _last_bar_time = 0 _last_news_hash = news_hash _flagged_news_ts = set() # Serie 0: Candlestick, inkrementelles Hinzufügen for r in records: t = r['Time'] bar = [t, r['Open'], r['High'], r['Low'], r['Close']] if t > _last_bar_time: _chart.add(0, bar) _last_bar_time = t elif t == _last_bar_time: _chart.add(0, bar, -1) # Serie 1: Flags für Schlüsselwort-Nachrichten, zeitlich auf Candlestick-Balken abgestimmt if not _cached_news: return kws = [k.strip() for k in NEWS_KEYWORD.split("|") if k.strip()] kw_news = [n for n in _cached_news if not kws or any(k in n.get("full_text", n["title"]) for k in kws)] if not kw_news: return p_ms = PERIOD_MS.get(KLINE_PERIOD, 60000) first = records[0]['Time'] last = records[-1]['Time'] by_bar = {} for n in kw_news: if not n.get("ts"): continue key = (n["ts"] // p_ms) * p_ms if key not in by_bar: by_bar[key] = n for ts, item in sorted(by_bar.items()): if not (first <= ts <= last): continue if ts in _flagged_news_ts: continue _chart.add(1, { "x": ts, "title": "📰", "text": item["title"][:100], }) _flagged_news_ts.add(ts)

Das Ergebnis: Jedes 📰-Symbol im Chart zeigt beim Darüberfahren mit der Maus den entsprechenden Nachrichtentitel an, und seine Position entspricht genau der Candlestick-Kerze zum Zeitpunkt der Nachricht. Preiswende- und Nachrichtenzeitpunkte werden erstmals auf eine intuitive Weise in einem einzigen Bild dargestellt – Sie müssen nicht mehr zwischen zwei Fenstern hin- und herschalten, um zu verstehen, "welcher Kurswechsel auf welches Ereignis zurückzuführen ist".

NEWS_KEYWORD unterstützt mehrere Schlüsselwörter, getrennt durch | (z. B. "Iran|Zinserhöhung|Arbeitsmarktbericht|Zölle"). Das System markiert vorrangig die Nachrichten, die auf die Schlüsselwörter zutreffen, um zu vermeiden, dass das Chart mit irrelevanten Meldungen überflutet wird.

4. Status-Panel: Markt, Positionen und Nachrichten auf einen Blick

Neben dem Chart haben wir eine Reihe von Statustabellen erstellt, die über LogStatus ausgegeben werden. Dazu gehören: Live-Marktdaten, Kontostand und Gewinn/Verlust, aktuelle Positionen, Nachrichten, die auf Schlüsselwörter zutreffen, sowie die aktuellsten Meldungen:

python
def make_status(symbol, ticker, positions, equity): # 4. Schlüsselwort-Nachrichten (Anzeige der gefundenen Schlüsselwörter, nicht der Quelle) kws = [k.strip() for k in NEWS_KEYWORD.split("|") if k.strip()] kw_rows = [] for item in _cached_news[:40]: t = item.get("time") or (_D(item["ts"]) if item.get("ts") else "-") title = item["title"][:90] text = item.get("full_text", item["title"]) hit_kws = [k for k in kws if k in text] if hit_kws: kw_rows.append([t, "/".join(hit_kws), title]) if not kw_rows: kw_rows = [["-", "-", "Keine Nachrichten zu den Schlüsselwörtern"]]

Dazu kommt eine einfache manuelle Befehlsschnittstelle – Long eröffnen, Short eröffnen, Long schließen, Short schließen, alle Positionen schließen, Auftragsgröße ändern – alles über GetCommand() empfangen:

python
def handle_command(symbol): global _cur_amount, _last_news_at cmd = GetCommand() if not cmd: return Log("CMD: " + cmd) parts = cmd.split(":") key = parts[0] val = parts[1] if len(parts) > 1 else "" if key == "openLong": market_order(symbol, "openLong", _cur_amount) elif key == "openShort": market_order(symbol, "openShort", _cur_amount) elif key == "closeLong": market_order(symbol, "closeLong", _cur_amount) elif key == "closeShort": market_order(symbol, "closeShort", _cur_amount) elif key == "closeAll": close_all(symbol) elif key == "amount": _cur_amount = float(val) Log("Amount updated: " + str(_cur_amount))

Insgesamt ist FUSE im Kern eine "Informationsintegration + manuelle Ausführung"-Übersichts-Tafel: Sie bringt Preise, Nachrichten, Positionen und Kontostatus möglichst auf einem Bildschirm zusammen. Die Entscheidungsfindung bleibt vollständig beim Menschen – sie trifft keine Urteile für dich, versucht aber sicherzustellen, dass du bei deinen Entscheidungen möglichst wenig übersiehst.

5. Grenzen: Der Mensch bleibt die größte Variable

Die Einschränkungen dieser Version sind recht offensichtlich, und wir möchten sie nicht unter den Teppich kehren.

Erstens ist die Zuordnung von Nachrichten und Kursen nur grobkörnig – Nachrichten werden lediglich anhand ihres Zeitstempels an die entsprechende Kerzenlinie gehängt, ohne inhaltliche Interpretation. Ob eine Nachricht bullisch oder bärisch ist und ob sie Kursbewegungen auslöst, bleibt vollständig der menschlichen Beurteilung überlassen.

Zweitens ist die Schlüsselwortfilterung selbst ein eher einfaches Verfahren. Ein Treffer bedeutet nicht zwangsläufig, dass die Nachricht wirklich wichtig ist, und ein Nicht-Treffer bedeutet nicht, dass sie unwichtig ist. Dabei können die persönliche Wahrnehmung, Erfahrung und sogar die Tagesform eine große Rolle spielen – dieselbe Nachricht, von verschiedenen Personen auf demselben Chart betrachtet, kann zu völlig unterschiedlichen Schlussfolgerungen führen.

Drittens folgt der gesamte Prozess immer noch dem "Mensch-im-Kreis"-Schema. Die Reaktionsgeschwindigkeit wird durch die menschliche Reaktionszeit begrenzt, während die Reaktion des Marktes auf Nachrichten oft innerhalb von Minuten oder sogar Sekunden erfolgt.

Wenn dieses Thema auf Interesse stößt, werden wir später versuchen, eine automatisierte Version auf Basis großer Sprachmodelle zu entwickeln, die eine erste Interpretation und Bewertung der Nachrichtenwichtigkeit übernimmt – als Unterstützung oder sogar Ersatz für menschliche Entscheidungen. Bei Interesse freuen wir uns über eure weitere Aufmerksamkeit.

Strategie-Quellcode: Echtzeit-Nachrichtenzündsystem

Comment
All comments (0)
No data
No data
  • 1
Forums
PINE Language
Get the app
iPhone Download
© 2015 - ∞ INVENTOR PTE LTD (SG)