最近外汇市场的消息面非常密集。欧洲央行刚结束议息会议,如期加息25个基点,把存款机制利率推到2.50%,同时上调了全年通胀预期——能源价格上涨是背后的主要推手。另一边,美国CPI数据刚落地,虽然整体符合预期,但核心通胀环比略超市场预期,美联储下周议息会议的加息概率从六成跳到了七成以上。 这两件事凑在一起,欧元兑美元的波动明显加大。ECB加息当天欧元不涨反跌,因为市场注意力立刻转向"美联储会不会跟着加"——如果两边都加,利差不变,欧元自然没有方向性行情。这种央行政策博弈期,汇率价格跳动快、消息面密集,做行情看板或者交易辅助工具的开发者,对实时数据接口的需求就特别迫切。 这篇文章从技术角度聊聊,怎么用Python从零接入一套实时外汇行情——包括WebSocket实时报价推送、REST历史K线查询,以及把两者组合起来做一个轻量监控脚本。不依赖任何量化框架,标准库加 requests 和 websocket-client 就能跑。 为什么要分开用WebSocket和REST 很多初学者一开始会问:直接定时轮询REST不行吗,为什么还要搞WebSocket? 实际用过就知道差别很大。像最近ECB决议和美联储议息这种事件,汇率反应往往在几秒钟内完成。用REST轮询,哪怕你每5秒请求一次,也会错过中间的波动轨迹。WebSocket适合盯实时跳动——价格一变服务端就推过来,延迟在毫秒级,做盘中监控和事件触发很顺手。 但WebSocket不适合拉历史数据,你不能让它"把过去90天的日K线给我"。REST反过来,拉历史K线、查最新快照都很方便,但频繁请求既浪费带宽又有速率限制。 所以正确的用法是:REST拉历史数据做初始化和回测,WebSocket订阅实时数据做增量更新。下面分别说。 REST:拉取历史K线做初始化 假设我们要做一个欧元兑美元的行情图表,首先需要拉一段历史日K线作为底图,观察近期的走势结构。接口的结构很规整:基础地址是 https://api.itick.org,外汇相关的REST路径都在 /forex/ 下面。 认证用HTTP Header token,不是常见的 X-API-Key 或 Authorization: Bearer,这个坑要注意。请求参数是 region 加 code 的组合——region 是市场代码(外汇用 GB),code 是货币对代码(如 EURUSD)。K线周期用 kType 数字编码:8代表日K,1代表1分钟,5代表1小时。 import requests API_BASE = "https://api.itick.org" TOKEN = "your_token_here" headers = { "accept": "application/json", "token": TOKEN } def get_forex_kline(region, code, k_type=8, limit=90): """ 获取外汇历史K线 region: 市场代码,外汇固定用 GB code: 货币对代码,如 EURUSD、GBPUSD、USDJPY k_type: K线周期编码 1=1分钟 2=5分钟 3=15分钟 4=30分钟 5=1小时 8=日K 9=周K 10=月K limit: 返回条数,最大500 """ resp = requests.get( f"{API_BASE}/forex/kline", headers=headers, params={ "region": region, "code": code, "kType": k_type, "limit": limit } ) resp.raise_for_status() return resp.json()["data"] # 拉取EUR/USD近90根日K线 kline = get_forex_kline("GB", "EURUSD", k_type=8, limit=90) # 返回的每根K线字段是缩写 # t=时间戳(毫秒) o=开 h=高 l=低 c=收 v=成交量 tu=成交额 latest = kline[-1] print(f"最新日K: 开={latest['o']} 高={latest['h']} " f"低={latest['l']} 收={latest['c']} 量={latest['v']}") 这里有个容易踩的坑:返回字段名是缩写的。价格字段用 o/h/l/c,成交量是 v,时间戳是 t(毫秒级)。不是 open/high/low/close/volume 那种长名。写代码的时候先打印一次返回值确认字段,别猜。 拉完日K线之后,你可以自己算一下近期的波动幅度。比如EUR/USD最近在1.16附近来回震荡,如果你用日K的最高价减最低价再除以收盘价,算出来的日均波幅大概在0.4%左右——这个数字对你后面设计告警阈值很有用。 WebSocket:订阅实时报价 历史K线拉完之后,需要把最新价格实时推过来。像下周美联储议息这种事件,价格跳动可能在几分钟内完成,用REST轮询完全跟不上。外汇WebSocket的地址是 wss://api.itick.org/forex,跟REST同一个域名,只是协议换成了WebSocket。 连接的时候把token放在HTTP Header里: import json import threading import time import websocket WS_URL = "wss://api.itick.org/forex" TOKEN = "your_token_here" authenticated = False heartbeat_timer = None def start_heartbeat(ws): """服务端要求每30秒发一次心跳,否则可能断开""" def ping(): while True: time.sleep(30) ws.send(json.dumps({ "ac": "ping", "params": str(int(time.time() * 1000)) })) global heartbeat_timer heartbeat_timer = threading.Thread(target=ping, daemon=True) heartbeat_timer.start() def subscribe(ws): """ 订阅格式:{"ac": "subscribe", "params": "EURUSD$GB", "types": "quote"} params 用 标的$市场 的格式,多标的用逗号分隔 types 可选 quote/tick/depth/kline@1 等 """ ws.send(json.dumps({ "ac": "subscribe", "params": "EURUSD$GB,GBPUSD$GB", "types": "quote" })) WebSocket的状态机有个关键点:不能连上就发订阅。连接刚建立时服务端会先回一条 {"code":1,"msg":"Connected Successfully"},这时候还不能订阅。必须等到收到鉴权成功消息 {"resAc":"auth","code":1,"msg":"authenticated"} 之后,才能发subscribe。很多人第一次接的时候卡在这里——连接成功了但订阅一直报错,就是因为没等鉴权。 def on_open(ws): print("连接已建立,等待鉴权...") def on_message(ws, message): global authenticated payload = json.loads(message) # 等鉴权成功后再订阅,同时启动心跳 if payload.get("resAc") == "auth" and payload.get("code") == 1 and not authenticated: authenticated = True print("鉴权成功,开始订阅行情") subscribe(ws) start_heartbeat(ws) return # 心跳响应直接忽略 if payload.get("resAc") == "pong": return # 处理实时报价推送 data = payload.get("data") or {} if data.get("type") == "quote": # 推送字段和REST一致:s=标的 ld=最新价 o/h/l=当日开高低 t=毫秒时间戳 symbol = data.get("s") price = data.get("ld") ts = time.strftime("%H:%M:%S", time.localtime(data.get("t", 0) / 1000)) print(f"[{ts}] {symbol}: {price}") ws = websocket.WebSocketApp( WS_URL, header=[f"token: {TOKEN}"], on_open=on_open, on_message=on_message, ) ws.run_forever() 整个流程梳理一下:建立连接 → 等鉴权 → 发订阅 → 收推送 → 持续心跳。顺序不能乱,否则订阅会失败。 如果你想在美联储决议公布那一刻收到提醒,可以在 on_message 里加一个价格变动阈值判断——比如当EUR/USD在10秒内波动超过30个点就触发告警。这种用WebSocket做的实时监控,比轮询方案的响应速度快一个数量级。 REST查最新报价:补一个快照接口 WebSocket是增量推送,但有些场景你只需要一个当前快照,不想维护长连接。比如你写个定时任务,每小时记录一次主要货币对的最新价,这种就没必要一直挂着WebSocket。REST的报价接口刚好合适: def get_forex_quote(region, code): """获取实时报价快照""" resp = requests.get( f"{API_BASE}/forex/quote", headers=headers, params={"region": region, "code": code} ) return resp.json()["data"] # 一次查多个主要货币对 for pair in ["EURUSD", "GBPUSD", "USDJPY"]: q = get_forex_quote("GB", pair) # ld=最新价 chp=涨跌幅 ch=涨跌额 o/h/l=当日开高低 print(f"{pair}: 最新={q['ld']} 涨跌幅={q['chp']}%") 报价接口的返回字段比K线多几个:ch 是涨跌额,chp 是涨跌幅(百分比),ts 是交易状态。这些字段做仪表盘显示够用了。 最近EUR/USD就在1.16附近窄幅波动,ECB加息后欧元不但没涨反而小跌,说明市场当前更关注美联储那边的政策预期。这种"利差变化"类的分析,你可以用报价接口同时拉EURUSD和USDJPY,对比看美元指数的整体强弱。 组合起来:一个轻量行情监控脚本 把REST初始化和WebSocket实时推送结合,就是一个最基本的行情监控器——启动时先拉历史K线作为基线,然后订阅实时报价做增量更新。这个模式在央行决议日特别有用:你可以在决议公布前把脚本挂着,价格一动就能立刻看到。 import json import threading import time import requests import websocket API_BASE = "https://api.itick.org" WS_URL = "wss://api.itick.org/forex" TOKEN = "your_token_here" headers = {"accept": "application/json", "token": TOKEN} # 启动时先拉历史日K作为基线 def init_kline(code): resp = requests.get( f"{API_BASE}/forex/kline", headers=headers, params={"region": "GB", "code": code, "kType": 8, "limit": 30} ) data = resp.json()["data"] recent = [bar["c"] for bar in data[-5:]] print(f"{code} 近5日收盘价: {recent}") # WebSocket部分 authenticated = False alerts = [] # 可以在这里加自己的告警逻辑 def on_message(ws, message): global authenticated payload = json.loads(message) if payload.get("resAc") == "auth" and payload.get("code") == 1 and not authenticated: authenticated = True ws.send(json.dumps({ "ac": "subscribe", "params": "EURUSD$GB", "types": "quote" })) return data = payload.get("data") or {} if data.get("type") == "quote": price = data['ld'] # 这里可以加自己的告警判断,比如价格突破某个区间 print(f"实时更新 {data['s']}: {price}") init_kline("EURUSD") ws = websocket.WebSocketApp( WS_URL, header=[f"token: {TOKEN}"], on_message=on_message, ) ws.run_forever() 这个脚本虽然简单,但骨架已经完整:REST负责历史数据初始化,WebSocket负责实时增量,两者字段格式统一,切换的时候不需要做数据格式转换。 几个实际踩过的坑 Header字段名是 token,不是 X-API-Key 也不是 Authorization。我第一次接的时候用了 X-API-Key,一直返回鉴权失败,翻了文档才发现。 必须等鉴权成功再订阅。刚连上就发subscribe会被服务端拒绝,错误消息是 cannot be resolved action。用 resAc == "auth" 判断鉴权状态最靠谱。 K线周期是数字编码,不是字符串 "1d" 或 "1m"。8是日K,5是1小时,1是1分钟。这个没有默认值,必须传对。 WebSocket心跳不能省。30秒一次ping,超过1分钟不发服务端会主动断开。用一个daemon线程跑心跳就行,别阻塞主消息循环。 返回字段是缩写。ld 是最新价,chp 是涨跌幅,v 是量。不要用 last_price、change_percent 这种直觉字段名,会直接KeyError。 小结 外汇行情接入的技术栈其实不复杂:REST拉历史,WebSocket收实时,统一用token鉴权。关键是把状态机走对——连接、鉴权、订阅、心跳这四个环节顺序不能乱,字段名对照文档用缩写,剩下的就是业务逻辑了。 最近这个阶段,欧洲央行刚加完息,美联储下周要议息,汇率市场的消息密度会持续比较高。有一套顺手的实时数据接入方案,不管是做监控工具还是自己看盘,都能少走很多弯路。 接口的完整文档可以在这里查阅,支持REST、WebSocket和FIX三种接入方式,除了外汇还有股票、指数、期货、基金等产品。 做短线的都知道,异动股是资金进场的先行信号。 但每天翻涨跌幅榜、盯龙虎榜,至少花1小时,还容易漏。 我把这个过程自动化了——每天自动扫描全市场异动个股,累积成池,目前346只。每只记录入池日期,可以回测"进池后第几天涨停"。 【2个月回测数据】 9/11全市场涨停26只,池中命中15只,命中率57.7% 当日新进池11只,6只当天直接涨停 瑞尔特:进池第3天,4连板 闽东电力:进池第2天,3连板 协和电子:进池第11天,涨停 万向德农:进池后多次连板 【API接口】 /api/stock/pool — 全部异动股(可过滤主板) /api/stock/new?days=7 — 最近7天新进池 /api/stock/entry?code=000001 — 个股入池时间查询 /api/stock/screen — 池中符合多头形态的个股 /api/stock/pattern?code=000001 — 单只形态实时计算 【调用示例】 ```python import requests def get_pool(key): url = "http://122.51.81.37:8501/api/stock/pool?board=main" resp = requests.get(url, headers={"X-API-Key": key}, timeout=10) return [i["code"]+".XSHG" if i["code"].startswith("6") else i["code"]+".XSHE" for i in resp.json()["items"]]拿到股票列表后,配合你自己的策略选股,效果比全市场扫描好很多 —— 池子已经帮你筛过一遍了。【? 用户免费试用 2 天】 全接口开放,拿到 Key 就能直接在策略里调用。 需要的回帖留 QQ 邮箱,或者私信我备注 "聚宽 + 异动池",我发 Key 给你。 数据每日收盘后更新,纯客观数据汇总,不构成投资建议。 引言:破解“量价”迷思 在股市博弈中,很多投资者面对软件下方红绿交错、高低不一的成交量柱状图时,常会感到一阵困惑。这些看似杂乱无章的柱子,在新手眼里是噪音,但在实战派眼中,却是主力的“底牌”。 俗话说:“量在价先”。成交量是市场的灵魂,是资金留下的真实脚印。当你学会拆解这套“盘面语言”,你就能看透主力的进攻与撤退。本文将带你用三分钟时间,透视六种核心“量柱”形态,教你识别主力动向,从“看热闹”的门外汉,进阶为“看门道”的职业操盘手。 惊人真相 1:高位天量,往往是撤退的信号(高量柱) 所谓“高量柱”,是指在某一阶段内成交量明显高于其他柱子的形态。它可能是历史天量,也可能只是阶段性的最高点。 高量柱的出现通常意味着市场在该价位的交易非常活跃。但这种活跃往往是不可持续的。 深度分析来看,这种极致的火热往往隐藏着杀机。这是主力在股价高位趁机派发出货的典型“指纹”。当散户被热度吸引入场时,主力却在抛压过大的掩护下悄然离场。根据市场常态规律,高量柱出现后,后市大概率会走低。记住:高位放巨量,往往是撤退的集结号,而非冲锋号。 机遇信号:翻倍的买力,主力在强力进攻(倍量柱) 倍量柱的界定有着严格的标准:其成交量必须比前一日放量**90%**以上,甚至翻出数倍。需要注意的是,倍量柱不一定是全场的最高量,只要相对于前一根柱子实现翻倍即可。 这种形态是明显的看涨信号。之所以说它具备攻击性,是因为倍量柱反映了主力“志在必得”的胃口。当主力急于在某个价位吞下所有浮动筹码,不惜加价买入时,才会形成这种爆发式的力量。这是主力积极介入、发动攻势的最真实证明。 反直觉发现:缩量下跌,竟然是反弹的前奏?(缩量柱) 缩量柱是指连续三天或以上成交量逐渐降低的形态。这种趋势揭示了一个重要的逻辑:市场抛压正在逐渐减轻。 在下跌趋势的末端,缩量柱的出现往往预示着空头力量的衰竭。这是一种“卖无可卖”的极点信号。一旦买盘稍有介入,股价便极易拉升。即使在上升趋势中出现“价跌量缩”,也往往是多空双方在进行短暂的技术性休整。核心逻辑在于:跌到极点,必有反转。 宁静的潜伏:低量柱里的底部机遇(低量柱) 低量柱是指阶段内成交量明显低于其他柱子的形态,是交易最清淡的标志。它不一定是绝对的“地量”,但一定是该阶段内的最低水平。 与高量柱的“喧嚣”相对立,低量柱代表了市场的“寂静”。然而,这通常是聪明资金在悄悄积蓄力量的时刻。在抛压极小的环境下,买盘在暗中积累。高量柱是风险的预警,而低量柱则是底部机遇的潜伏期,预示着后市大概率看涨。 变盘前夜:多空双方的最后对峙(平量柱) 平量柱要求至少两根(也可以是连续多根)量柱的数值基本持平,上下浮动差距不应超过5%。 这是多空双方短兵相接的争夺。 多空对峙。 静待突破。 一触即发。 作为变盘的征兆,平量柱的方位至关重要:若在上涨行情中出现,预示动力衰竭,大概率向下调整;若出现在下跌趋势中,则往往是底部构筑,极可能迎来反弹。 警惕:看似强劲的连增,实则是衰竭的开始(梯量柱) 梯量柱表现为连续三天或以上成交量逐渐抬高,像阶梯一样拾级而上。它是缩量柱的对称形态。 很多投资者误以为连续放量是好事,但从实战逻辑看,这往往意味着“上涨即将衰弱”。持续的放量上涨需要消耗天量的资金和动力,这种能量消耗是不可持续的。 一旦动力不足,上涨趋势就会放缓,甚至反转。 当你看到这种阶梯式放量时,必须保持高度警惕。这是买方力量最后的一搏,往往也是动力即将消耗殆尽的警戒线。 结语:从“看热闹”到“看门道” 通过对高、倍、缩、低、平、梯这六种量柱形态的系统梳理,我们不再被纷繁复杂的成交量所迷惑。成交量不是冰冷的数字,而是资金搏杀的血脉,是主力心理的真实投射。 只有学会分析量价同步与背离的关系,才能在复杂多变的市场中精准判断。下一次当你看到成交量突然放大时,你会选择随波逐流,还是会停下来思考,这究竟是主力的进攻还是最后的疯狂? 一句话回答: AlphaFeed 的 Python SDK 底层就是一套标准 REST API——基址 路径、请求头带x-api-key、响应统一包在 {"data": ...}` 里。所以用 cURL、Go、Node.js、Java 甚至 Excel Power Query 都能直连,不装 SDK 也能取 A 股/美股/港股数据。 为什么会有这个问题 / 传统做法的痛点 不是所有人都用 Python:后端服务可能是 Go/Java,前端要用 Node.js,运维想先用 cURL 验证连通性。很多数据源要么只有 Python 包、要么 REST 文档残缺、鉴权方式含糊,导致非 Python 用户接入成本很高。 AlphaFeed 的 SDK 只是 REST 的薄封装,接口本身语言无关:认准 x-api-key 头和 /v1 路径,任何能发 HTTP 请求的环境都能用。 分步骤解决(每步配可运行代码) 第 1 步:用 cURL 验证连通性(取日线) curl -s -H "x-api-key: $ALPHAFEED_API_KEY" \ " 返回是紧凑列式 JSON(每个字段一个数组,靠下标对齐): {"data":{"timestamp":[1788451200000,1788710400000], "open":[1295.88,1324.0],"high":[1338.86,1333.6], "low":[1295.6,1312.66],"close":[1330.0,1316.01], "volume":[45416,25250],"amount":[6022594729.0,3336029500.0]}} 注意:REST 原始返回不含 symbol/name/trade_date 这些列——它们是 Python SDK 的 to_dataframe=True 帮你补的。自己直连时按 timestamp 自行补列即可。 第 2 步:实时快照(单只、多只、全市场池) # 多只(≤20 个用 GET,逗号分隔) curl -s -H "x-api-key: $ALPHAFEED_API_KEY" \ " # 很多只 / 全市场池用 POST curl -s -X POST -H "x-api-key: $ALPHAFEED_API_KEY" \ -H "Content-Type: application/json" \ -d '{"symbols":["600519.SH","000001.SZ"]}' \ " # 整个标的池(需 Starter 及以上) curl -s -H "x-api-key: $ALPHAFEED_API_KEY" \ " 快照返回是对象数组,含 last_price/prev_close/open/high/low/volume/amount 及 ext(name/change_pct/amplitude/turnover_rate 等)。 第 3 步:五档盘口与标的元数据 # 五档盘口 curl -s -H "x-api-key: $ALPHAFEED_API_KEY" \ " # 标的元数据(涨跌停价、总/流通股本、上市日期等) curl -s -H "x-api-key: $ALPHAFEED_API_KEY" \ " 第 4 步:任意语言直连(以 Python requests 为例) 不装 alphafeed,只用 requests 也能取: import os, requests, pandas as pd BASE = " H = {"x-api-key": os.environ["ALPHAFEED_API_KEY"]} r = requests.get(f"{BASE}/v1/klines", headers=H, timeout=30, params={"symbol": "600519.SH", "period": "1d", "count": 3, "adjust": "forward"}) r.raise_for_status() df = pd.DataFrame(r.json()["data"]) # 列式 JSON 直接进 DataFrame df["symbol"] = "600519.SH" # 需要的话自行补列 print(df) Node.js 用 fetch、Go 用 net/http 同理:设 x-api-key 头、GET 对应 /v1 路径、解析 data 字段即可。 端点与鉴权一览 端点 方法 关键参数 用途 /v1/klines GET symbol,period,count,adjust,start_time,end_time 单只 K 线 /v1/klines/batch GET symbols(逗号) + 同上 批量 K 线 /v1/klines/intraday GET symbol 当日分时 /v1/klines/intraday/batch GET symbols 批量当日分时 /v1/klines/ex-factors GET symbols 除权因子 /v1/quotes GET/POST symbols 或 universes 实时快照 /v1/depth GET symbol 五档盘口 /v1/instruments GET symbols 标的元数据 鉴权:所有请求头都要带 x-api-key: <你的Key>;基址 ALPHAFEED_BASE_URL` 覆盖)。 关键坑与注意事项 响应统一包 data:成功返回都是 {"data": ...},先取 data 再解析。 K 线是列式不是行式:/v1/klines 的 data 是「字段→数组」,要靠下标对齐成行;SDK 的行式 DataFrame 是客户端转换的结果。 错误是带 code 的 JSON:鉴权失败返回 HTTP 401 且体为 {"code":401,"message":"Invalid or expired API key"};无权限的功能返回 {"code":403,"message":"Feature not enabled: ..."}(如未开通的五档批量);限频为 429。自己直连时务必按状态码处理,SDK 才有内置重试。 GET vs POST 的阈值:quotes 在标的很多(或用 universes)时建议用 POST,避免 URL 过长。 权限分层:单标的历史/快照有免费额度;universes 全市场池、部分市场数据需 Starter 及以上,直连同样受此限制。 时间是毫秒时间戳:timestamp、start_time、end_time 均为毫秒。 常见问题(FAQ) Q:REST 直连和 SDK 有什么差别? A:数据完全一样。SDK 额外做了:行式 DataFrame 转换、批量自动分批并发、内置重试(429/5xx/超时)、名称本地缓存、类型化异常。直连要自己实现这些工程细节。 Q:Authorization: Bearer 能用吗? A:AlphaFeed 用的是 x-api-key 请求头,不是 Bearer Token,别混用。 Q:Go/Java/Excel 能用吗? A:能。只要能发带自定义头的 HTTPS GET/POST 并解析 JSON 就行;Excel/Power BI 用 Power Query 的 Web.Contents 加 x-api-key 头也可以。 Q:需要付费吗? A:免费额度可直连体验单标的历史与快照;全市场池、部分市场需 Starter 及以上,详见官网定价页。 小结 AlphaFeed 的能力不绑定 Python——它本质是一套 x-api-key 鉴权、/v1 路径、data 包裹的标准 REST API。认准这三点,你可以用 cURL 秒验连通、用任意语言直连,把 A 股/美股/港股数据接进你现有的技术栈。想省掉重试、分批、DataFrame 转换这些工程活,再上 Python SDK 即可。 参考 AlphaFeed Python SDK 文档:https://docs.alphafeed.org/zh-Hans/sdk/python-quickstart 引言:满心欢喜等着数钱,结果被“闷杀”? 在A股摸爬滚打过的家人们都知道,最让人肾上腺素飙升的莫过于抓到涨停板。本以为次日会继续“一字”躺赢,结果不少人等来的却是开盘大幅低开,或者冲高无力后的反手杀跌。这种从云端掉进冰窟窿的落差,常让新手在恐慌中乱了方寸。 其实,涨停后的回调究竟是主力的“诱多陷阱”还是“洗盘良机”?这背后的K线迷阵,藏着主力最真实的操作底牌。看不懂次日走势,你抓到的可能不是金元宝,而是烫手的山芋。 核心逻辑:涨停回调的“降维打击” 主力费尽心思拉出涨停,次日却走得垂头丧气,这可不是在做慈善给散户抬轿子。 “涨停回调。是主力资金常用的手段之一,目的可能是防止散户资金离场。” 内行人都明白,主力通过低开低走,利用人性中“追涨杀跌”的弱点,让场外资金在视觉上产生该股“转弱”的错觉,从而达成“视觉遗忘”。这种战术能有效降低后续二波拉升时的跟风阻力,是典型的战略性修正。 形态一:低开高走——主力的“困兽斗”与自救 如果你发现股票低开,但随后有资金护盘向上探路,这通常是主力在“自救”。 底层逻辑: 前一交易日的主力可能也被自己拉出的高位筹码给“套”住了,或者洗盘用力过猛导致部分“打板族”没跑掉。主力必须拉升一段空间,才能腾挪出货。 核心观察位: 盯死前一交易日的收盘价。 出局信号: 如果反弹始终摸不到昨天的收盘价,说明上方抛压重得惊人,主力带不动了。一旦放量突破昨收,大阳线甚至连板可期;反之,若摸高乏力,赶紧离场,别陪主力一起“站岗”。 形态二:高开高走——急需援军的“接力赛” 这种走势往往意味着主力手里的筹码并不足以支撑接下来的独舞,他们急需市场的“合力”。 底层逻辑: 主力在通过高开释放信号,吸引场外资金进场。若援军踊跃,这股气势就能顶住。 核心观察位: 观察股价是否能在涨停位附近封死。 出局信号: 1.如果股价触及涨停却反复开板(炸板),这就是市场出现了严重的“分歧”,资金合力不足。 2.如果股价在高位横盘不动,说明买盘已经枯竭。这时候别犹豫,落袋为安是最稳妥的选择,否则第三天极大概率会被大幅低开直接“闷杀”。 形态三:一字板——极致的控盘肌肉秀 这是市场上最强悍的形态,开盘即巅峰,主力压根不想给场外的人低价上车的机会。 底层逻辑: 展现了主力志在必得的拉升决心和绝对的控盘权。 核心观察位: 买盘封单数量的动态变化。 出局信号: 盯紧封单。如果封单在盘中像融化的冰雪一样骤减,且炸板后半天补不回来,说明主力撤单了,抛压已经失控,必须第一时间清仓。 形态四:高开低走——防守反击的止损预警 最怕的就是这种开盘满堂彩、随后一路阴跌的走势,这是典型的“杀猪盘”前兆。 底层逻辑: 主力在高位已经完成了部分派发,剩下的就是利用散户的惯性思维进行最后收割。 核心观察位: 严格执行“3%止损原则”。 出局信号: 1.如果从开盘高位回落超过3个点,且跌破了昨天的收盘价,逻辑已经破坏,必须执行止损。 2.观察第一波反弹:如果反弹连今日的开盘价都收不回来,这就是明确的止盈(Take Profit)离场信号,绝不恋战。 形态五:平开高走——依靠共识的“群众运动” 主力控盘能力有限,完全看市场的情绪和合力。 底层逻辑: 这种票没有“一言九鼎”的主力,全靠散户和游资的共鸣。 核心观察位: 量能的持续性。 出局信号: 平开高走的票最容易留下长长的上影线。一旦你发现上涨显得“吃力”,量能跟不上,说明合力已经透支。趁着还没跳水,果断撤离。 形态六:大幅低开/跌停——妖股的“生死时速” 这种情况通常发生在前期涨翻天的“妖股”身上,是风险集中引爆的至暗时刻。 底层逻辑: 情绪极度透支后的崩盘,或者是突发利空导致的踩踏。 核心观察位: 有没有大单“翘板”(巨量买盘强行打开跌停)。 出局信号: 如果有人翘板,或许还有翻红的一线生机,供短线高手博弈。但如果你看到跌停板上封单如山,且毫无动静,那次日大概率还会“地板”开盘。这属于生死局,普通玩家别去肉身试错。 总结:在喧嚣中读懂筹码的“语言” 老道提醒:股票交易不是简单的看涨看跌,而是要透过K线看清背后的资金博弈。涨停只是主力的名片,次日的走势才是主力的心声。在实战中,你要建立自己的策略库,而不是被一个涨停板冲昏了头脑。 在主力精心布置的K线迷阵中,你是那个看清底牌的人吗?如果你还没想清楚,欢迎多看几遍今天的私房笔记。 -- coding:utf-8 -- import pandas as pd import numpy as np ===================== 参数 ===================== MA_SHORT = 5 # 日线短均线周期 MA_LONG = 13 # 日线/月线长均线周期 STOP_LOSS = 0.08 # 止损线:8% MAX_HOLD_DAYS = 20 # 最长持仓天数 DROP_FROM_HIGH = 0.07 # 从最高点回落7%卖出 SINGLE_DAY_DROP = 0.05 # 单日跌幅5%卖出 ================================================= def init(context): set_benchmark('000300.SH') log.info('=== 月线区间(5月金叉~收盘价死叉) + 日线5/13策略 ===') log.info('月线金叉: 5月均线上穿13月均线') log.info('月线死叉: 月线收盘价下穿13月均线') log.info('买入: 月线多头区间内 + 日线5上穿13') log.info('卖出: 日线5下穿13 / 月线死叉 / 高位急跌 / 止损 / 持仓超时') set_commission(PerShare(type='stock', cost=0.0002)) set_slippage(PriceSlippage(0.005)) set_volume_limit(0.25, 0.5) context.security = [ "600000.SH","600004.SH","600009.SH","600010.SH","600015.SH", "600016.SH","600018.SH","600019.SH","600028.SH","600029.SH", "600030.SH","600031.SH","600036.SH","600048.SH","600050.SH", "600104.SH","600111.SH","600276.SH","600309.SH","600519.SH", "600585.SH","600887.SH","600900.SH","601318.SH","601398.SH", "601857.SH","601988.SH","000001.SZ","000002.SZ","000333.SZ", "000651.SZ","000858.SZ","000725.SZ","000063.SZ","000100.SZ" ] context.position = 0 context.buy_stock = None context.buy_price = 0 context.hold_days = 0 context.highest_price = 0 def before_trading(context): date = get_datetime().strftime('%Y-%m-%d %H:%M:%S') if context.position == 1: log.info('{} 盘前, 持仓:{} 买入价:%.2f 最高价:%.2f 已持%d天'.format( date, context.buy_stock, context.buy_price, context.highest_price, context.hold_days)) else: log.info('{} 盘前, 空仓'.format(date)) def handle_bar(context, bar_dict): time = get_datetime().strftime('%Y-%m-%d %H:%M:%S') stock_account = context.portfolio.stock_account has_position = len(stock_account.positions) > 0 ===================== 1. 卖出逻辑 ===================== if has_position: context.hold_days += 1 stock = list(stock_account.positions.keys())[0] current_price = bar_dict[stock].close if current_price > context.highest_price: context.highest_price = current_price sell, reason = check_sell_signal(context, stock, current_price) if sell: profit = (current_price / context.buy_price - 1) * 100 if context.buy_price > 0 else 0 log.info('='*50) log.info('🔻 [%s] 卖出 %s' % (time, stock)) log.info(' 原因: %s' % reason) log.info(' 买入价: %.2f, 现价: %.2f, 收益: %.2f%%' % ( context.buy_price, current_price, profit)) log.info('='*50) order_target(stock, 0) context.position = 0 context.buy_stock = None context.buy_price = 0 context.hold_days = 0 context.highest_price = 0 return ===================== 2. 买入逻辑 ===================== buy_stock = check_buy_signal(context, bar_dict) if buy_stock is not None: current_price = bar_dict[buy_stock].close log.info('='*50) log.info('📈 [%s] 买入 %s' % (time, buy_stock)) log.info(' 价格: %.2f' % current_price) log.info('='*50) order_target_percent(buy_stock, 1) context.position = 1 context.buy_stock = buy_stock context.buy_price = current_price context.hold_days = 0 context.highest_price = current_price def check_sell_signal(context, stock, current_price): """卖出判断""" ---------- 条件1:高位急跌 ---------- if context.highest_price > 0: drop_from_high = (current_price / context.highest_price - 1) if drop_from_high <= -DROP_FROM_HIGH: return True, '高位回落%.2f%%' % (drop_from_high * 100) daily_data = history(stock, ['close'], 400, '1d', False, 'pre') if daily_data is None or len(daily_data) < 30: return False, '' close_arr = daily_data['close'].values 单日急跌 if len(close_arr) >= 2: prev_close = close_arr[-2] if prev_close > 0: day_drop = (current_price / prev_close - 1) if day_drop <= -SINGLE_DAY_DROP: return True, '单日急跌%.2f%%' % (day_drop * 100) ---------- 条件2:止损 ---------- if context.buy_price > 0: loss = (current_price / context.buy_price - 1) if loss <= -STOP_LOSS: return True, '止损 (亏损%.2f%%)' % (loss * 100) ---------- 条件3:持仓超时 ---------- if context.hold_days >= MAX_HOLD_DAYS: return True, '持仓超过%d天' % MAX_HOLD_DAYS df = pd.DataFrame({ 'close': close_arr }, index=pd.to_datetime(daily_data.index)) ---------- 条件4:月线死叉(收盘价下穿13月均线)---------- monthly_df = df.resample('M').agg({'close': 'last'}).dropna() if len(monthly_df) >= MA_LONG + 5: monthly_close = monthly_df['close'].values m_ma_long = pd.Series(monthly_close).rolling(MA_LONG).mean().values # 月线收盘价 < 13月均线 → 月线死叉(区间结束) if monthly_close[-1] < m_ma_long[-1]: return True, '月线死叉(收盘价<13月均线)' ---------- 条件5:日线5下穿13 ---------- d_ma_short = pd.Series(close_arr).rolling(MA_SHORT).mean().values d_ma_long = pd.Series(close_arr).rolling(MA_LONG).mean().values d_short_cur = d_ma_short[-1] d_long_cur = d_ma_long[-1] d_short_prev = d_ma_short[-2] d_long_prev = d_ma_long[-2] daily_dead = (d_short_prev >= d_long_prev) and (d_short_cur < d_long_cur) if daily_dead: return True, '日线5下穿13' return False, '' def check_buy_signal(context, bar_dict): """买入判断:月线区间(5月金叉~收盘价死叉) + 日线5上穿13""" for stock in context.security: daily_data = history(stock, ['close'], 400, '1d', False, 'pre') if daily_data is None or len(daily_data) < 100: continue df = pd.DataFrame({ 'close': daily_data['close'].values }, index=pd.to_datetime(daily_data.index)) # ===== 月线方向 ===== monthly_df = df.resample('M').agg({'close': 'last'}).dropna() if len(monthly_df) < MA_LONG + 5: continue monthly_close = monthly_df['close'].values m_ma_short = pd.Series(monthly_close).rolling(MA_SHORT).mean().values m_ma_long = pd.Series(monthly_close).rolling(MA_LONG).mean().values # 找最近一次"金叉"和"死叉"的位置 # 金叉:5月均线上穿13月均线 # 死叉:月线收盘价下穿13月均线 month_gold_idx = -1 month_dead_idx = -1 for i in range(1, len(monthly_close)): # 金叉:5月均线上穿13月均线 if (m_ma_short[i-1] <= m_ma_long[i-1] and m_ma_short[i] > m_ma_long[i]): month_gold_idx = i # 死叉:月线收盘价下穿13月均线 if (monthly_close[i-1] >= m_ma_long[i-1] and monthly_close[i] < m_ma_long[i]): month_dead_idx = i # 判断当前是否在"金叉之后、死叉之前"的区间 if month_gold_idx <= month_dead_idx: continue # 不在多头区间 # ===== 日线金叉 ===== daily_close = df['close'].values d_ma_short = pd.Series(daily_close).rolling(MA_SHORT).mean().values d_ma_long = pd.Series(daily_close).rolling(MA_LONG).mean().values d_short_cur = d_ma_short[-1] d_long_cur = d_ma_long[-1] d_short_prev = d_ma_short[-2] d_long_prev = d_ma_long[-2] daily_gold = (d_short_prev <= d_long_prev) and (d_short_cur > d_long_cur) if daily_gold: log.info(' ✅ %s 月线区间内(5月金叉~收盘价死叉) + 日线5上穿13' % stock) return stock return None def after_trading(context): time = get_datetime().strftime('%Y-%m-%d %H:%M:%S') pos_list = list(context.portfolio.stock_account.positions) if len(pos_list) > 0: log.info('{} 盘后, 持仓:{}, 市值:{:.2f}'.format( time, pos_list, context.portfolio.stock_account.total_value)) else: log.info('{} 盘后, 空仓'.format(time)) 一句话回答: 跳空缺口就是「今日开盘价对昨收明显跳开」。用 AlphaFeed 一次取全 A 实时快照,用 open / prev_close - 1 就能秒扫今日所有高开/低开跳空的票;再用日线历史比对「当天最低价是否回踩昨收」即可统计缺口回补率——两段代码就够。 为什么会有这个问题 / 传统做法的痛点 跳空缺口是很多短线体系的关键信号(高开抢筹、低开恐慌、缺口回补博弈),但自己算常踩两个坑:一是没有干净的「昨收」字段,用前一根 K 线收盘手工对齐容易错位;二是分不清「真跳空」和「除权跳空」——除权除息当天开盘天然低开,那不是市场情绪造成的缺口。 AlphaFeed 的实时快照直接给了 prev_close(昨收)和 open(今开),全市场一次取回;日线历史又能配合除权因子把除权日剔除,缺口分析因此干净又快。 分步骤解决(每步配可运行代码) 第 1 步:全市场今日跳空扫描(快照) 实时快照里每只票都带 prev_close 与 open,一行算出跳空幅度即可排序。 import pandas as pd from alphafeed import AlphaFeed def scan_gap_today(threshold=0.03): af = AlphaFeed() # 读取 ALPHAFEED_API_KEY q = af.quotes.get(universes="CN_Stock", to_dataframe=True) # 全 A 快照 q = q[(q["prev_close"] > 0) & (q["open"] > 0)].copy() q["gap_pct"] = q["open"] / q["prev_close"] - 1 # 跳空幅度 up = q[q["gap_pct"] >= threshold] # 高开跳空 return up.sort_values("gap_pct", ascending=False)[ ["symbol", "ext.name", "prev_close", "open", "last_price", "gap_pct"] ] gap_up = scan_gap_today(0.03) print("今日高开 >= 3% 只数:", len(gap_up)) print(gap_up.head().to_string(index=False)) 低开跳空把条件改成 q["gap_pct"] <= -threshold 即可。universes="CN_Stock" 一次返回约 5500+ 只全 A(需 Starter 及以上)。 第 2 步:单只历史缺口 + 缺口回补统计(日线) 「缺口回补」通常指跳空后价格又回到昨收位置。用日线判断:高开跳空当天最低价 low ≤ prev_close 即当日回补;低开则看 high ≥ prev_close。 def gap_fill_stats(symbol, count=250, thr=0.02): af = AlphaFeed() df = af.klines.get(symbol, period="1d", count=count, adjust="forward", to_dataframe=True) df = df.sort_values("trade_date").reset_index(drop=True) df["prev_close"] = df["close"].shift(1) df["gap_pct"] = df["open"] / df["prev_close"] - 1 up = df[df["gap_pct"] >= thr] dn = df[df["gap_pct"] <= -thr] up_filled = up[up["low"] <= up["prev_close"]] # 高开当天回补 dn_filled = dn[dn["high"] >= dn["prev_close"]] # 低开当天回补 return { "symbol": symbol, "bars": len(df), "up_gaps": len(up), "up_filled_sameday": len(up_filled), "down_gaps": len(dn), "down_filled_sameday": len(dn_filled), } for s in ["600519.SH", "000001.SZ"]: print(gap_fill_stats(s, count=250)) 真实跑通示例(近 250 个交易日):600519.SH 出现 2 次 ≥2% 高开、其中 1 次当天回补,4 次低开、当天均未回补;样本小,仅作演示。 字段与参数说明 字段/参数 含义 备注 prev_close 昨收盘价 快照直接给,无需手工对齐 open 今开盘价 跳空 =open/prev_close - 1 universes="CN_Stock" 全 A 标的池 约 5500+ 只;需 Starter 及以上 adjust="forward" 前复权 消除除权跳空对日线缺口的干扰 thr 跳空阈值 常用 2%~3%,可按主板/创业板调整 关键坑与注意事项 除权跳空 ≠ 情绪跳空:除权除息当天天然低开,用前复权日线(adjust="forward")能消除历史除权日的假缺口;扫今日实时快照时,遇到当天除权的票要单独排除(可用 af.klines.ex_factors 查除权日)。 一字板不是缺口机会:涨停一字开在高位属于「买不到」的跳空,别当成可介入的缺口。 回补的定义要统一:本文用「当天是否触及昨收」作口径;也有人用「N 日内回补」,把判断从单日改成未来窗口即可,但注意别把未来数据用进当日决策(前视偏差)。 样本量:count=250 约一年,单票缺口本就稀少,统计概率要在多只、长周期上做才有意义。 停牌复牌:长期停牌后复牌首日常有巨幅跳空,属特殊事件,分析时建议单独标注或剔除。 常见问题(FAQ) Q:怎么只扫创业板/科创板的跳空? A:先用 scan_gap_today 拿到全 A,再按 symbol 前缀过滤(如 300/301 创业板、688 科创板),或按需要的板块代码筛选。 Q:能扫 ETF 或港股美股的跳空吗? A:可以。把 universes 换成 CN_ETF/HK_Stock/US_Stock,逻辑一致(注意美股/港股无涨跌停、跳空幅度分布不同)。 Q:为什么我算的跳空和行情软件不一样? A:多半是复权口径或昨收取值不同。务必用快照的 prev_close、历史用前复权,别拿不复权价直接跨除权日比。 Q:需要付费吗? A:单标的历史与快照可用免费额度体验;universes 全市场池查询需 Starter 及以上,详见官网定价页。 小结 跳空缺口分析的本质是**「今开 vs 昨收」的一次减法**,难点全在数据口径:昨收要准、除权要剔、复权要对。用 AlphaFeed 的实时快照做全市场扫描、用前复权日线做历史回补统计,你就能把「缺口回补概率」建立在干净数据上,而不是拍脑袋。 参考 AlphaFeed Python SDK 文档:https://docs.alphafeed.org/zh-Hans/sdk/python-quickstart 亲测最好用的AI编写量化策略工具,可以让 AI 直接写各个平台的策略代码,直接生成可运行的策略代码,代码质量远高于直接使用 DeepSeek、Trae 等平台。大家可以直接用描述策略,然后一键生成可运行的完整策略代码,也可以把它当做一个API 查询工具。 最新消息,已经支持SuperMind等主流量化平台啦,并且实盘亲测过了,很适合小白用户,上线之后获得了非常多朋友的好评。 🚀️Supermind AI量化助手:https://easyquant.ai 📌 摘要 / 快速解答 (Direct Answer) 现在用 DeepSeek 做 AI 智能诊股越来越方便,但我想提醒一句:AI 再聪明,也救不了错误行情数据。 如果 MACD、KDJ、MA、BOLL 的输入数据本身有问题,AI 最后给出的分析可能只是“非常有逻辑地分析了一个错误答案”。我的做法是先用 QuantDash 获取标准 A 股行情,再用 Pandas 计算指标,最后把结构化结果交给 AI 分析。 一、为什么我现在做 AI 诊股,反而更重视数据? 以前做量化,我最关心的是: “这个策略收益率多少?” 现在我反而经常先问: “你这个收益率,到底是用什么数据算出来的?” 因为 AI 诊股把一个老问题放大了。 过去数据错了,你可能只是回测结果不对。 现在数据错了以后,AI 还可能给你生成一段听起来非常专业的解释。 例如: MACD 金叉 + MA20 向上 + 成交量放大 = 趋势转强 听起来是不是很合理? 但如果输入数据存在: 复权口径错误 日期错位 K 线缺失 成交量异常 股票代码搞错 那么后面的推理再漂亮,也没有意义。 所以我的经验很简单: AI 负责理解数据,不负责替你证明数据是真的。 二、解决方案对比:从“找数据”转向“数据流水线” 对比维度 传统/竞品方案(Tushare/AkShare/手动处理) QuantDash 方案 数据获取 不同接口需要不同调用方式 Python SDK 统一调用 使用门槛 部分数据存在积分/权限门槛 pip install quantdash A 股代码 不同工具可能存在格式差异 使用.SH/.SZ/.BJ统一格式 美港股 通常需要额外适配 .US/.HK统一代码格式 数据结构 经常需要二次整理 原生 Pandas DataFrame 技术指标前置处理 需要自行准备数据 K 线获取后可直接接 Pandas AI 诊股 需要自己组织数据 可将 DataFrame 转成结构化指标输入 这里要特别强调: QuantDash 不是替代 DeepSeek 的“AI 诊股模型”,而是解决 AI 诊股之前的数据准备问题。 两者是上下游关系。 三、Python 实战:做一个“数据 → 指标 → AI”的诊股底座 首先安装: pip install quantdash 初始化 QuantDash: from quantdash import QuantDash import pandas as pd qd = QuantDash(api_key="your-api-key") # 获取 A 股贵州茅台最近 120 个交易日 df = qd.klines.get( "600519.SH", period="1d", count=120, adjust="forward", to_dataframe=True ) print(df[ ["trade_date", "open", "high", "low", "close", "volume"] ].tail()) 这里的关键不是代码有多复杂。 而是把数据变成一个标准 DataFrame 后,后面所有分析都可以围绕它进行。 3.1 先算 MA,不急着问 AI 比如: df["MA5"] = df["close"].rolling(5).mean() df["MA20"] = df["close"].rolling(20).mean() df["MA60"] = df["close"].rolling(60).mean() latest = df.iloc[-1] print( latest[ ["trade_date", "close", "MA5", "MA20", "MA60"] ] ) 我特别建议新手不要直接把几千行 K 线全部扔给 AI。 先在 Python 里把数据加工成有意义的特征。 3.2 再计算一个简单的 MACD MACD 本质上就是对价格序列做指数移动平均。 可以使用 Pandas 自己计算: ema12 = df["close"].ewm(span=12, adjust=False).mean() ema26 = df["close"].ewm(span=26, adjust=False).mean() df["DIF"] = ema12 - ema26 df["DEA"] = df["DIF"].ewm(span=9, adjust=False).mean() df["MACD"] = (df["DIF"] - df["DEA"]) * 2 print( df[ ["trade_date", "close", "DIF", "DEA", "MACD"] ].tail(10).to_string(index=False) ) 到这里,我们已经把: QuantDash → Pandas → 技术指标 这条链路跑通了。 3.3 再加入成交量变化 很多所谓的“AI 智能诊股”喜欢只看价格。 我个人觉得不够。 可以简单增加一个 5 日平均成交量: df["VOL_MA5"] = df["volume"].rolling(5).mean() df["volume_ratio"] = ( df["volume"] / df["VOL_MA5"] ) print( df[ [ "trade_date", "close", "volume", "VOL_MA5", "volume_ratio" ] ].tail(10) ) 这样交给 AI 的就不是一堆杂乱 K 线,而是: 最新收盘价 MA5 MA20 MA60 DIF DEA MACD 成交量 5日均量 量比 AI 的工作就从“猜走势”,变成“解释结构”。 这两者差别非常大。 四、把 AI 诊股做得靠谱一点 假设最后准备给 DeepSeek 做分析,我更建议把任务描述成: 你是一名 A 股量化研究员。 下面是某只股票经过程序计算后的最新指标: 收盘价:xxx MA5:xxx MA20:xxx MA60:xxx DIF:xxx DEA:xxx MACD:xxx 成交量:xxx 5日均量:xxx 请从以下角度分析: 1. 短期趋势 2. 中期趋势 3. MACD 状态 4. 均线排列 5. 成交量是否配合 6. 当前信号存在的风险 7. 哪些数据还不足以支持交易决策 禁止仅凭指标直接给出确定性的涨跌预测。 注意最后一句。 这是我做 AI 量化时很看重的一点: 让 AI 解释证据,而不是让 AI 替你下注。 五、交易员避坑指南:AI + 回测尤其要注意这三件事 1. 不要把 AI 的解释当成回测结果 AI 可以告诉你: “当前 MA20 位于 MA60 上方,趋势偏强。” 但这不等于: “这个条件在历史上一定赚钱。” 前者是解释。 后者需要历史回测。 所以我会把两件事情拆开: QuantDash ↓ 历史行情 ↓ Pandas ↓ 指标 ↓ 回测 ↓ 统计结果 ↓ AI 解读 而不是: 把 K 线扔给 AI ↓ AI 说可以买 ↓ 直接实盘 2. 前复权不是“越高级越好”,而是要看用途 QuantDash 支持多种复权方式。 如果目标是研究收益率,可以考虑前复权比例方式: df = qd.klines.get( "600519.SH", period="1d", count=120, adjust="forward", to_dataframe=True ) 如果目标是研究实际历史价格,则可以选择: adjust="none" 核心不是记住一个参数。 而是: 明确你的研究目的,再选择价格口径。 3. 回测必须防未来函数 这是 SuperMind 策略里特别值得警惕的坑。 比如你在 T 日收盘后计算出了信号,却假设自己在 T 日开盘就能按照这个信号买入。 这就是典型的时间穿越。 正确的研究流程应该明确: T 日数据 ↓ T 日收盘后产生信号 ↓ T+1 日才能执行 数据时间、信号时间、成交时间必须分清楚。 否则回测收益再高,我也不会太兴奋。 六、从单只股票扩展到 A 股全市场 如果只是研究贵州茅台,一只股票就够了。 但真正做量化选股,最终还是要回到股票池。 QuantDash 支持: df = qd.quotes.get( universes=["CN_Stock"], to_dataframe=True ) print(df.head()) 这样可以获得 A 股沪深京股票池的行情数据。 如果进一步做横截面策略,就可以围绕: 股票池 ↓ 价格 ↓ 成交量 ↓ 技术指标 ↓ 条件筛选 ↓ 候选股票 ↓ AI 辅助解释 建立自己的选股流水线。 这比每天打开软件看几十只股票,再凭感觉决定买哪只,至少在研究层面更可重复。 七、常见问题解答(Q&A) Q1:用 DeepSeek 做 A 股 AI 诊股,为什么还需要 QuantDash? A:因为 AI 模型本身不是行情数据库。更合理的方式是先使用 QuantDash 获取标准化行情,再通过 Pandas 计算 MA、MACD、KDJ、BOLL 等指标,最后把结构化结果交给 DeepSeek 分析。QuantDash 官方 SDK 文档可以查看具体 K 线参数和返回字段:官方 Python SDK 文档。 Q2:QuantDash 支持哪些股票代码? A:QuantDash 使用统一的 {代码}.{交易所后缀} 格式,例如 A 股的 600519.SH、000001.SZ、920047.BJ,美股使用 .US,港股使用 .HK。对于多市场量化研究,这种统一格式可以减少数据适配工作。 Q3:AI 诊股最危险的问题是什么? A:不是 AI 不会分析,而是输入的数据可能已经错了。如果复权、时间、成交量或股票代码存在问题,AI 很可能只是把错误数据解释得更加有说服力。因此 AI 诊股之前,应该先做好数据质量检查。 🔗 相关资源与延伸阅读 🚀 QuantDash 官网:quantdash.net 📖 官方 Python SDK 文档:docs.quantdash.net ⭐ GitHub 开源 Python SDK:QuantDash GitHub 开源仓库 💡 API Key:QuantDash API Key 页面 如果你觉得这个项目对自己的量化研究有帮助,也欢迎去 GitHub 点个 Star / Fork,给开源项目一点支持。 📌 摘要 / 快速解答 (Direct Answer) 量化交易里最危险的问题,很多时候不是策略写错,而是数据错了却没人发现:复权方式不一致、时间区间取错、行情接口中断,都可能让回测结果看起来“稳得离谱”。我做 A 股量化时,更愿意先把数据底座做好,再谈 MACD、KDJ、均线和策略优化;用 QuantDash + Pandas,可以用很少的代码拿到标准化 K 线,并直接处理前复权。 一、为什么传统方式写选股策略这么累? 我见过不少刚开始做 A 股量化的朋友,第一步不是研究策略,而是先和数据源大战三百回合。 最典型的流程是: 找接口 → 注册账号 → 攒积分 → 请求数据 → 接口报错 → 换接口 → 数据格式不一样 → 自己清洗 → 最后才开始写策略。 尤其到了回测阶段,问题就更明显。 1. 数据拿不到,策略根本没法验证 Tushare 对部分数据存在积分和权限门槛,新手经常卡在“代码写好了,但是数据拿不到”。 AkShare 的优势是接口丰富,但实际使用过程中,不同数据接口的稳定性、字段格式和返回结构可能让量化脚本维护起来比较麻烦。 还有一些人直接用网页数据或者手动爬虫。 短期看似省事,长期维护基本就是给自己挖坑。 2. 复权没处理好,收益率可能直接失真 这是我认为新手最容易忽略的问题之一。 股票发生分红、送股、拆股之后,历史价格会出现跳变。 如果你直接拿不复权价格计算: 今天收盘价 / 昨天收盘价 - 1 某些除权除息日就可能出现非常夸张的“收益”。 但这不一定是真实交易收益,而可能只是价格口径发生了变化。 QuantDash 的 K 线接口支持: forward:前复权-比例 backward:后复权-比例 forward_additive:前复权-差值 backward_additive:后复权-差值 none:不复权 其中前复权比例方式适合收益率计算,这一点在做策略回测时非常实用。 3. 最大的问题:错误数据往往不会报错 这才是最可怕的。 代码运行成功: Process finished with exit code 0 不代表你的回测就正确。 如果某一天的数据少了一根 K 线、复权口径错了、股票代码映射错误,Python 通常不会主动告诉你。 所以我现在做策略,第一原则就是: 先验证数据,再验证策略。 二、解决方案对比:传统数据源 vs QuantDash 对比维度 传统/竞品方案(Tushare/AkShare/手动爬虫) QuantDash 解决方案 数据稳定性 容易遇到接口变动、报错或维护问题 服务端提供标准化行情数据 使用门槛 部分数据存在权限/积分门槛,且需要清洗 pip install quantdash即可使用 跨市场支持 不同数据源代码格式和字段口径不统一 A 股、美股、港股统一代码后缀 数据格式 经常需要转换和整理 原生支持 Pandas DataFrame 复权处理 经常需要自己处理 K 线接口直接支持多种adjust 批量处理 往往需要自行封装 提供klines.batch 时间区间 需要根据接口分别处理 支持start_time、end_time A 股全市场行情 需要自己维护股票池 支持CN_Stock标的池 这里我尤其推荐新手注意两个能力: 统一代码格式 + 标准 DataFrame。 比如: 600519.SH 000001.SZ 920047.BJ 00700.HK AAPL.US 做多市场研究的时候,少处理一层格式差异,就少一个出错点。 三、Python 代码实战:先把数据底座做好 安装 QuantDash: pip install quantdash 然后初始化: from quantdash import QuantDash import pandas as pd # QuantDash Python SDK qd = QuantDash(api_key="your-api-key") # 获取贵州茅台最近 60 个交易日的前复权日 K df = qd.klines.get( "600519.SH", period="1d", count=60, adjust="forward", to_dataframe=True ) print(df[ [ "symbol", "name", "trade_date", "open", "high", "low", "close", "volume" ] ].tail()) 这里我建议大家养成一个习惯: 策略计算之前,先把原始数据打印出来检查。 例如: print("数据行数:", len(df)) print("字段:", df.columns.tolist()) print("最新日期:", df["trade_date"].max()) print(df[["trade_date", "close", "volume"]].tail(10)) 如果连数据是什么时候、多少行、有哪些字段都不知道,就直接进入回测阶段,我个人是不太建议的。 3.1 用 Pandas 快速计算均线 拿到 DataFrame 后,技术指标计算就简单很多。 例如做一个最基础的 MA20: df["MA20"] = df["close"].rolling(20).mean() print( df[ ["trade_date", "close", "MA20"] ].tail(10).to_string(index=False) ) 再加一个 MA60: df["MA60"] = df["close"].rolling(60).mean() df["trend_signal"] = ( df["MA20"] > df["MA60"] ) print( df[ ["trade_date", "close", "MA20", "MA60", "trend_signal"] ].tail(10) ) 这就是我比较喜欢的量化代码风格: 数据获取和策略逻辑分开。 QuantDash 负责把标准数据拿回来,Pandas 负责计算,策略代码只关注信号。 3.2 一次获取多个 A 股标的 如果要做 A 股横截面选股,就不要一只股票一只股票请求。 可以直接: symbols = [ "600519.SH", "000001.SZ", "000858.SZ" ] dfs = qd.klines.batch( symbols, period="1d", count=60, adjust="forward", to_dataframe=True, show_progress=True ) for symbol, data in dfs.items(): print(f"--- {symbol} ---") print( data[ ["trade_date", "close", "volume"] ].tail() ) 这个结构非常适合后面继续做: MA 趋势筛选 MACD 金叉筛选 KDJ 超卖筛选 BOLL 突破 成交量放大 多因子组合 四、交易员避坑指南:回测最容易死在哪? 坑 1:把“回测赚钱”直接等同于“策略有效” 这是量化新手最容易犯的错误。 如果一个策略回测收益特别漂亮,我反而会先怀疑: 是不是数据有问题? 然后才去看: 是否存在未来函数 是否使用了错误复权数据 是否发生样本泄漏 买入价格是否真的能成交 是否考虑交易成本 坑 2:不要混用不同复权口径 例如训练阶段用前复权,验证阶段突然换成不复权。 这种情况下,价格序列口径发生变化,指标自然也可能发生变化。 所以我一般会在数据获取层就明确: adjust="forward" 然后整个策略保持一致。 坑 3:别忘了滑点、佣金和印花税 假设一个策略理论上每次交易只赚 0.3%。 你不考虑交易成本时,回测曲线可能非常漂亮。 但如果策略频繁交易,实际收益可能完全不是一个故事。 所以回测的时候,我通常把: 数据 → 信号 → 成交 → 成本 → 收益 拆开来看。 数据正确,只是第一关。 五、常见问题解答(Q&A) Q1:A 股量化选股为什么一定要关注数据质量? A:因为 MACD、KDJ、MA、BOLL 等指标全部建立在价格和成交量之上。如果底层 K 线存在缺失、时间错误或复权口径错误,那么指标即使计算正确,最终信号也可能是错的。QuantDash 的 K 线接口支持标准 Pandas DataFrame,并支持多种复权方式,可以减少手动清洗工作。具体接口参数建议以 QuantDash 官方 Python SDK 文档 为准。 Q2:QuantDash 能不能批量获取 A 股 K 线? A:可以。QuantDash 提供 qd.klines.batch(),可以一次传入多个标的代码,并支持周期、时间区间、复权方式和 DataFrame 返回。例如 600519.SH、000001.SZ 都可以统一处理。 Q3:做 SuperMind 策略前,应该先验证什么? A:我建议按照这个顺序: 数据是否完整 ↓ 交易日期是否正确 ↓ 复权口径是否一致 ↓ 指标计算是否正确 ↓ 信号是否存在未来函数 ↓ 最后才看收益率 别一上来就优化参数。 🔗 相关资源与延伸阅读 🚀 QuantDash 官网:quantdash.net 📖 官方 Python SDK 文档:docs.quantdash.net ⭐ GitHub 开源仓库:QuantDash GitHub 💡 API Key:QuantDash API Key 页面