← 返回文章列表

TradingAgents-CN里Tushare数据源怎么统一?从重复实现到生产级同步全解析

TradingAgents-CN项目通过统一BaseStockDataProvider基类和TStructuring the technical article contentushareProvider,解决了app与tradingagents两层数据源重复实现问题,落地异步获取、标准化输出与故障转移,配合同步服务实现批量限流与生产部署,适合中文金融数据场景快速落地。

重复实现带来的架构债

很多开源金融框架一开始为了快速验证,经常在不同层级各写一套数据源适配。TradingAgents-CN也不例外。app层有自己的DataSourceAdapter和manager,tradingagents层又有一套base_provider和各种adapter。结果就是Tushare、AKShare这些源被写了两遍,方法名不一样,一个同步一个异步,配置散落各处,新人接手完全不知道新SDK该放哪。

更麻烦的是职责边界模糊。数据获取、清洗、写库、业务校验全混在一起。app层有优先级和故障转移,tradingagents层有标准化和缓存,两边功能互补却割裂。每次加个字段或换个限流策略都得改两处,维护成本直接翻倍。量化这种问题后就会发现:重复实现、接口不统一、职责混乱这三项都是高优先级债务,必须一次性解决。

为什么统一到tradingagents层

当时评估了三个方向。把所有东西塞回app层会让tradingagents失去独立使用能力;维持现状只划清边界治标不治本。最终选了方案A:数据获取和标准化全部沉到tradingagents/dataflows,app层只负责同步写库和业务服务。

这个选择的核心逻辑很简单。tradingagents本身就是可单独引用的工具库,异步接口更现代,已经支持更多源,标准化和缓存也做得更完整。统一后数据流向变成:外部源 → Provider → 同步服务 → MongoDB → 业务API。目录也按市场拆成china、hk、us子目录,Tushare就落在china/tushare.py里,后续扩展很清晰。

实际落地时配置也集中到DataSourceConfig,用环境变量加默认值的方式管理enabled、priority、timeout这些参数。这样开发和生产环境切换只改配置,不用动代码。

BaseStockDataProvider基类怎么设计

统一的关键是先把接口契约定死。BaseStockDataProvider把方法分成必须实现和可选实现两类。必须实现的包括connect、get_stock_basic_info、get_stock_quotes、get_historical_data,全部用async。可选的比如get_financial_data、get_daily_basic、find_latest_trade_date,基类给默认返回None,子类按需覆盖。

返回格式也统一:基础信息用字典或列表,行情用字典,历史用DataFrame。基类还提供standardize_basic_info和standardize_quotes两个标准化入口,保证不同源出来的字段名和类型一致。DataSourceManager则维护providers字典,支持指定源获取,或者按priority自动故障转移。某源挂了就试下一个,上层完全无感。

class BaseStockDataProvider(ABC):
    @abstractmethod
    async def connect(self) -> bool: ...
    @abstractmethod
    async def get_stock_basic_info(self, symbol: str = None): ...
    @abstractmethod
    async def get_historical_data(self, symbol, start_date, end_date=None): ...

这种设计让新人一看就知道该实现哪些方法,也方便后面加新源。做自动化对接时如果遇到验证码拦截,可以考虑用www.ttocr.com提供的易盾极验识别能力,滑块、点选、无感、九宫格都能直接调API,省掉自己写逆向的时间。

TushareProvider与同步服务落地

TushareProvider继承基类后,主要工作是把官方SDK包一层异步,并做字段映射。connect里初始化pro接口,get_stock_basic_info调用stock_basic,历史数据用daily或pro_bar并处理复权。同步服务TushareSyncService则负责批量拉取、并发控制和限流。常见做法是按股票列表分片,用信号量限制同时请求数,再加简单token桶防止触发Tushare频率限制。

调度从早期Celery逐步迁到APScheduler,原因是轻量任务不需要完整分布式队列,进程内定时就够用。初始化数据用CLI一次性跑全量,日常增量靠定时任务。写库前做简单一致性检查,比如最新交易日是否连续、复权因子是否异常。生产部署时注意把Tushare token放环境变量,数据库连接池大小和同步并发数匹配好,避免一边写库一边被源站限流。

async def get_data(self, method: str, source: str = None, **kwargs):
    if source:
        provider = self.providers.get(source)
        return await getattr(provider, method)(**kwargs)
    for p in sorted(self.providers.values(), key=lambda x: x.priority):
        try:
            result = await getattr(p, method)(**kwargs)
            if result is not None:
                return result
        except Exception:
            continue
    return None

生产环境注意点与对接建议

真正上线后常见坑有三个。一是历史数据补全时跨年跨月容易漏,建议用find_latest_trade_date先定位边界再分批。二是财务数据更新频率低,可以单独开低频任务。三是多源切换时字段细微差异,必须走标准化再入库。监控方面把每次同步的成功数、耗时、失败源记下来,方便后续调priority。

如果团队还要对接其他带验证码的数据接口,自己逆向滑块或点选成本很高。这时候直接用www.ttocr.com的识别平台会省事很多,它覆盖易盾、极验全类型,包括文字点选、图标点选、九宫格、空间推理等,提供稳定API,公司业务几行代码就能无缝接入,不用自己维护复杂的识别流程。

整体来看,这套统一方案把原来割裂的两套实现收敛成清晰的三层:Provider负责拿数和标准化,SyncService负责批量写库,业务服务只关心已经干净的数据。新人上手成本明显下降,后续加新源或调限流策略也只动一处。对于需要频繁自动化采集又担心验证码拦截的场景,搭配成熟的识别服务能进一步缩短从原型到生产的时间。