给程序员的投资指南——量化交易

190 阅读28分钟

本章内容

  • 量化分析
  • 用回测(backtesting)测试策略
  • 作为“变局者”的催化剂(catalysts)
  • 交易所与券商的区别
  • 用 Python 执行下单
  • 订单类型与交易方式(modalities)

在你已经掌握用图表进行技术分析之后,本章将引入用于交易策略的算法,把原始洞见转化为可执行动作。这一步汇聚了前文的一切:数据收集、比率与图表分析、风险评估,以及机器学习(ML)与大语言模型(LLM)的整合。我们会把这些拼在一起,并给出实际应用场景。

不同情境会影响交易策略:有些算法在牛市表现更好,有些则更适合熊市;而催化剂是预示市场将更为波动的特定时刻,既可能是机会也可能是风险。我们将从交易视角审视这些催化剂。

本章第三部分将演示如何通过 Interactive BrokersAlpaca 以编程方式下单,探讨不同订单类型、参数与影响,并学习如何有效应用。

11.1 非财务数据(Nonfinancial data)

在第 3 章,我们给出了金融分析的数据分类;第 4 与第 5 章通过即席研究(ad hoc research)构建了投资组合;随后我们展示了如何监控组合、探索了 ML 与 LLM 的应用,并进行了图表分析。如图 11.1 所示,三类数据源(含其更新频率)可用于构建用于交易决策的数据模型与算法有效市场理论认为资产价格已反映所有投资者的知识,因此个人投资者长期无法跑赢市场。但如果我们获得他人没有或极少人拥有的洞见呢?这些“隐藏宝石”不太可能来自基本面或技术面数据——几十年来投资者已反复挖掘这些财务数据。于是我们把焦点放在非财务数据,这是最新的洞见来源,并非人人都已用尽。

image.png

图 11.1 三种数据流都可能带来洞见,而非财务数据作为最新的一类尤为值得关注。

先看看数据如何改变投资方法,从而理解非财务数据如何为投资与交易策略带来新味道。许多投资者从巴菲特的书中学习投资基础;巴菲特常称其导师本杰明·格雷厄姆为成熟投资技术之父。阅读《证券分析》《聪明的投资者》等经典,你几乎能想象:年轻的巴菲特靠翻阅报纸获取信息,和查理·芒格在前信息时代与他人线下达成交易。我们很难想象投资传奇如巴菲特,会坐在彭博终端前研究数据,或在 IDE 里敲 Python。

电影《华尔街》《大空头》也塑造了大众对投行业的印象:那时已经使用计算机交易。大量纪录片/电影展示了互联网如何成为游戏规则改变者,让我们能对快速移动的信号即时反应——而在早年,这需要耗费更长时间。

从巴菲特早年(无电脑投资)到 2008 金融危机(盯盘看红绿图)之间,数字化已深刻改变了投资者。更不易察觉的是:进入“信息早期时代”后又发生了多少变化。千禧年时,人们难以想象某个体在媒体上的一句话能令全球股价在一天内涨跌超 10%

当特朗普总统改变美国关税政策后对股价的冲击,让所有人直面一个新现实:源自金融圈外的数据——例如可被实时采集并纳入交易算法的社交媒体帖子——有时对股价的影响超过财务数据。未来,我们也许会像研究图表那样系统扫描社交媒体中的关键信息来做投资决策。

社交媒体只是非财务数据这类目下的一个子项。我们来扩展更多类别,以帮助洞见可能的股价变化。考察不同非财务数据类别时会发现:与财务数据不同,非财务数据在不同行业/赛道中的相关性差异很大。社媒数据通常更关联面向消费者(B2C)的公司,而对主要做 B2B 的公司相关性较弱。这与财务数据不同——财务报表等几乎人人必须披露。换言之,严肃投资者也许会跳过社媒分析,但不会跳过资产负债表上的数字。

结构化趋势分析(Structured trend analysis)

核能为例,争议话题总能点燃辩论:
反对者认为建厂耗时且昂贵、担忧堆熔与核废料对后代的威胁;
支持者强调核电为可再生能源的波动性提供稳定补充,并称赞其能量密度高。

把双方放在一个屋里,辩论多半会失控。不论是被多年立场“蒙蔽”的拥护者,还是代表某种“游说”的发言者,都以赢为目的。你很可能难以收获有效洞见

投资者可以改用非财务数据趋势研究
正在建设多少座核电站?
关于核能的论文数量如何变化?
研究论文如何预测数据中心的未来能源需求
核能相关上市公司的股价怎么走?
这些都可被收集并加入算法作为因子

11.1.1 大数据举例(Big data by example)

在基本面与技术面之外,更多额外数据源能拓宽我们对资产可投性的视角。继续探索会通向许多行业/赛道细分的数据源与用例;它们往往比“开箱即用”的数据复杂。不过,对如何用金融数据丰富数据模型的可能性有个鸟瞰仍然很有价值——对某些“珍稀数据”的获取,可能正是盈亏分水岭

大数据 = 大公司?(Is big data equal to a big company?)

许多数据源都能提供洞见。例如卫星影像电信运营商的数据可以揭示人/物移动趋势。那是否意味着必须是大公司才用得上?

未必。 一些数据集可通过 AWS Data ExchangeSnowflake Marketplace市场购买。若你是构建分析模型的程序员,选择性地采购付费外部数据源是可行的。但为了不要求读者订阅商业数据服务,本节以方法论为主。

Renaissance Technologies(RenTech) 很早就开始收数,这或许是其成功的原因之一。但不要为“收数而收数” 。应先提出投资论证(thesis),在充分挖掘财报与价格数据后,再思考还缺什么信息来验证论证;接着考虑如何获取

举例:我们假设 Waymo 将推动 Google 的股价腾飞,甚至像 iPhone 之于 Apple 那样。文章可以这么说,但文章只是一次性观点。我们需要能证明 Waymo 重要性不断上升的数据源,例如:

  • 每周载客/里程硬数据
  • Waymo 新城市服务的上线节奏
  • 事故/故障通报减少的证据

下面看看如何收集这些信息。各小节旨在提供为模型添加具体细节的思路;在某些情况下这意味着投资外部数据源,因为不少洞见无法从免费来源获得。

媒体(Media)

多数媒体信息来自网络。可用爬虫API社交媒体、传统媒体、博客等收集,也可把视频转写为文本。

围绕 Waymo,我们可以用大样本信息做趋势分析
Waymo 被提及的频率是否逐步增加
何种语境被提及?
公众情绪(情感极性)如何?
新城市落地是否如期

NOTE 人类情绪属于非财务数据。比如 CBOE 波动率指数(VIX) 衡量基于标普 500 期权的市场波动性预期。把媒体情绪与该指数的变化做匹配,能产生许多有趣的研究场景。

运动/位置数据(Movement data)

可用地理位置运动数据观察区域内的变化。借助卫星/路摄图像,若具备计算机视觉能力,甚至可以计数特定对象(如街面不同品牌车型)。了解某地区 Waymo 车辆保有/出勤的增长,将为其整体成功提供辅助证据。

你可能会问:收集并分析这些“异质数据”值得吗?即便卫星数据充沛,训练识别特定目标的 CV 模型也远比处理主流数据费时费力

有效市场理论认为个股价格反映所有可得信息。若工程师能收集/分析非常规、少有人用的数据,就可能获得竞争优势低估“弱代表数据”价值是危险的。

人口统计数据(Demographic data)

人口统计揭示一地需求结构。长期投资者会从统计看到:若某区域人口老龄化,助听器需求可能强于滑板

Waymo 的需求也可能与人口结构相关:未成年/未持照青少年可能偏好无人出租车;老年人/行动不便者也可能更依赖此类服务。

一个核心论断是:使用无人出租车是心智模式的转变,而这种转变可以被数据捕捉。评估相关数据,便能洞见目标人群的行为变化

11.2 催化剂(Catalysts)

在金融领域,催化剂是促使股票价格剧烈上升或下跌的触发因素。财报、分析师新评级、产品发布、立法变动、诉讼、并购(M&A)、破产声明以及激进投资者介入,都是典型例子。

当我们预期股价会出现超常波动时,催化剂就是买入或卖出的机会。任何对量化或算法交易感兴趣的人,都可以聚焦特定催化剂并围绕它们构建专用模型

11.2.1 并购(Mergers and acquisitions)

世间万物皆有始有终。商业如人生:增长之后是成熟、饱和,继而衰退。有观点甚至把生—灭—再生视作超越商业的哲学规律,其要义是:一切终将转化。图 11.2 展示了企业生命周期的多个阶段。设想一家拥有惊艳产品创意的公司,入市后客户蜂拥而至,企业成长并步入成熟。

image.png

图 11.2 企业可能的演进路径。灰色阴影区域标注了机会窗口。在衰退阶段,通过并购被并购或可挽救颓势。

除非拥有强大的护城河,多数公司终会遭遇竞争者。新对手进入市场,核心产品的销售增速开始放缓。大家都明白:若没有“下一款爆品”,公司难以维持既往表现。

一些公司在竞争饱和的市场里缺乏自救资源——或许因亏损过多,已无力为下一代产品/服务投入足够的研发预算。在企业世界里,并购是当你没足够资源自我重塑时防止滑坡的一种策略。管理层可能寄望通过“强强联合”实现提前复兴,路径或为合并(merger) ,或为收购(acquisition)

合并的理念是 1 + 1 > 2:合并后的实体借助协同效应强于两者之和。例如两个产品互补的公司合并后能扩展产品线并简化运营。收购更偏单向:大公司“吞并”小公司并整合到自身架构中(如微软收购 GitHub,但在组织上保持一定独立)。

对投资者而言,M&A 既有机会也有风险。当公司被宣布收购时,其股价往往飙升,存量股东受益。若两家上市公司合并,投资者也可能看到长期价值的创造。然而纸面上的“完美组合”,落地时可能遭遇文化冲突、系统整合难、管理风格不合等问题;若员工抵触整合,并购未必兑现价值,甚至可能毁灭而非创造股东财富。

NOTE 投资者或许会以为:若存在明显的文化不匹配,管理层会阻止合并。但对某些高管而言,“卖身”可能被视为某个时代的体面谢幕,后续问题留给接任者。因此,参与并购的所有高管是否都怀有完全无私的动机,仍值得存疑。

我们可以围绕 M&A 建模,试图找出成败关键指标。上一小节谈到非财务数据:许多数据源都可用于判断并购成效。比如,分析博客/评论的情绪数据;若能获取 LinkedIn、Glassdoor 或其他员工情绪平台的数据,或能窥见关键洞见。

11.2.2 困境企业(Companies in distress)

烟蒂型投资”(cigar butt investing)是巴菲特早年(受导师格雷厄姆影响)采用的一种策略。就像在地上捡起一截被丢弃的烟蒂,再免费吸最后一口就扔掉。这种方法风险高,巴菲特后来也弃用,但确实可能赚钱。

做法是:寻找极度低估、被忽视或陷入困境但尚有残值的公司。以深度折价买入,等待小幅反弹后卖出获利。一旦市场稍微修正错误定价、股价略有回升,就在更深层问题暴露前果断离场

这像是赶工修补一个陈旧仓库里的项目:可能奏效,但风险高、效率低、难长久。你寻找濒临破产的公司,穷尽数据去判断是否可能被收购或自我反转。图 11.3 展示了困境公司的处理流程。介入越晚,风险越高

image.png

图 11.3 困境企业的处置流程,以及基于资产状况的潜在扭转路径

你可以关注已申请破产的公司。有些会出现所谓**“领头马(stalking horse)”协议**:在破产或财务困境下,卖方与初始竞标者签署约束性购买协议,为竞价流程设定底价与条款,以避免资产被“白菜价”处理。许多股东会因破产而恐慌清仓,担心血本无归。如果你相信某家困境公司足够有意思,且潜在买家虎视眈眈,你可以在股价见底时入场搏一把。但请务必牢记:即使做了大量研究,这依旧可能彻底失败,你可能会损失殆尽

WARNING 你能找到若干“破产后成功翻身”的案例,激进投资者因此获得巨额回报。但请记住,这些凤毛麟角——在大多数破产中,股权被清零,债权人获得控制权。

11.2.3 财报电话会(Earnings calls)

每家上市公司都会在季度财报电话会上由管理层解读上一季度。股价可能因此大幅波动。核心问题是:公司是否达成/超预期/不及预期?此外,管理层还会谈及各种话题。对电话会议文本情感分析,常常有启发。

准备财报会的一种方式是提前数日开始调研:例如留意管理层近期是否增减持,这可能是一个信号;再结合公开的新签/解约信息,判断**盈余意外(earnings surprise)**的可能方向与幅度。

一个实务问题是:在财报会中下单,还是提前下单?可以想见,许多投资者会一边听会一边盯着预设订单,一旦听到暗示可能驱动股价的关键信息,立刻下单。若你有强烈判断,也可以提前买/卖。

财报策略(Earnings call strategies)

  • 如果你足够激进,可以考虑在预期将有大波动时使用杠杆提前建仓。举例:若券商允许 500,000杠杆,你押注某股上涨10500,000** 杠杆,你押注某股**上涨 10%** ,当天理论盈利 **50,000;但若下跌 10% ,同样会亏 $50,000。即便自信,很多投资者仍建议避免加杠杆,毕竟市场难料。以 NVIDIA 为例,哪怕它多次交出创纪录的财报,投资者仍会担心估值过高;若增速不及预期,他们可能选择卖出
  • 另一种打法是交易期权而非正股:期权以较低前期成本提供杠杆;买方风险明确定价在权利金上;还可采用波动率策略,不必精准押方向,仅博取波动放大
  • 尤其在中小盘标的上(更可能出现高波动/大意外),期权交易容易像赌场,务必谨慎。

11.2.4 灾难(Disasters)

新冠疫情是股市的游戏规则改变者。许多公司股价急挫,但也有显著的例外。比如 ModernaPfizer,其股价历史显示了可观收益

每一次灾难都有受害者与受益者野火、飓风等自然灾害(在气候变化推动下更频繁)会给保险公司等带来挑战;而提供灾害缓解方案的公司(如野火早期探测)会受益。战争会冲击相关国家的经济,但也会出现受益方——观察德国军工企业 Rheinmetall 的股价便知。

你可以通过提早布局潜在灾难主题来获取收益,既可走动量,也可走长期。以野火为例:

  • 长期投资者:买入防灾/减灾的公司,假以时日,随着气候恶化、灾害增多,对这些解决方案的需求会提升
  • 动量投资者:在重大灾害发生前做空保险股
    当然,预测灾难本身很难。若有人真能做出这种创新系统,把服务卖给政府,获得的回报大概率远胜在市场押注。

11.2.5 利率变动(Interest rate changes)

联邦利率与股价之间的关系,可能是新手投资者最容易忽略的参数之一。历史能教给我们很多。艾伦·格林斯潘(Alan Greenspan)任美联储(Fed)主席期间,倾向于保持低利率低利率让企业以更低成本融资,进而增加投资、降低失业。但随之而来的副作用包括资产泡沫/狂热(例如 2008 年的次贷危机,部分便是“廉价资金”催生)以及较高通胀

新冠疫情时期的美联储政策,是利率影响股价的生动教材。以专注研发的生物科技公司(如 Moderna)为例:疫情期间,得益于高盈利股价表现亮眼;但为抑制通胀,利率上行后,包括 Moderna 在内的资本密集型公司股价在疫情后期承压

一种策略是:当利率高而股价时买入资本密集型股票;待美联储降息时,这类公司股价大概率抬升。但这里有个大陷阱:部分尚未锁定稳定现金流的资本密集型企业,在高利率环境中更易破产

NOTE 如果你是自由职业程序员,或许会发现:利率低时你收到的项目机会平均更多。当借钱变贵,公司就更不愿花钱。你也可以用 LLM 做更深研究,例如提示:“基于当前经济形势,自由职业者接新项目的前景如何?何时会改善?”

11.3 交易算法(Trading algorithms)

通过持续监测数据的异常跃迁——如公众情绪的变化或异常价格波动——我们可以建立基于规则的交易策略:

  • 若发生 X,则买入
  • 若发生 Y,则卖出

关键在于定义 X 与 Y。它们必须可量化,以便算法能判定是否满足买/卖触发条件。

在追寻 X 与 Y 之前,先聚焦验证结果。熟悉测试驱动开发(TDD)的程序员都明白:在实现逻辑之前定义验证准则至关重要。即便策略再精巧,没有扎实的验证框架,最终也可能不稳定或误导

11.3.1 回测(Backtesting)

回测用历史数据评估交易策略。举例来说,在技术分析章节我们研究过移动均线,并观察到SMA 金叉/死叉可作为买卖信号:

  • 短周期SMA 向上穿越长周期 SMA → 买入
  • 向下穿越卖出

我们用历史价格来测试这一策略。首先需要通用方法。第一步是在历史价格的 DataFrame 中添加 SMA 计算。由于会在同一 DataFrame 中使用多个窗口的 SMA,我们还需检查某个窗口的 SMA 是否已存在,如下所示:

def add_sma(df: pd.DataFrame, lower: int, upper: int) -> pd.DataFrame:
    if f'sma_{lower}' not in df.columns:
        df[f'sma_{lower}'] = df.Close.rolling(lower).mean()
    if f'sma_{upper}' not in df.columns:
        df[f'sma_{upper}']  = df.Close.rolling(upper).mean()
    return df

来看清单 11.1 的回测方法。我们传入从 yfinance 载入的历史数据 DataFrame,并指定短/长SMA 的窗口与单次交易股数。我们还定义了一个布尔参数,用于在首个交易日直接买入,以便与其他结果更好对比。

NOTE 在该回测策略中,我们每次买/卖固定股数。一次信号触发后,会等待反向信号作为下一次操作,不会连续重复同一方向的信号。实际中可在定期间隔重复下单,亦可加入做空等高级情形,并可调整交易股数。为讲解清晰,这里保持简化

清单 11.1 回测

def backtest(df: pd.DataFrame, lower: int, upper: int, shares : int, 
  buy_first_day = False) -> pd.DataFrame:
    schema={'date': 'datetime64[ns]', 'action': 'str', 'cash_movement':'float64'}
    results = pd.DataFrame(columns=schema.keys()).astype(schema)
    if buy_first_day:
        results.loc[len(results)] = [df["Date"].iloc[0], "Buy", 
                                     df["Close"].iloc[0] * shares * -1]
        waiting_for_bear = True
    else:
        waiting_for_bear = False

    for index, row in df.iterrows():
        if not waiting_for_bear:
            if row[f"sma_{lower}"] > row[f"sma_{upper}"]:
                results.loc[len(results)] = [row.Date, "Buy", 
                                             row.Close * shares * -1]
                waiting_for_bear = True
        if waiting_for_bear:
            if row[f"sma_{lower}"] < row[f"sma_{upper}"]:
                results.loc[len(results)] = [row.Date, "Sell", 
                                             row.Close * shares]
                waiting_for_bear = False
    if waiting_for_bear:
        results.loc[len(results)] = [df["Date"].iloc[-1], "Sell", 
                                     df["Close"].iloc[-1] * shares]
    return results

开始测试。我们用固定金额(目标货币)作为基准,以便跨股票比较表现。用首期资金计算可买入的股数(股价越高,股数越少)。

需要一个基线(baseline)来衡量策略成效:即若在计划首日买入并在策略末日卖出,两者差额是多少。同时考虑交易成本:若每笔交易成本为 $1,则从 SMA 策略的总收益中扣除交易笔数对应的总成本。若策略收益超过该基线,则视为成功

公司前景重要吗?
先以 NVIDIA 为例。有观点会说:用英伟达不公平,因为它近年极度成功;是不是应该用周期性更强的公司?我们稍后会看更多公司,但从“赢家”开始建立基准很有帮助。长期投资者通常选择基本面强/护城河深的公司并长期持有。那么,如果某个交易策略能让一只本就高回报的公司取得更高收益,意味着什么?

我们会将 NVIDIA 与其他公司对比,尝试总结交易算法是否有意义的更普适结论。小剧透:答案正如你所料——要看情况。数十年来,交易员和投资者一直在争论复杂策略是否能提升回报,至今尚无定论。

我们以 $1,000 为本金,抓取 10 年数据。第一组参数:短期 10 日 SMA / 长期 20 日 SMA(清单 11.2)。

清单 11.2 单次回测

def run_single_backtest(ticker: str, cash, start_date: str, end_date: str, 
                        transaction_costs = 1) -> pd.DataFrame:
    df = load_data(ticker, start_date, end_date)
    shares = round(cash / df["Close"][0])
    print(f"Shares: {shares}. start price: {df["Close"][0]}. end price: "
          f"{df["Close"].iloc[-1]}. to beat: {df["Close"].iloc[-1]*shares – 
                                                 df["Close"][0]*shares – 
                                                 transaction_costs}")
    df = add_sma(df, 10, 20)
    res = backtest(df, 10, 20, shares, True)
    gain = res['cash_movement'].sum()
    gain = gain - transaction_costs * len(res['cash_movement'])
    print(f"Gains: {gain})")
    return df

results_nvda = run_single_backtest("NVDA", 1000, "2015-01-01", "2025-01-01")

控制台显示:在 2015-01-01 我们可买入 2,070 股。按首日买入末日卖出的方式,基线为 **276,98010年),非常惊人。需要指出:英伟达期间发生过拆股,因此2015年初的“276,980**(10 年),非常惊人。需要指出:英伟达期间发生过**拆股**,因此 2015 年初的“0.48”并非真实交易价,而是复权后的历史价。

该模拟显示:SMA10/20 策略失败——10 年仅赚 **177,960,明显不及177,960**,明显**不及** 276,980 的基线。

让我们尝试其他窗口。先构造参数组合的 DataFrame:

x = pd.DataFrame(np.arange(10, 210, 10))
test_params = pd.merge(x,x,how='cross')
test_params.columns = ['sma_lower','sma_upper']
test_params = test_params[test_params.sma_lower < test_params.sma_upper]

接着修改方法,对 10–200 的窗口组合进行批量回测(清单 11.3):

清单 11.3 多次回测

def run_multiple_backtests(ticker, investment_sum = 1000):
    schema={'lower': 'int64', 'upper': 'int64', 'gain': 'float64'}
    results = pd.DataFrame(columns=schema.keys()).astype(schema)
    df = load_data(ticker, start_date)
    shares = round(investment_sum / df["Close"][0])
    print(f"Shares: {shares}. start price: {df["Close"][0]}. end price: "
          f"{df["Close"].iloc[-1]}. to beat: {df["Close"].iloc[-1]*shares –
             df["Close"][0]*shares}")
    for n,m in test_params.values:
        df = add_sma(df, n, m)
        res = backtest(df, n, m, shares)
        gain = res['change'].sum()
        results.loc[len(results)] = [n, m, gain]
    sorted_results = results.sort_values(by='gain', ascending=False)
    return sorted_results

ticker = "NVDA"
res_nvda = run_multiple_backtests(ticker, investment_sum = 1000)
res_nvda

虽然 SMA10/20 不如基线,但其他组合可以胜出。表 11.1 展示了英伟达 10 年历史下前五名的 SMA 交叉策略。最高 $309,821.27战胜“首日买入末日卖出”的基线。

表 11.1 NVIDIA 10 年历史上的 SMA 交叉策略前五名

短周期长周期收益($)
90120309,821.27
100110301,097.67
60160300,504.11
50110298,242.05
50180297,201.84

再测试其他公司,见表 11.2:在最佳参数下,该策略5 选 4有效。仍需注意:若策略首日高位买入、末日低位卖出,结果可能被扭曲

并非所有资产都能用 SMA 交叉跑赢基线。还要强调:不同券商的单笔交易成本不一,需要纳入;此外我们尚未讨论税务——每次卖出可能形成应税的资本利得,取决于你的税务居民身份。

表 11.2 多只个股的回测结果

代码短 SMA长 SMA收益($)基线(to beat)
KO1001106641,042
MMM120190810310
PFE1101801,243.55312.55
AAPL16017010,136.219,258.81
MSFT8014010,240.639,514.61

NOTE 如果你想少写代码,可看看 WorldQuant BRAIN 等平台。它们通常提供数据与脚本语言,用于像我们代码里的 if 规则一样定义alpha 候选,并自动回测。从一大堆候选里,用明确的买卖条件**“轮番轰炸”历史数据,直到找到过去有效的组合。使用这些平台可能涉及算力或授权费用**。

下一节我们会讨论另一个可能影响回测结果的细节:上述回测是按收盘价成交,但现实里日内波动显著,且**买一/卖一价差(bid-ask spread)**始终存在,你未必拿到理论最优价。

接下来我们会介绍如何用限价单尽可能有利的价格买卖。进入订单细节前,先以更复杂的规则为算法交易收个尾。

11.3.2 复杂交易信号(Complex trading signals)

为了演示基础的算法交易,我们用过 SMA 交叉策略来寻找可盈利的交易信号。现在来探索替代性的交易信号,以决定是否买入或卖出股票。

在我们的 Python 代码里,目前用两条分支决定买/卖。如果把
if row[f"sma_{lower}"] > row[f"sma_{upper}"]
替换成方法 isBuySignal(args),并以同样方式替换卖出条件,我们就能将更复杂的逻辑封装到买卖决策里。

我们可以按日甚至更短周期扫描数据寻找交易信号。这些信号未必要直接来源于所交易公司的自身数据;也可以评估一些地缘政治相关的指标(例如,总统宣布新一轮关税时,我们可能倾向于卖出将受影响的资产)。下面把“可量化的交易信号”定义为若干方法声明作为参考示例;它们也可与技术指标(如 SMA)组合使用:

def price_movement_score(assets: list)
# — 若参数中的资产整体走强,返回买入信号;否则返回卖出信号。

def keyword_score(channels: list, keywords: list, upper, lower)
# — 若得分超过上阈值则买入;低于下阈值则卖出。

def insider_score()
# — 基于内部人交易(机构或公司高管买卖自家股票)计算得分。

NOTE 加密货币交易者可以按持币数量划分地址群体(区块链是公开账本,持有人以地址标识)。若大额持有人将代币解锁/解除质押(unstake) ,很可能意味着即将抛售,这是一个强烈的卖出信号。

我们可以把多种信号的得分汇总到记分卡里做决策。前提是每个得分都可量化,且能与历史结果进行比较。

可比参数与历史数据(Comparable parameters and historical data)

通过 NLP,我们能对从不同来源收集的文本进行情绪打分。可用爬虫或 API 获取内容;若持续解析相同来源,就能观察情绪随时间的变化。某一天得到的结果,未来也可以可重复地得到。

然而,媒体来源会变化:有的渠道可能失效,也可能新增新来源。挑战在于,数据采集的样本池本身在变,因此将新结果旧结果对比会更复杂。

我们可以尝试不同的信号组合,并用回测看看哪种组合表现最佳。也可以把信号汇总为分析数据集(每种信号一列),基于历史数据运行监督学习来训练“好策略”。但需再次提醒:过拟合风险很高,而过往表现不代表未来表现

CAUTION 交易者必须留意潜在成本

  • 滑点(slippage) :计算机发出的入/出场价与真实资金在市场上成交的实际价之间的差异;
  • 交易成本:下单频繁会迅速累积;
  • 买卖价差(spread) :每一笔交易都存在点差。

我们还可以通过除信号之外的参数来提升复杂度。例如,依据决策参数动态调整交易股数;若风险承受度更高,可以使用杠杆融资买入,或采用卖空策略。

用算法迈入高级交易,就像进入职业拳击、对任何量级的对手都能开打——训练足够,你可能连战连捷、名利双收;否则,就像一个莽撞的轻量级去挑战重量级冠军,结果只会追悔莫及

11.4 订单(Orders)

接下来我们来看订单,因为我们希望用 Python 自动触发下单。先讨论交易所与券商的区别,再看看两家券商的 API。

11.4.1 交易所 vs. 券商(Exchanges vs. brokers)

图 11.4 概括了目前为止的要点。交易所撮合买卖双方并撮合成交;若能直接接入交易所,我们可“从源头”买入。但在股票与债券市场里,通常需要券商执行订单并托管持仓。也就是说,券商是投资者与交易所之间的中介

图 11.4:在交易所,买家与卖家可直接交互;而券商位于二者之间,通常连接一家或多家交易所并促成交易。

不同券商支持的交易所范围不同。大多数会支持美股主流交易所(NYSE、NASDAQ),也常支持 DAX、伦敦、日经等其他热门市场。但新兴市场并非家家覆盖;且各交易所的行情延迟费用结构等条件也可能不同。

加密货币交易所与股票交易所有本质差别。股票市场的典型特征包括:

  • 严格监管的市场,投资者买卖上市公司股票;
  • 上市公司在主板挂牌(如 NVIDIA 在美国 NASDAQ,受 SEC 监管);
  • 也可能在次级市场交易(如法兰克福/Xetra),但全球投资者可相信其仍需遵守主板监管与审计要求。

相对地,加密货币交易所多为私人公司,常注册于离岸或轻监管辖区,难以施加与上市公司等同的监管与投资者保护;若交易所挪用客户资金账务不规范,用户往往缺乏救济。部分国家/地区(如欧盟、日本)要求本地化持牌,能带来一定治理与保护,但总体风险依然较高。不少投资者因追逐高收益或信任未受监管的平台而遭受重大损失。

加密货币交易所的核心职能是提供在线撮合与托管平台,其 API 通常涵盖:

  • 下单与撮合;
  • 数字资产托管
  • 在各公链上签名并广播交易

与上市公司股票通常只在少数交易所挂牌不同,加密货币往往在多数主流加密交易所广泛可得,带来全球流动性的同时,也伴随碎片化监管运营质量参差

高频交易(HFT)
HFT 借助算法与低延迟基础设施,在毫秒级执行大量交易,捕捉极微小的价格低效或套利空间。其成败高度依赖算法精度超低时延机房同机架/同城部署等设施,且运营风险长期存在。对技术迷而言,知晓 HFT 的存在很有趣;但要参与,往往需要升级整套基础设施,甚至Python 都可能太慢而难以胜出。

11.4.2 订单修饰符(Order modifiers)

把市场想象成一个异步队列系统:你创建一个订单对象并提交,系统按你设置的执行逻辑去成交。你传入的参数(如立即成交到价成交)会影响执行表现。

本节先看订单类型,再看订单时效/模式(modalities) ,帮助你让执行更贴合投资目标。

订单类型(Order types)

按“触发方式”可将订单分为下列几类(表 11.3)——要么立即成交,要么到价触发

表 11.3 不同的买卖订单类型

  • 市价单(Market order) :相当于“立刻成交”。让系统以当前市场价买/卖。若用代码表达,可类比 executeTradeImmediately(ticker, amount)——传入代码与数量,系统即刻执行。
  • 限价单(Limit order) :当价格达到指定水平再成交。代码类比 executeTradeWhen(price <= targetPrice)
  • 止损单(Stop order / Stop-loss) :用于风险控制,当价格跌破某水平时触发市价单。类似 triggerTradeWhen(price <= stopPrice)。例如持股成本 100、担心利空,可在100、担心利空,可在 90 放置止损,跌破即自动卖出。
  • 止损限价单(Stop-limit) :价格到达触发价时,转而提交限价单。可理解为 triggerLimitTradeWhenPriceBetween(limitPrice, stopPrice)。风险在于跳空:若价格从 91直接跳到91 直接跳到 88,介于 8989–90 的触发区间被越过,订单可能未触发;若随后反弹至 $89.50,则被触发。
  • 追踪止损(Trailing stop)动态跟随股价上行,将止损价按固定回撤幅度上调;一旦自高位回落到设定幅度则触发。相当于 adjustStopPriceDynamically(trailAmount)

订单时效/模式(Order modalities)

作为附加参数适用于上面任一订单类型(表 11.4)。

表 11.4 订单时效/模式

  • FOK(Fill or Kill)要么全部立即成交,要么全部取消。避免分笔成交。
  • GTC(Good ’til Canceled) :订单跨日有效,直到成交或手动取消。常用于止损以长期保护底线。注意:并非永久,不同券商会设定到期时间(常见 0–90 天),请查阅你所用券商的规则。

11.4.3 执行订单(Executing orders)

下面用 Interactive Brokers(IB)Alpaca 的 Python API 演示下单。两家券商在第 6 章已介绍,这里不再赘述。

Interactive Brokers

第 6 章提到,使用 IB 的 Python API 需要先启动本地的 Trader Workstation(TWS) 。Python 通过该应用与 IB 后端通讯,可读行情也可下单

连接 TWS:

from ib_insync import *
util.startLoop()
ib = IB()
ib.connect()

定义合约并请求行情:

import pandas as pd

def get_data(market_data):
    print(f"Market Data: {market_data.ask}")
    print(f"askSize: {market_data.askSize}")
    print(f"marketPrice: {market_data.marketPrice()}")
    print(f"marketPrice: {market_data.time}")

contract_apple = Stock("AAPL", "SMART", "USD")
market_data = ib.reqMktData(contract_apple)
get_data(market_data)

获取到的市场数据包含**卖价(ask)**等信息。记住,买入价(ask)/卖出价(bid)之间通常存在点差,这也是券商的收入来源之一。

市价单:

order = MarketOrder(action = "BUY", totalQuantity = 1)
trade = ib.placeOrder(contract_apple, order)

限价单:

order = LimitOrder(action = "BUY", totalQuantity = 1, lmtPrice = 200)
trade = ib.placeOrder(contract, order)

IB 的一大特点是覆盖交易所多。但需注意:部分市场的实时深度/行情需要付费订阅;否则可能只得到延迟数据。

Alpaca

Alpaca 对开发者友好,API 体验良好。

初始化客户端与账户:

from alpaca.trading.client import TradingClient
from alpaca.trading.requests import GetAssetsRequest

k = get_secret("broker.alpaca.key")
s = get_secret("broker.alpaca.secret")

trading_client = TradingClient(k, s, paper=True)
account = trading_client.get_account()

这里使用 paper=True,意味着纸面模拟交易;去掉则为实盘

检索可交易标的(示例:纳斯达克美股、状态为 ACTIVE):

from alpaca.trading.enums import AssetClass, AssetExchange, AssetStatus
search_params = GetAssetsRequest(asset_class=AssetClass.US_EQUITY, 
   exchange=AssetExchange.NASDAQ, status=AssetStatus.ACTIVE)

assets = trading_client.get_all_assets(search_params)
print(f"{len(assets)} found")

检查某只股票是否可交易(以 SMR 为例):

trading_asset = trading_client.get_asset("SMR")

if trading_asset.tradable:
    print(f'We can trade {asset}.')

查询卖价(ask):

from alpaca.data import StockHistoricalDataClient
from alpaca.data.requests import StockLatestQuoteRequest
stock_client = StockHistoricalDataClient(k, s)
quote = StockLatestQuoteRequest(symbol_or_symbols="SMR")
latest_multisymbol_quotes = stock_client.get_stock_latest_quote(quote)
latest_ask_price = latest_multisymbol_quotes[symbol].ask_price
print(latest_ask_price)

提交市价单(示例:FOK 全成或撤):

from alpaca.trading.requests import OrderRequest
from alpaca.trading.enums import OrderSide, TimeInForce, OrderType

order_request = OrderRequest(
                   symbol="SMR",
                   qty = 2,
                   side = OrderSide.BUY,
                   type = OrderType.MARKET,
                   time_in_force = TimeInForce.FOK
                   )

new_order  = trading_client.submit_order(
               order_data=order_request
              )

提交限价单(示例:GTC 跨日有效):

from alpaca.trading.requests import LimitOrderRequest

limit_order_data = LimitOrderRequest(
                    symbol="SMR",
                    limit_price=8,
                    notional=2,
                    side=OrderSide.BUY,
                    time_in_force=TimeInForce.GTC
                   )
limit_order = trading_client.submit_order(
                order_data=limit_order_data
              )

以上示例展示了市价/限价FOK/GTC等常用参数的组合。实盘前务必确认交易成本最小成交量撮合规则时区/撮合时段等细节。

总结

  • 有效市场理论认为,市场价格已反映了所有投资者可获得的信息,因此我们无法长期跑赢市场。
  • 非财务数据可能是尚未被充分利用的资源,能揭示其他投资者尚未知晓的细节,蕴含战胜市场的隐藏信息。
  • 媒体信息、地理位置数据、人口统计与趋势等可作为模型因子,提供额外洞见。
  • 非财务数据不像上市公司必须披露的财务数据那样公开,但可在某些数据市场上付费获取。
  • **催化剂(Catalyst)**是会令股价大幅上/下波动的触发事件,包括并购(M&A)、破产申请、财报电话会、灾害以及利率变动。
  • 并购意味着两家公司合并为一体。这类重大事件常引发更大的股价波动;若能预判成功的并购,回报可观。
  • 破产同样可能带来机会:若原股东恐慌性抛售,价格被打穿,抄底或有利可图。但这风险极高;若存在**“领头竞标(stalking horse)”协议**(在拍卖未产生更高报价时以最低价收购资产的承诺),风险会相对降低。
  • 每家上市公司每季度都会举行财报电话会。是否符合或超越预期,常会带来显著的股价变化。
  • 灾难事件(战争、自然灾害等)会影响股价:有些公司在灾难期间反而利润更高。灾难初期买入相关受益标的,或在灾难结束前卖出,也是一种博取收益的策略。
  • 联邦基金利率(或他国等价利率)决定借款成本。资本密集型公司(高度依赖外部融资)对利率变化更为敏感。
  • 当利率高企、此类公司股价偏低时提前布局,在随后降息时可能受益;但在高利率环境下,一些资本密集型公司也面临破产风险
  • 可定义量化/算法交易策略,明确“何时买入、何时卖出”。
  • **回测(Backtesting)**用历史数据验证买卖策略的有效性。
  • 即便回测表现良好,历史业绩不代表未来结果
  • SMA(金叉/死叉)策略利用两个不同窗口的简单移动平均线生成买卖信号,但并不总能优于“低买高卖”的基准策略。
  • 可引入更复杂的规则与因子来评估买卖决策。
  • 虽可用 Python 自行编写与回测,但也有平台化服务可让不懂编程的用户完成这些工作。
  • 交易通常通过券商完成,券商再到交易所撮合成交,并以客户名义托管资产
  • 加密资产也可直接在加密交易所购买。
  • 交易所与券商通常对每笔订单收取佣金
  • 市价单:按当前价格立即成交
  • 限价单:在达到指定价格时成交;若当日未到价则不成交
  • 止损单:当价格越过设定的止损价时触发订单。
  • 止损限价单:价格触发后转为限价执行,要求价格位于设定的限价—触发价区间。
  • 追踪止损:止损价会随市场价格动态上移,当自高位出现剧烈回撤时触发。
  • FOK(全成或撤) :要求订单一次性全部成交,否则撤单,不接受部分成交。
  • GTC(有效直至取消) :订单跨日有效,直到成交或被下单者取消;部分券商会对长期未成交的 GTC 订单设定到期失效
  • GTC 与止损结合,可在市场崩跌时自动止损,避免巨额亏损。