Type/to search
2
Follow
490
Followers
Спусковой крючок: что это за попытка — вписать «новости» в свечной график?
Discussions
Created 2026-06-12 09:56:54  Updated 2026-06-16 14:26:06
 0
 159

img

1. Начало: K-линии, нарисованные рукой Трампа

Актуальность новостей не вызывает сомнений. После сообщения об авиаударе США по Ирану цены на нефть резко взлетели за короткое время. В этом процессе заявления Трампа, Ирана и других факторов постоянно переплетались и взаимно усиливали друг друга, толкая рынок к новым уровням.

img

Мы часто шутим, что «Трамп рисует K-линии своей рукой» – зачастую резкие колебания цен возникают не из-за изменения технических индикаторов, а из-за одного твита, выступления или политического заявления. Технический анализ говорит вам «где вы сейчас», но именно новости часто являются ключевым фактором, объясняющим «почему вы здесь и куда можно двинуться дальше».

img

Важность новостей неоспорима, но проблема очевидна: человек не может 24 часа в сутки следить за графиком и новостями, а в потоке информации сложно сразу уловить ту новость, которая действительно вызовет движение рынка. Поэтому возникла простая идея – можно ли «нанести» новости прямо на K-линии, чтобы цена и сообщения отображались одновременно на одном графике? Хотя бы чтобы решить проблему «видимости».

img

2. Выбор источника новостей: подключение Jin10 через MCP

Чтобы подключить новости, первый шаг – найти источник с достаточно быстрым обновлением и относительно стандартной структурой. В этот раз мы выбрали Jin10 Data (金十数据) и подключаемся через MCP (Model Context Protocol), используя два типа интерфейсов: list_flash (мгновенные новости) и list_news (новости).

Здесь не будем тратить много времени на описание самого Jin10 – это лишь наш текущий выбор. Идея и конкретный источник новостей не связаны: если какой-то источник может предоставлять заголовки/текст с временными метками и вызываться стандартным способом через MCP, его можно заменить. Важно именно проектирование этого уровня, а не привязка к конкретному инструменту.

Подключение MCP и управление сессией – это «низкоуровневая» , но критически важная часть системы:

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)"))

После установки сессии загрузка новостей сводится к двум вызовам инструментов, после чего выполняется унификация формата и удаление дубликатов:

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

Имена полей, возвращаемых разными источниками новостей, сильно различаются (title/content/introductiontime/ts/created_at……), поэтому мы добавили промежуточный слой _extract_items + _normalize, который приводит все форматы к единой структуре {ts, time, title, source, full_text}. После этого логика отрисовки графиков и фильтрации уже не зависит от того, из какого интерфейса пришли данные.

Примечание: Для использования требуется получить API-ключ MCP.

img

3. Основная идея: «привязать» новости к K-линиям

Теперь самое интересное – поместить новости и K-линии на один график.

img

Мы добавили вторую серию на график, тип flags, которая накладывается на серию K-линий и служит «слоем меток новостей»:

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()

При каждом обновлении сначала инкрементально обновляются данные свечных графиков, затем по ключевым словам фильтруются «важные» новости и привязываются по времени к соответствующим барам свечного графика:

python
def draw_chart(records): global _last_bar_time, _last_news_hash, _flagged_news_ts if not _chart or not records: return # Проверяем, изменились ли новости; если да, сбрасываем график и перерисовываем 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() # series 0: свечной график, инкрементальное добавление 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) # series 1: флаги новостей по ключевым словам, привязка ко времени бара свечного графика 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)

Результат: на графике каждый раз, когда появляется метка 📰, при наведении мыши отображается соответствующий заголовок новости, причём её положение соответствует бару свечного графика, на момент которого произошла эта новость. Точки разворота цен и моменты новостей впервые оказываются на одном экране в наглядном виде — больше не нужно переключаться между двумя окнами, чтобы понять, «из-за чего произошёл этот участок».

NEWS_KEYWORD поддерживает разделение нескольких ключевых слов через | (например, "Иран|повышение ставки|Nonfarm|тарифы"); система в первую очередь помечает на графике новости, содержащие ключевые слова, чтобы избежать засорения графика нерелевантными сообщениями.

4. Панель состояния: котировки, позиции, новости — всё на одном экране

Помимо графика, мы также создали набор таблиц состояния, выводимых через LogStatus, включающий текущие котировки, капитал и прибыль/убыток счёта, текущие позиции, новости, содержащие ключевые слова, а также последнюю полную ленту новостей:

python
def make_status(symbol, ticker, positions, equity): # 4. Новости по ключевым словам (отображаем найденные ключевые слова, а не источник) 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 = [["-", "-", "Нет новостей по ключевым словам"]]

Плюс простой интерфейс ручных команд — открыть длинную, открыть короткую, закрыть длинную, закрыть короткую, закрыть все одним кликом, изменить количество ордера — всё принимается через GetCommand():

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))

В целом, FUSE по сути представляет собой панель для наблюдения за рынком, объединяющую "интеграцию информации + ручное выполнение": она старается разместить цены, новости, позиции и состояние счета на одном экране. Решение полностью остается за человеком — она не делает выводы за вас, но помогает не упустить важные детали при принятии решений.

V. Ограничения: человек – по-прежнему самая большая переменная

Ограничения этой версии вполне очевидны, и мы не собираемся их скрывать.

Первое. Соответствие между новостями и ценами носит "грубый" характер – новости просто привязываются по временной метке к соответствующей свече на графике, без анализа содержания. Является ли конкретная новость позитивной или негативной, вызовет ли она движение рынка – полностью определяется человеком.

Второе. Фильтрация по ключевым словам – это довольно примитивное решение. Совпадение с ключевым словом не означает, что новость действительно важна, а отсутствие совпадения не означает неважности. В этом случае личное восприятие, опыт и даже текущее настроение могут играть большую роль – один и тот же график с одной и той же новостью может привести к совершенно разным выводам у разных людей.

Третье. Весь процесс по-прежнему строится по схеме "человек в контуре", скорость реакции ограничена человеческой реакцией, а в большинстве случаев рынок реагирует на новости в течение минут или даже секунд.

Если вам интересно это направление, в будущем мы попробуем создать автоматизированную версию на основе большой языковой модели, которая будет выполнять предварительный анализ новостей и оценку их значимости, выступая в качестве помощника или даже замены для принятия решений человеком. Если интересно – следите за обновлениями.

Исходный код стратегии: Система "Запал" для торговли по новостям в реальном времени

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