点击开始动手实验


ChatGPT炒股实战:基于LLM的量化交易策略开发指南

  1. 传统技术分析的“老毛病”

我最早做量化时,满屏都是MACD、KDJ、布林带。跑完回测发现,收益曲线看着漂亮,一上实盘就“翻车”。症结无非三点:

  • 技术指标天生滞后,价格走完了才金叉死叉,滑点吃光利润。
  • 财报、突发新闻、社媒情绪这类文本数据,用关键词打分太粗糙,“利好”可能被打成“利空”。
  • 特征工程靠人肉,换一只票就要重新调参,策略迁移性极差。

于是我把目光投向大模型:让LLM直接读新闻、给信号,把“情绪”量化成可执行的交易指令。

  1. 技术方案:FinBERT vs ChatGPT

FinBERT金融领域预训练,专业术语准,但只有两个标签(positive/negative),粒度太粗;而且自己微调要标注数据,成本不低。ChatGPT zero-shot就能吐出“强烈看空”“谨慎做多”这类带程度的词,还能给出理由,方便我做五档信号(+2 +1 0 -1 -2)。

Prompt我迭代了七版,最终定型如下,token 120 左右,便宜又稳定:

你是一名资深交易员。请阅读以下财经新闻,给出交易信号(+2强烈做多,+1谨慎做多,0观望,-1谨慎做空,-2强烈做空),并一句话说明理由。输出格式:信号|理由
新闻:{text}

把新闻标题+正文拼进 {text},temperature 设 0.3,既抑制幻觉,又保留一定区分度。

  1. 核心代码:端到端流水线

下面代码全部跑通 Python3.10,依赖见注释。为了阅读体验,我拆成四段,每段都能单独测试。

3.1 新闻爬取与清洗(以新浪财经为例)

# pip install requests beautifulsoup4 lxml
import requests, bs4, datetime as dt, pandas as pd

HEADERS = {"User-Agent": "Mozilla/5.0"}
def crawl_sina_finance(page=1):
    url = f"https://feed.sina.com.cn/api/roll/get?pageid=153&lid=2516&k=&num=50&page={page}"
    data = requests.get(url, headers=HEADERS, timeout=10).json()
    records = []
    for item in data["result"]["data"]:
        records.append({
            "time": dt.datetime.fromtimestamp(int(item["ctime"])),
            "title": item["title"],
            "url": item["url"]
        })
    return pd.DataFrame(records)

def clean_text(url):
    html = requests.get(url, headers=HEADERS, timeout=10).text
    soup = bs4.BeautifulSoup(html, "lxml")
    article = soup.find("div", class_="article")  # 新浪正文容器
    if not article:
        return ""
    return " ".join(p.text for p in article.find_all("p"))

# 示例:抓两页,清洗后落盘
df = crawl_sina_finance(page=1)
df["body"] = df["url"].apply(clean_text)
df = df[df["body"].str.len() > 40]  # 过滤太短
df.to_csv("news_raw.csv", index=False)

时间复杂度:爬取 O(n),清洗正则 O(m)(m 为单篇字符数),整体可忽略。

3.2 ChatGPT API 封装(含重试与限速)

# pip install openai tenacity
import openai, tenacity, time, os
openai.api_key = os.getenv("OPENAI_API_KEY")

@tenacity.retry(stop=tenacity.stop_after_attempt(5),
               wait=tenacity.wait_exponential(multiplier=1, min=4, max=60))
def call_gpt(prompt, model="gpt-3.5-turbo", temperature=0.3):
    resp = openai.ChatCompletion.create(
        model=model,
        messages=[{"role": "user", "content": prompt}],
        temperature=temperature,
        max_tokens=80
    )
    return resp["choices"][0]["message"]["content"].strip()

# 批量调用,60 条/分钟 保守限流
def batch_signal(news_df, col="body"):
    signals, reasons = [], []
    for txt in news_df[col]:
        out = call_gpt(PROMPT_TEMPLATE.format(text=txt[:1500]))  # 截断防超额
        sig, _, reason = out.partition("|")
        signals.append(int(sig.strip()))
        reasons.append(reason.strip())
        time.sleep(1.05)  #  RPM 限制
    news_df["signal"], news_df["reason"] = signals, reasons
    return news_df

3.3 信号与 TA 指标融合(示例:MACD+新闻情绪)

import talib, numpy as np

def hybrid_signal(df_price, df_news, lookback=5):
    # 1. 计算 MACD
    macd, signal, _ = talib.MACD(df_price["close"].values)
    macd_cur = macd[-1] - signal[-1]

    # 2. 新闻情绪均值
    news_window = df_news.tail(lookback)["signal"].mean()

    # 3. 冲突解决:简单投票
    if news_window > 0.5 and macd_cur > 0:
        return 1  # 双多头
    elif news_window < -0.5 and macd_cur < 0:
        return -1  # 双空头
    else:
        return 0  # 观望

3.4 回测+可视化(Walk Forward 示例框架)

# 假设 price.csv 含 ['date','open','high','low','close','vol']
price = pd.read_csv("price.csv", parse_dates=["date"])
news = pd.read_csv("news_with_signal.csv", parse_dates=["time"])

results = []
for train, test in walk_split(price, train_days=250, test_days=30):
    # 训练集内只做参数统计,这里无超参可略
    for idx in test.index:
        sub_news = news[news["time"].between(train.date.iloc[-1], test.date[idx])]
        sig = hybrid_signal(price[:idx], sub_news)
        results.append({
            "date": price.loc[idx, "date"],
            "signal": sig,
            "price": price.loc[idx, "close"]
        })

res = pd.DataFrame(results)
res["ret"] = res["price"].pct_change() * res["signal"].shift(1)
res["nav"] = (1 + res["ret"].fillna(0)).cumprod()

# 画图
import matplotlib.pyplot as plt
plt.figure(figsize=(8,4))
plt.plot(res["date"], res["nav"], label="LLM+MACD")
plt.plot(res["date"], (1+res["price"].pct_change()).cumprod(), label="Buy&Hold")
plt.legend(), plt.show()
  1. 避坑指南
  • 频率控制:OpenAI 免费 tier 60 请求/分钟,超出直接 429。用 tenacity 装饰器+sleep 双保险。
  • 结果验证:
    – SHAP 分析:把 prompt 拆成“标题”“正文”“行业词”三段,看哪段对信号贡献最大,防止模型偷懒只读标题。
    – Walk Forward:滚动 250/30 天,比单次回测更能暴露过拟合;我初期偷懒用全量,Sharpe 1.8,滚动后掉到 0.9,真相了。
  • 数据合规:美股材料必须符合 Reg-FD,公开渠道抓取可以,但别碰付费终端的 raw feed;保存日志 3 年,防止 SEC 抽查。
  • 实盘隔离:同一策略先跑 Paper Account 两周,确认延迟<500 ms、滑点<2 bps 再上小资金;用子账户单独放 10% 本金,爆仓也不伤筋动骨。
  1. 安全与伦理
  • 不公开未经验证的“神单”,防止社群跟单导致闪崩。
  • 在代码仓库加 DISCLAIMER,注明“仅供教育,非投资建议”。
  • 定期回炉重训:模型知识半年就过期,财报季新词一堆,信号漂移及时修。
  1. 开放问题留给你

当模型给出强烈买入,而 MACD 刚死叉,你更愿意相信哪一边?是设计硬规则投票,还是让 LLM 连 MACD 值一起读再下结论?或者干脆用强化学习把冲突丢给智能体?欢迎在评论区交换思路。

  1. 写在最后

把 ChatGPT 当“情绪增量”而不是“圣杯”,是我这趟折腾的最大体会。整套流程我已经打包成可一键跑的模板,如果你也想亲手试试“让大模型帮你读新闻”,可以从这个动手实验开始——从0打造个人豆包实时通话AI(对,语音对话那套框架同样能用在实时读新闻播报上)。我这种 Python 半桶水都能跑通,相信你也可以。祝你回测曲线永远向左上角扬!

点击开始动手实验


Logo

这里是“一人公司”的成长家园。我们提供从产品曝光、技术变现到法律财税的全栈内容,并连接云服务、办公空间等稀缺资源,助你专注创造,无忧运营。

更多推荐