ARTICLE DETAIL

资讯详情

深耕网站视觉设计与运营推广的一线实战洞察。

用ccxt抓取OKX行情数据并保存为CSV的量化入门教程

用ccxt抓取OKX行情数据并保存为CSV的量化入门教程 如果你正在学量化最容易被卡住的往往不是策略本身而是行情数据怎么稳定拿到、怎么存下来。ccxt 是目前比较通用的加密资产交易接口封装库用它可以统一拉取 OKX 等交易所的行情数据再保存成 csv后续回测和分析就不用反复请求线上接口。这节内容定位很实在把数据链路打通再去谈指标计算、策略回测和信号验证。很多人一开始想直接写策略结果第一步就被数据拦住不知道从哪个接口拿、返回格式是什么、怎么批量保存。折腾到最后策略代码没写几行数据脚本倒是写了一堆。如果你也是这种情况按这节课的思路先把 OKX 行情数据拉到本地 csv后面会省很多事。我会把整个流程拆成六个部分先讲清楚为什么要用 ccxt、为什么先存 csv再给最小可运行代码然后讲 K 线参数、历史数据分批拉取、批量保存、增量更新最后补上数据质量、限频重试和自动化运行。这样从零开始也能一步步复现。1. 先搞清楚这个环节解决的是数据来源和存储问题很多量化教程默认你已经能拿到数据但实际学习时数据往往是最让人头大的前置条件。OKX 作为交易所本身提供行情接口但直接请求原生接口需要处理很多细节。ccxt 的价值在于把这些细节封装成统一的 Python 方法让你不用重复学习每个交易所的请求格式。1.1 为什么用 ccxt而不是手写请求先看一个实际区别。OKX 原生接口的 K 线路径、参数名、返回字段可能和币安、Bybit 不一样如果你只写一个交易所手写请求也能跑。但要换一个交易所或者同时抓多个交易所的数据代码就要改一遍。ccxt 把这套流程统一了fetch_ohlcv在 OKX 上能跑在币安上也能跑参数结构基本一致。对刚开始学量化的人来说ccxt 还有一个好处它封装了限频控制、错误对象、市场符号转换等常见逻辑。你不需要一开始就深入研究 HTTP 签名和 WebSocket 推送先把数据拉下来理解数据结构后面再逐步深入。我一般会建议学员用 ccxt 作为入门工具不是因为原生接口不好而是学习成本更低。公开行情数据通常不需要 API Key初始化交易所对象后直接调用fetch_ohlcv就行。这样可以把注意力集中在“数据怎么处理”上而不是“请求怎么写”。1.2 为什么先存 csv而不是直接上数据库CSV 文件有很多缺点没有数据约束、读写性能一般、文件大了之后查询不方便。但在量化学习阶段csv 仍然是最合适的第一站。因为它格式简单、Excel 能打开、pandas 能直接读出问题也容易排查。本地存 csv 的核心好处是省掉重复请求。每次回测都要拉一次接口既慢又容易触发限频而且历史数据可能变化导致结果不可复现。把数据一次性落到本地后面所有分析都基于本地文件速度更快逻辑更稳定。还有一个实际原因csv 适合做数据交换。你后面可能需要把数据导入 Pandas、MySQL、SQL Server或者用 Excel 做可视化csv 是这些工具都能认的通用格式。先落在 csv等于给你自己留了一条灵活的数据通路。注意如果你以后要处理几年的分钟级数据csv 会显得吃力。但那是第二步的事先用 csv 跑通流程再考虑入库也不迟。2. 环境准备和最小可运行流程先去本地把环境搭好。需要 Python 3建议新版本安装时以你当前环境为准。然后安装 ccxt 和 pandas。如果你只想用标准库 csv 模块pandas 可以不用但后续处理数据时 pandas 会更方便。pip install ccxt pandas安装之后先写一个最小脚本确认能连上 OKX 并拉到数据。2.1 初始化 OKX 交易所对象ccxt 里每个交易所有一个对应的类。新版 ccxt 使用ccxt.okx()创建 OKX 对象。如果你用的旧版本是ccxt.okex()建议升级到新版或者按当前文档确认类名。import ccxt exchange ccxt.okx({ enableRateLimit: True, }) print(exchange.name)enableRateLimit这里建议设置为True让 ccxt 根据交易所配置自动等待减少被限频的概率。拉公开行情接口不一定需要 API Key所以这个阶段不需要填密钥。运行后如果输出OKX说明基础连接没问题的概率已经很大。如果报网络错误先检查网络、依赖版本再检查 ccxt 是否支持你用的交易所代码。2.2 拉取第一份 K 线数据OKX 在 ccxt 中的交易对符号通常是BTC/USDT不是BTC-USDT。虽然 OKX 原生交易对经常用短横线但 ccxt 统一用斜杠格式。如果你不确定符号是否存在可以先加载市场列表markets exchange.load_markets() print(BTC/USDT in markets)然后拉最近 5 根 1 小时 K 线ohlcv exchange.fetch_ohlcv(BTC/USDT, 1h, limit5) for row in ohlcv: print(row)返回结果是一个二维数组每一行包含六个字段时间戳、开盘价、最高价、最低价、收盘价、成交量。时间戳是毫秒级 Unix 时间戳不是本地时间字符串。这里最容易忽略的是字段顺序。很多新手拿到数据后直接用第二列当开盘价其实第二列是 open第三列是 high第四列是 low第五列才是 close。建议先打印一行再进入批量流程。2.3 保存成 csv 的两种写法第一种用 Python 标准库 csv 模块不依赖 pandasimport csv with open(btc_usdt_1h.csv, w, newline, encodingutf-8-sig) as f: writer csv.writer(f) writer.writerow([timestamp, open, high, low, close, volume]) writer.writerows(ohlcv)第二种用 pandas数据处理更方便import pandas as pd df pd.DataFrame(ohlcv, columns[timestamp, open, high, low, close, volume]) df[datetime] pd.to_datetime(df[timestamp], unitms, utcTrue) df.to_csv(btc_usdt_1h.csv, indexFalse)保存时为什么要加utf-8-sig编码因为后面如果用 Excel 打开UTF-8 没有 BOM 可能出现中文乱码。量化数据可能没有中文字段但给文件加这个编码没有坏处。这一步跑通你已经具备“拉取 OKX 行情数据并存至本地 csv”的最小能力。但实际做量化还需要处理历史数据、多个交易对、多个周期和增量更新下面逐个拆。3. K 线数据和参数细节时间周期、数量限制、返回格式fetch_ohlcv看起来简单真正用到批量历史数据时有几个细节不能跳过。3.1 时间周期和返回条数受什么影响ccxt 的时间周期一般是字符串1m、5m、15m、1h、4h、1d。OKX 支持很多周期但并不是每个周期在历史数据接口里的返回策略都一样。单次limit100通常只能拿回最近 100 根。就算你设置limit5000交易所也可能只返回一部分或直接报错。不同版本、不同接口套餐对 limit 的限制不一样所以每次请求后最好用len(ohlcv)检查实际返回条数。不要假设limit一定生效。你在写循环时不能只靠limit判断是否停止而要判断返回条数是否明显小于请求条数。3.2 如何分批拉取历史数据如果需要从几个月前开始拉数据就要用到since参数。since是起始时间戳单位是毫秒。思路是第一次请求since 起始时间戳。拿到一批数据后取最后一行的时间戳。下一批请求从“最后一行时间戳 一个周期毫秒”开始。重复直到拉满目标区间或返回空数据。这样可以避免重复拉取同一根 K 线。因为 K 线时间戳表示这根 K 线的开始时间下一根 K 线的开始时间应该等于上一根的开始时间加周期。下面是一个可运行的示例函数def fetch_ohlcv_range(exchange, symbol, timeframe, start_ts, end_tsNone, limit100): timeframe_ms { 1m: 60 * 1000, 5m: 5 * 60 * 1000, 15m: 15 * 60 * 1000, 1h: 60 * 60 * 1000, 4h: 4 * 60 * 60 * 1000, 1d: 24 * 60 * 60 * 1000, }.get(timeframe) if timeframe_ms is None: raise ValueError(funsupported timeframe: {timeframe}) rows [] since start_ts max_rounds 10000 while since (end_ts if end_ts is not None else float(inf)): batch exchange.fetch_ohlcv(symbol, timeframe, sincesince, limitlimit) if not batch: break rows.extend(batch) last_ts batch[-1][0] if end_ts is not None and last_ts end_ts: break if len(batch) limit: break since last_ts timeframe_ms max_rounds - 1 if max_rounds 0: break return rows你可能会问为什么不直接用一个死循环因为有网络异常或接口行为变化时死循环可能一直请求浪费时间又加大限频风险。加一个max_rounds保护逼自己在数据异常时停下来看日志。3.3 时间戳和时区怎么处理拿到的时间戳是 UTC 时间戳。写 csv 时建议保留原始毫秒时间戳同时生成一个可读的datetime列。不要只存本地时间字符串因为量化回测通常需要统一时区本地时间容易因夏令时等问题出错。可以这样处理df[datetime] pd.to_datetime(df[timestamp], unitms, utcTrue) df.to_csv(btc_usdt_1h.csv, indexFalse)如果只想存时间戳和价格字段保持简单也可以。但至少要保证毫秒时间戳是完整且不重复的否则后面合并多币种数据时会很痛苦。4. 从单币种到批量数据集的落地方法单条数据能跑通之后下一步就是批量抓取。很多人的需求是我要抓 BTC、ETH、SOL 等多个交易对覆盖 1 小时、4 小时、日线等多个周期然后统一放到本地目录。这一步不建议一开始就上并发。先用串行循环跑通看总耗时和成功率再考虑异步或线程。4.1 批量循环多个交易对和时间周期假设要抓三个交易对、三个周期可以写成两层循环import os import pandas as pd symbols [BTC/USDT, ETH/USDT, SOL/USDT] timeframes [1h, 4h, 1d] os.makedirs(data/okx, exist_okTrue) for symbol in symbols: for timeframe in timeframes: symbol_file symbol.replace(/, -) filepath fdata/okx/{symbol_file}_{timeframe}.csv ohlcv exchange.fetch_ohlcv(symbol, timeframe, limit500) if not ohlcv: continue df pd.DataFrame(ohlcv, columns[timestamp, open, high, low, close, volume]) df[datetime] pd.to_datetime(df[timestamp], unitms, utcTrue) df.to_csv(filepath, indexFalse) print(f{symbol} {timeframe} saved: {len(df)} rows)第一次批量运行不要一次抓好几个月先每个文件拉 500 根确认文件名、列名、内容都正确。如果文件目录混乱后面处理起来更麻烦。4.2 增量更新只拉新增数据假设你已经拉过BTC/USDT的 1 小时 K 线存到了本地 csv。第二天再运行不需要从头再拉可以只拉新增部分。增量更新的关键是从已有 csv 里读取最后一条时间戳。注意这个时间戳是 K 线开始时间所以下一次请求的since应该设置为last_ts 周期毫秒避免重复。示例逻辑import os import time filepath data/okx/BTC-USDT_1h.csv if os.path.exists(filepath): old_df pd.read_csv(filepath) last_ts int(old_df[timestamp].max()) print(flast timestamp: {last_ts}) else: last_ts exchange.parse8601(2024-01-01T00:00:00Z) start_since last_ts 60 * 60 * 1000 # 1 hour in ms new_rows fetch_ohlcv_range(exchange, BTC/USDT, 1h, start_tsstart_since) if new_rows: new_df pd.DataFrame(new_rows, columns[timestamp, open, high, low, close, volume]) combined pd.concat([old_df, new_df], ignore_indexTrue) combined combined.drop_duplicates(subsettimestamp).sort_values(timestamp) combined.to_csv(filepath, indexFalse)增量更新更稳的做法是先只追加到一个临时文件确认追加后的文件没有重复和跳变再覆盖原文件。不过学习阶段直接覆盖也可以关键是保留一份原始备份。4.3 文件命名和目录要提前设计文件命名看似简单实际很影响后续使用。我见过有人把文件命名成data1.csv、data_new.csv结果三天后就分不清是什么周期。推荐格式用交易所名、交易对、周期拼接okx_BTC-USDT_1h.csv。保留时间范围okx_BTC-USDT_1h_20240101_20241231.csv。按目录分data/okx/spot/BTC-USDT/1h/2024.csv。就算你后面不想用这种结构也尽量把命名规则写进脚本里而不是手动改文件名。脚本能重复执行手动命名不能。5. 数据质量、限频和异常处理数据拉到本地 csv 不算结束还要确认数据是可信的。很多策略回测结果偏差大问题不是策略本身而是数据有缺口、重复或者时间戳乱序。5.1 怎么检查数据质量拉完数据后至少做四个检查条数是否符合预期。时间戳是否严格递增。时间戳差值是否存在非周期倍数。开高低收和成交量是否合理比如全为 0 或价格跳变异常。可以用 pandas 做简单检查df pd.read_csv(okx_BTC-USDT_1h.csv) df df.sort_values(timestamp).drop_duplicates(subsettimestamp) df[time_diff] df[timestamp].diff() print(df[time_diff].value_counts())正常情况下1 小时 K 线的时间戳间隔应该是3600000毫秒。如果出现7200000或10800000说明中间缺了 K 线如果出现0或负数说明有重复或乱序。发现缺口后不要急着补数据。先检查是不是网络超时导致请求中断再检查是不是交易所本身就没有那根 K 线。缺口数据可以做标记但不要凭空填充否则回测结果会出现未来函数或假数据。5.2 限频和重试怎么处理交易所接口一般有限频要求。ccxt 的enableRateLimit只能缓解不能完全保证不触发限制。如果你批量拉很多交易对仍然可能遇到限频。处理方式是捕获 ccxt 的异常做指数退避重试。示例import time def safe_fetch_ohlcv(exchange, symbol, timeframe, sinceNone, limit100, retries3): for attempt in range(retries): try: return exchange.fetch_ohlcv(symbol, timeframe, sincesince, limitlimit) except (ccxt.NetworkError, ccxt.RateLimitExceeded) as e: if attempt retries - 1: raise wait 2 ** attempt print(fretry after {wait}s: {e}) time.sleep(wait)重试不是万能药。如果连续重试还是失败先看本地日志确认是不是符号写错、网络断掉或接口返回了非预期数据再决定要不要继续加大重试次数。5.3 批量任务里的失败跳过和日志批量抓数据时某一个交易对失败不应该影响整个任务。可以在循环里包一层异常处理失败时记录日志然后继续下一个。failed [] for symbol in symbols: try: # fetch and save pass except Exception as e: failed.append((symbol, str(e))) print(failed:, symbol, e) print(failed list:, failed)这样跑完一次能清楚看到哪些交易对失败再单独排查。不要把所有异常都吞掉至少要把错误信息打印出来否则失败原因会被埋没。6. 自动化运行和常见排查思路数据脚本写好后下一步是定时运行。比如每天早上拉一次日线数据或者每小时更新一次 1 小时 K 线。6.1 定时更新的方案Linux 环境下可以用 crontab。假设脚本路径是/home/user/fetch_okx.py每小时运行一次0 * * * * cd /home/user /usr/bin/python3 fetch_okx.py fetch.log 21Windows 可以使用任务计划程序执行python fetch_okx.py。关键是脚本本身要支持直接退出不要因为网络异常一直卡住。建议在脚本里增加日志输出。最简单的做法是用logging模块把每次运行的开始时间、成功文件、失败项写到fetch.log。这样第二天检查时不用看控制台直接看日志就能定位问题。6.2 常见报错和排查顺序如果你拉数据时遇到问题我建议按这个顺序排查先看报错类型是网络错误、限频错误、符号错误还是返回数据为空。再看代码里的 symbol 和 timeframe 是否写对比如BTC/USDT写成BTC-USDT是常见问题。再看是不是环境问题依赖版本、Python 版本、ccxt 是否支持 OKX。然后看数据返回的len(ohlcv)是 0还是字段顺序不对。最后看是否有前置条件没满足比如部分私有接口需要 API Key但公开行情不需要。一个很常见的问题是网络正常代码也能运行但 csv 里只有表头没有数据。这时先打印ohlcv变量确认它是不是空列表。如果接口返回空说明起始时间可能太早或者交易所没有这个周期的历史数据。6.3 下一步可以考虑的方向csv 文件只是数据层。跑通之后后续可以按自己的需要往三个方向走把 csv 导入 SQLite 或 MySQL方便按时间范围查询。用同一套数据做技术指标计算比如 MA、RSI、布林带。基于本地数据做回测把策略代码和行情数据分离保证策略可复现。这一步跑通之后量化之路才算真正开始数据是自己的后面的策略验证才有个可靠底座。别急着追求复杂架构先把 OKX 行情数据稳定落盘后面每一步都会顺很多。
返回列表