В последнее время AI действительно набирает невероятную популярность.
За последние два года главная тема на рынке, так или иначе, вращается вокруг AI. NVIDIA, AMD, Broadcom, TSMC, Micron, Microsoft, Google, Meta, CoreWeave, Supermicro… Каждое время от времени какая-нибудь компания резко взлетает из-за отчётов о прибылях, заказов, капитальных затрат, HBM, GPU, облачных вычислений, центров обработки данных.
Что ещё интереснее — они редко растут поодиночке.
Иногда первой реагирует NVIDIA, а затем рынок начинает копать ASIC, оптические модули, серверы, жидкостное охлаждение, электроэнергию; иногда отчёт Micron превосходит ожидания, и следом средства уходят в HBM, память, полупроводниковое оборудование, передовую упаковку; а бывает, что облачные провайдеры повышают капитальные затраты, и вся цепочка AI-инфраструктуры переоценивается.
Это больше похоже на ротацию активов внутри самой темы AI. Рынок не просто покупает одну акцию, а под большим нарративом постоянно ищет новые ветви, новые объяснения и новые объекты для догоняющего роста.
Отсюда и проблема: если полагаться на ручную обработку всех этих связей, это слишком утомительно.
Один человек может знать NVIDIA, AMD, Micron, Microsoft, Google, но ему сложно постоянно отслеживать изменения в бизнесе десятков или сотен TradFi-акций, новости, отраслевую принадлежность и взаимосвязи цен. Ещё сложнее то, что многие компании не являются однопрофильными. Google — это и AI-облако, и Gemini, и рекламная платформа; Microsoft — это и облако, и Copilot, и корпоративное ПО; Broadcom — это и ASIC, и сетевые чипы, и связь с AI-дата-центрами.
Поэтому я начал пробовать: может ли большая модель помочь нам построить карту AI-акций?
Не чтобы модель прямо говорила нам, что покупать, а чтобы она делала то, что у неё получается лучше: понимать бизнес компаний, классифицировать подсектора AI, систематизировать новости, объяснять события, оценивать, на какие акции той же темы может повлиять движение одной компании. Настоящие торговые решения всё равно остаются за ценами, статистикой и управлением рисками.
Сначала поместите акции в правильную корзину
Первый шаг стратегии — не торговля, а построение карты.
На бирже сейчас доступно множество контрактов на TradFi-акции, можно напрямую получить набор похожих на NVDA, AMD, MU, MSFT, GOOGL, SMCI контрактов. Система сначала фильтрует контракты на акции, оставляя только настоящие инструменты типа EQUITY.
Здесь важная деталь: нельзя путать ETF, индексы, кредитные ETF. Такие вещи, как QQQ, SOXL, EWT, хоть и могут появляться в списке контрактов, но не являются акциями компаний. Если поместить их в корзину AI-акций, статистические связи впоследствии будут искажены.
Основная логика фильтрации выглядит примерно так:
python
def refresh_equity_universe(store, force_reanalyze):
ms = exchange.GetMarkets()
symbols = []
for key, market in ms.items():
info = market.get("Info", {}) or {}
sub_type = info.get("underlyingSubType", []) or []
underlying_type = info.get("underlyingType", "")
if ".swap" not in key:
continue
if "TradFi" not in sub_type:
continue
if underlying_type != "EQUITY":
continue
symbol = normalize_symbol(key)
store["markets"][symbol] = {
"symbol": symbol,
"contract": key,
"underlyingType": underlying_type,
"amountPrecision": market.get("AmountPrecision", 0),
"pricePrecision": market.get("PricePrecision", 2),
"ctVal": market.get("CtVal", 1) or 1,
"minQty": market.get("MinQty", 0) or 0,
}
symbols.append(symbol)
Получив пул акций, система поручает большой модели составить профиль каждой акции.
Профиль — это не просто написать «это технологическая акция». Нужно определить, к каким подсекторам AI она относится. Например, одна акция может одновременно принадлежать нескольким корзинам. Google может относиться к AI-облаку, AI-моделям и приложениям, AI-рекламной платформе; Microsoft — к облаку и корпоративным AI-приложениям; NVIDIA — к AI-вычислениям и AI-серверам; Micron — к AI-памяти.
Ключевой момент: в эпоху AI акции не имеют одного тега, они мультитеговые.
Каждая акция обладает несколькими атрибутами: к каким корзинам относится, насколько высока экспозиция, является ли она лидером или последователем в этой корзине, на какие компании может влиять и от каких может зависеть.
Я наложил строгие ограничения на структуру вывода большой модели:
python
schema = {
"symbol": symbol,
"asset_type": "stock|adr|etf|leveraged_etf|index|fund|other",
"company_name": "",
"sector": "",
"business_summary": "",
"tradable": False,
"model_confidence": 0,
"baskets": [
{
"id": "ai_cloud",
"exposure": 0.0,
"role": "leader|follower|both|observer",
"sub_theme": "",
"reason": ""
}
],
"search_names": [],
"event_keywords": [],
"negative_keywords": [],
"possible_leaders": [],
"possible_followers": [],
"brave_queries": [],
"reason": ""
}
Здесь наиболее критично поле asset_type. Только stock и adr могут попасть в корзину; ETF, кредитные ETF, индексы, фонды, товарные или региональные ETF отфильтровываются напрямую.
Кроме того, reason должен быть на китайском, имена полей должны совпадать, уровень уверенности — единый от 0 до 100. Иначе на реальной торговой странице всё будет выглядеть хаотично.
Информация о секторе не должна браться из воздуха
Одной классификации акций недостаточно.
Сектор AI меняется слишком быстро. Сегодня рынок торгует GPU, завтра может переключиться на HBM, послезавтра — на оптические модули, а через несколько дней — на жидкостное охлаждение, электроэнергию, капитальные затраты облачных провайдеров.
Если полагаться только на знания самой большой модели, они быстро устареют. Поэтому системе необходим источник информации в реальном времени.
Я подключил BraveSearch. Система периодически ищет информацию о каждом AI-секторе, а также новости о ключевых акциях внутри корзины.
Например, для корзины AI-памяти — смотрит ключевые слова Micron, Western Digital, SanDisk, HBM, DRAM, NAND; для корзины AI-оптической связи — ищет информацию о Ciena, Credo, Lumentum, AAOI, оптических модулях 800G, 1.6T.
Основной процесс получения данных по секторам выглядит так:
python
def refresh_basket_intelligence(store):
if not BRAVE_KEY:
return
if not LLM_API_KEY:
return
intel = {}
for bid, basket in store["baskets"].items():
members = sorted(
basket["members"],
key=lambda x: x.get("exposure", 0),
reverse=True
)
top_symbols = [m["symbol"] for m in members[:8]]
basket_news = fetch_basket_news(basket, top_symbols)
stock_news = {}
for symbol in top_symbols[:6]:
profile = store["profiles"].get(symbol, {})
stock_news[symbol] = fetch_stock_news(profile, bid)
Sleep(800)
analysis = analyze_basket_intel_with_llm(
store, basket, top_symbols, basket_news, stock_news
)
if analysis:
analysis["basket"] = bid
analysis["basket_name"] = basket["name"]
analysis["symbols"] = top_symbols
analysis["updatedAt"] = int(time.time())
intel[bid] = analysis
store["basket_intel"] = intel
Назначение этого шага — создать динамический фон для каждой AI-подсекции.
Большая модель на основе новостей, полученных через BraveSearch, выводит тренд сектора, ключевые катализаторы, ключевые риски, кандидатов в лидеры, кандидатов в последователи, а также взаимосвязи, требующие последующей верификации.
Это отличается от прямого вопроса модели «Как смотреть на AI-хранилища?». Мы сначала подаём ей актуальные новости, а затем просим сделать резюме.
Не новостной трейдинг, а аномалия цены
Поначалу я тоже думал о новостном драйвере.
Например, при превышении ожиданий по отчётности какой-то компании пусть модель определяет, на какие акции это позитивно влияет, и сразу генерирует сигнал. Но, поразмыслив, я понял, что этот путь очень шумный. Новостей слишком много, многие из них кажутся важными, но рынок на них не реагирует.
Поэтому я перевернул логику.
Сначала смотрим на цену, потом на новости.
Если все акции в корзине относительно стабильны, система просто наблюдает. Только когда какая-то акция заметно отклоняется от среднего показателя по сектору и становится текущим лидером, система начинает выяснять: почему она движется?
На этом этапе используется ZScore внутри корзины.
Проще говоря, мы смотрим, является ли акция аномально сильной или слабой по сравнению с другими акциями того же сектора. Если акция вдруг вырывается вперёд, а остальные акции в той же корзине ещё не синхронизировались, возможно, существует возможность распространения.
Основная логика расчёта ZScore корзины:
python
def calc_basket_zscores(store, symbols):
returns = {}
min_len = 999999
for symbol in symbols:
bars = get_records(
store["markets"][symbol]["contract"],
KLINE_PERIOD,
SYNC_WINDOW + 10
)
if len(bars) < SYNC_WINDOW + 2:
return None
r = calc_returns(bars)[-SYNC_WINDOW:]
returns[symbol] = r
min_len = min(min_len, len(r))
latest = {s: returns[s][-1] for s in symbols}
avg_latest = mean(list(latest.values()))
dev_series = {s: [] for s in symbols}
for i in range(min_len):
row = [returns[s][-min_len + i] for s in symbols]
row_avg = mean(row)
for s in symbols:
dev_series[s].append(returns[s][-min_len + i] - row_avg)
zscores = {}
max_abs = 0
for s in symbols:
sigma = std(dev_series[s])
z = (latest[s] - avg_latest) / sigma if sigma > 0 else 0
zscores[s] = round(z, 3)
max_abs = max(max_abs, abs(z))
return {"zscores": zscores, "maxAbs": max_abs}
Если максимальное отклонение в какой-либо корзине превышает порог пробоя, система переходит в состояние BREAKOUT_DETECTED.
В этот момент система не начинает торговать сразу, а приступает к поиску причины.
Новости только объясняют, статистические связи — проверяют
Когда у лидера происходит ценовой пробой, система собирает его последние новости, а затем просит большую модель определить, может ли эта новость объяснить ценовую аномалию.
Но здесь не простой вопрос «позитивна ли эта новость».
Система передаёт модели текущую корзину, разведданные по сектору, текущее состояние цены, ZScore лидера, кандидатов в последователи, уже проверенные статистические связи и новости, полученные через BraveSearch.
Модель должна ответить:
(1) Может ли эта новость объяснить ценовую аномалию лидера?
(2) Совпадает ли направление новости с направлением цены?
(3) Разрешена ли торговля?
(4) На каких последователей это влияет?
(5) Насколько велико влияние на каждого последователя?
(6) Почему эти последователи могут быть затронуты?
Основная логика поиска причины:
python
def explain_breakout_with_news(store, bid, leader, direction, leader_z, followers, edges):
if not BRAVE_KEY:
return None
if not LLM_API_KEY:
return None
profile = store["profiles"].get(leader, {})
news = fetch_stock_news(profile, bid)
if not news:
return None
event = analyze_breakout_news_with_llm(
store, bid, leader, direction, leader_z,
followers, edges, news
)
if not event:
return None
if not event.get("explains_price_move"):
return None
expected_direction = "bullish" if direction == "long" else "bearish"
if event.get("direction") != expected_direction:
return None
event["confidence"] = normalize_confidence(event.get("confidence", 0))
if event["confidence"] < MIN_NEWS_CONFIDENCE:
return None
if not event.get("trade_allowed"):
return None
return event
Но даже если большая модель говорит, что какой-то follower может пострадать, торговать сразу нельзя.
Нужно ещё посмотреть статистическую взаимосвязь на K-линиях.
Система в каждой корзине рассчитывает историческую взаимосвязь между акциями: например, после пробоя source часто ли следует target, какова корреляция, какой оптимальный период запаздывания, какой средний доход от следования.
Только те source → target, которые прошли статистическую проверку, становятся активными рёбрами (active edge).
python
def rebuild_basket_stats(store):
edges = []
for bid, basket in store["baskets"].items():
symbols = [
m["symbol"] for m in basket["members"]
if m["symbol"] in store["markets"]
]
for source in symbols:
for target in symbols:
if source == target:
continue
metric = validate_pair(store, source, target)
if not metric:
continue
source_exp = basket_member_info(basket, source).get("exposure", 0)
target_exp = basket_member_info(basket, target).get("exposure", 0)
score = clamp(
metric["corr"] * 0.3 +
metric["follow"] * 0.4 +
min(source_exp, target_exp) * 0.3,
0, 1
)
edge = {
"basket": bid,
"source": source,
"target": target,
"corr": round(metric["corr"], 3),
"follow": round(metric["follow"], 3),
"lag": metric["lag"],
"avgReturn": round(metric["avgReturn"], 6),
"sample": metric["sample"],
"score": round(score, 3),
"active": (
score >= MIN_EDGE_SCORE and
metric["corr"] >= MIN_CORR and
metric["follow"] >= MIN_FOLLOW_SUCCESS
),
"updatedAt": int(time.time()),
}
edges.append(edge)
store["edges"] = edges
Этот шаг важен.
Большие модели хорошо понимают логику, но рынок не обязательно следует логике. Статистические взаимосвязи как раз служат проверкой: была ли эта логика когда-либо отыграна на рынке.
Торговля идёт на диффузию, а не на догон
Стратегия не нацелена на догон лидера.
Лидер уже пробил уровень, и бросаться за ним напрямую – это почти наверняка покупка на пике. Стратегия действительно хочет использовать возможности диффузии внутри одной AI-корзины.
То есть: лидер уже сдвинулся, новости объяснимы, но некоторые follower ещё не полностью отреагировали. Если эти follower и лидер исторически действительно имеют взаимосвязь, то может возникнуть краткосрочная возможность для следования.
При сканировании пробоев сначала ищутся активные рёбра, а затем запаздывающие follower:
python
def scan_price_breakouts(store):
for bid, rt in store["realtime"].items():
if rt["state"] != "BREAKOUT_DETECTED":
continue
leader = rt["leader"]
z = 0
for m in rt["members"]:
if m["symbol"] == leader:
z = m["z"]
direction = "long" if z > 0 else "short"
edges = [
e for e in store["edges"]
if e["active"] and e["basket"] == bid and e["source"] == leader
]
followers = []
for e in edges:
target_rt = find_realtime_member(rt, e["target"])
if target_rt and abs(target_rt["z"]) < BREAKOUT_THRESHOLD * 0.75:
followers.append(e["target"])
if not followers:
continue
Только после того, как и новостная атрибуция пройдена, генерируется подтверждённый сигнал (confirmed signal).
Объект торговли – в основном follower.
Если в данный момент нет позиции, открываем по направлению сигнала. Если уже есть позиция того же направления по тому же контракту – увеличиваем. Если есть противоположная позиция – сначала закрываем её, затем открываем по новому направлению.
python
def execute_symbol_trade(store, signal, symbol, detail):
direction = signal["direction"]
if TRADE_MODE != "trade":
return open_or_add_position(store, signal, symbol, detail, add_mode=False)
pos = get_position_by_symbol(symbol)
if pos:
pos_dir = position_direction(pos)
if pos_dir == direction:
return open_or_add_position(store, signal, symbol, detail, add_mode=True)
close_existing_position(store, symbol, pos, "reverse_signal")
Sleep(1000)
return open_or_add_position(store, signal, symbol, detail, add_mode=False)
Это также связано с тем, что многие акции одновременно присутствуют в нескольких корзинах. Одна акция может быть follower в корзине AI Cloud, а в корзине AI Application – участником другой логики. Сигналы от разных корзин могут одновременно влиять на неё, поэтому необходимо единообразно обрабатывать направление по одному контракту.
Управление рисками: сначала просто, но обязательно
В части управления рисками пока используются жёсткий стоп-лосс и трейлинг-стоп.
При достижении убытком порога – выход; при достижении прибылью определённого уровня включается трейлинг-стоп, и если откат от максимальной плавающей прибыли превышает допустимый уровень, прибыль фиксируется и позиция закрывается.
python
def monitor_positions(store):
for pos in exchange.GetPosition() or []:
symbol = position_symbol(pos)
if symbol not in store["markets"]:
continue
state = _G(position_state_key(symbol)) or {}
market = store["markets"][symbol]
ticker = exchange.GetTicker(market["contract"])
direction = position_direction(pos)
pnl_pct = (
(ticker["Last"] - pos["Price"]) /
pos["Price"] * 100 *
(1 if direction == "long" else -1)
)
if pnl_pct > state.get("peakPnlPct", 0):
state["peakPnlPct"] = pnl_pct
if not state.get("trailActive") and state.get("peakPnlPct", 0) >= TRAIL_ACTIVATE_PCT:
state["trailActive"] = True
hard_stop = False
if state.get("stop"):
hard_stop = (
ticker["Last"] <= state["stop"]
if direction == "long"
else ticker["Last"] >= state["stop"]
)
trail_hit = False
if state.get("trailActive"):
giveback = state.get("peakPnlPct", 0) - pnl_pct
allowed = max(
1.5,
state.get("peakPnlPct", 0) * TRAIL_GIVEBACK_PCT / 100.0
)
trail_hit = giveback >= allowed
if hard_stop or trail_hit:
close_existing_position(
store, symbol, pos,
"hard_stop" if hard_stop else "trailing_stop"
)
Это не финальная версия риск-контроля, но для первого этапа наблюдения на реальном счете уже достаточно.
Позднее необходимо добавить ограничения по общей позиции на уровне счета, экспозиции по отдельным секторам, дневному убытку, подтверждению сделок и количеству добавлений позиций.
На каком этапе сейчас система
Сейчас система уже способна выполнять достаточно полный исследовательский цикл.
Она может автоматически сканировать контракты TradFi (акции), автоматически формировать с помощью большой языковой модели портрет корзины long-акций, автоматически строить AI-подсектора, автоматически через BraveSearch получать информацию о секторах и отдельных акциях, автоматически обобщать разведданные по секторам, автоматически отслеживать текущую относительную силу каждой корзины, автоматически вычислять статистическую зависимость акций внутри корзины, а после пробоя цены лидера проводить поиск новостных причин.
Если новостное объяснение подтверждается, направление совпадает, и статистические связи также подтверждаются, система генерирует подтвержденный сигнал. В режиме по умолчанию только уведомляет, без реальной торговли; только при переключении в режим trade начинается торговля.
На странице реального счета теперь не только логи, но и таблицы с обзором системы, текущим состоянием корзин, разведданными по секторам, последними сигналами, последними сделками и последними шагами. Это позволяет наглядно видеть, на каком этапе застряла система: только аномалия цены, или нет новостного объяснения, или не выполнены условия для follower, или сработал риск-контроль.
Что пока не доработано
Честно говоря, эту систему еще нельзя считать зрелой автоматической торговой стратегией.
Она скорее похожа на исследовательский фреймворк для тематической ротации на основе ИИ, который уже позволяет связать воедино «портреты акций, разведданные по секторам, ценовые аномалии, новостные причины, статистическую проверку и торговые сигналы», но требует дальнейшей валидации.
Наиболее важные направления для доработки следующие.
(1) Качество портретов акций. Большие языковые модели иногда включают в корзину пограничные акции, поэтому необходимо принудительно проверять asset_type, чтобы исключить ETF, индексы, региональные фонды.
(2) Качество новостей. Новости, полученные через BraveSearch, не всегда являются ключевыми; модель должна оценивать, какие из них действительно могут объяснить движение цены.
(3) Статистические связи. Историческая зависимость не гарантирует эффективность в будущем, требуется постоянный ретроспективный анализ.
(4) Риск-контроль сделок. Перед автоматической торговлей необходимо добавить ограничения по позициям на уровне счета, экспозиции по секторам, дневному убытку и подтверждению сделок.
Поэтому сейчас лучше сначала запустить в режиме notify и посмотреть на качество confirmed сигналов.
Если через некоторое время окажется, что сигналы действительно объясняют рыночные аномалии, и follower впоследствии часто реагируют, можно рассмотреть торговлю на реальном счете с малым объемом.
В завершение
Эта стратегия не предполагает, что ИИ напрямую размещает ордера.
Я всегда считал, что наиболее подходящее место для большой языковой модели в торговой системе — не «скажи мне, что покупать», а помощь в обработке информационных структур, которые человеку трудно поддерживать вручную: бизнес компаний, принадлежность к секторам, новости в реальном времени, интерпретация событий, пути движения капитала и потенциальные объекты влияния.
Настоящая торговля все равно должна основываться на цене, статистике и риск-контроле.
Количественная стратегия в эпоху ИИ не должна сводиться к простому добавлению интерфейса к большой модели; необходимо объединить данные в реальном времени, тематические графы, новостные события, ценовую структуру, исполнение сделок и пост-анализ.
Данная стратегия «Карта акций на основе ИИ» — это попытка двигаться в этом направлении.
Исходный код стратегии: AI 产业链 TradFi 合约图谱交易系统
- 1





