首页 / 资讯中心 / 文章详情

Python爬虫与量化交易:从数据采集到策略回测的完整实践

Python爬虫与量化交易:从数据采集到策略回测的完整实践 ★ FEATURED ARTICLE
开盘前半小时我通常会做一件事打开自己写的那套Python脚本让它自动拉取持仓和自选股的行情算完均线、RSI、MACD这些指标再根据预定义的规则给出今日关注清单。这些动作从2019年开始就一直由代码替我完成——说到底我只是用Python爬虫和量化思路给自己造了一个私人股票分析师。这篇文章会把整套思路完整拆开从数据采集、存储清洗到指标计算、策略回测再到上线后遇到的各种坑。不需要你懂多高深的金融理论只要会一点Python基础、知道pandas和requests大概怎么用就能跟上节奏。哪怕你完全是为了学爬虫来的也能从这套真实项目里看到数据采集、反爬应对、SQLAlchemy入库的完整链路。1. 为什么你需要一个私人分析师从需求到系统架构先明确一个概念量化交易不是全自动炒股更不是躺着赚钱。它本质上是一个决策辅助系统——把人为的情绪、犹豫、直觉干扰降到最低用规则驱动判断。而私人分析师的核心任务就是把看一眼K线、翻一翻新闻再做决定这种模糊过程变成指标A触发、条件B满足、仓位C执行的确定性流程。1.1 这个系统到底解决了什么问题先算一笔时间账。手动看盘一个人要盯几十只票每只票要看日K、成交量、各种技术指标的变化开盘前至少要花半小时到一小时而且容易漏掉关键变动。如果让代码来做数据采集加指标计算加信号生成整个过程不超过一分钟。这不是效率提升的问题是维度区别。第二个问题是纪律性。人最大的敌人是手痒。涨了想追跌了想割这是人性不是能力问题。但如果你把交易规则写死在代码里比如MA5上穿MA20且RSI小于70时列入观察名单那么当这个条件不满足时系统就是什么都不推。它不会受情绪影响这就是规则化决策的价值。第三点是可复盘性。代码跑过的每一天都会留下数据记录今天为什么给出这个信号、当时的市场环境长什么样都能回溯。手动看盘很难做到这种程度的回溯。1.2 整体架构分层设计才能走远我先用一张表把系统分四层每一层只做自己的事层与层之间不互相掺和层级职责核心技术典型产出数据层从公开接口获取行情与基础信息requests、API调用股票列表、日K数据存储层清洗、去重、增量持久化SQLAlchemy SQLite/MySQL本地数据库表计算层技术指标、信号合成pandas、numpy指标字段、触发信号输出层结果展示与主动推送定时任务、终端打印当日关注列表、告警信息这四层互相独立好处是出问题能快速定位。比如指标算错了不需要去怀疑爬虫出了问题只需要检查计算层的输入输出即可。这也是我给很多新手朋友的建议第一版不要想着做一个大而全的系统先把数据链路打通后面加什么都简单。1.3 技术选型怎么定requests还是scrapySQLite还是MySQL选型这块我直接说结论然后解释为什么。爬虫框架用requests配合Session不用scrapy。理由是行情数据接口是标准的HTTP JSON接口结构固定、频率不高、单机完全够用。scrapy是重型框架适合大规模分布式采集海量网页这里用不上。数据存储用SQLAlchemy ORM底层先SQLite数据量大了再切MySQL。SQLAlchemy的ORM能帮你平滑切换数据库这是它最大的价值一开始在SQLite上调试逻辑以后真要换库只改连接字符串。计算与回测pandas numpy完全够用不需要引入专门的回测框架。回测框架虽然功能全但你要先花大量时间看它的API文档而且规则定义会被框架的套路限制。第一版自己写一个20行的回测循环逻辑完全可控。这里有个非常重要的心法第一版永远用你最熟悉、能最快跑通的技术栈不要为了专业而引入一个没接触过的重量级框架。很多人的量化项目死在第一步不是技术不行是系统设计过度复杂了。2. 数据采集层别研究K线了先学会喂饱模型整个系统的地基是数据。数据不对后面算的指标全是垃圾。所以采集层的核心目标就一句话稳定、完整、增量地拿到你需要的行情数据。2.1 数据源怎么选东方财富、腾讯、新浪接口对比国内股票公开数据接口现在主要就几家我实际用下来各有特点数据源接口形式优点缺点新浪财经hq.sinajs.cn/listsh600000简单、返回快字段少、偶尔403腾讯财经qt.gtimg.cn/qsh600000稳定、字段较多需要处理编码东方财富push2his.eastmoney.com/api/qt/stock/kline/get数据最全、有复权参数略复杂我自己主用东财原因是它的K线接口直接支持前复权、后复权、不复权三种模式这个后面讲复权时你会明白有多重要。另外它单次最多能返回几百根K线一天一条数据的话一次性拿五年完全没问题。如果你只是拿某一只票的数据试水可以先从腾讯接口开始返回的是简单的字符串用split就能解析对新手非常友好。但要做到批量、稳定的系统东财是更好的选择。2.2 批量获取股票代码先用列表接口再逐只拉K线这步是实现分析师的关键你的自选列表只有十只票就把这十只票的K线一次性拉全存在数据库里以后每天增量更新。但如果你想从4000多只票里筛选就先把股票列表拉下来。东财的股票列表接口长这样import requests def get_stock_list(): url http://push2.eastmoney.com/api/qt/clist/get params { pn: 1, # 页码 pz: 100, # 每页数量 po: 1, np: 1, fltt: 2, invt: 2, fid: f3, # 按涨跌幅排序 fs: m:0t:6,m:0t:80,m:1t:2,m:1t:23, # 沪深主板创业板等 fields: f1,f2,f3,f4,f5,f6,f7,f12,f13,f14, # f12为代码,f14为名称 } headers { User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64), Referer: http://quote.eastmoney.com/, } resp requests.get(url, paramsparams, headersheaders, timeout10) data resp.json()[data][diff] return [{code: item[f12], name: item[f14]} for item in data]注意这里一定要带上Referer头否则容易被拦截。东财有分页机制pz最大可以拉500个你可以把pn循环几次拿完全量。这个接口我测试过比手动网页翻盘要快几个量级。2.3 日K线采集核心逻辑与参数细节拿到代码列表后就可以用K线接口批量拉数据。K线接口用的是secid参数这个参数有讲究上交所股票是1.600000这样的格式深交所股票是0.000001这样的格式。前面那个占位数字代表交易所别搞反。def get_kline(code, market1, days1000): secid f{market}.{code} url https://push2his.eastmoney.com/api/qt/stock/kline/get params { secid: secid, fields1: f1,f2,f3,f4,f5,f6, fields2: f51,f52,f53,f54,f55,f56,f57,f58,f59,f60,f61, klt: 101, # 101代表日K线 fqt: 1, # 1前复权 2后复权 0不复权 beg: 20200101, end: 20500101, } headers {**BASE_HEADERS} resp requests.get(url, paramsparams, headersheaders, timeout10) data resp.json()[data][klines] rows [] for item in data: parts item.split(,) # 日期,开盘,收盘,最高,最低,成交量,成交额,振幅,涨跌幅,涨跌额,换手率 rows.append({ date: parts[0], open: float(parts[1]), close: float(parts[2]), high: float(parts[3]), low: float(parts[4]), volume: float(parts[5]), amount: float(parts[6]), amplitude: float(parts[7]), pct_change: float(parts[8]), change: float(parts[9]), turnover: float(parts[10]), }) return rows拿到的是CSV格式字符串split处理最简单。我把每一个字段都解析成结构化字典方便后面直接塞进SQLAlchemy。采集的过程有没有做过重试机制很重要网络抖动在长跑时一定会发生。我的做法是加一个简单的retry装饰器失败三次才放弃。2.4 请求频率与反爬的平衡别把你的IP玩没了行情接口的限频不像社交媒体爬虫那么严但也不代表你可以无限请求。我早期犯过一个错用for循环一次性拉四百只票每只之间不设间隔结果跑了五分钟就被封了IP一个小时才恢复。后来我总结出一套保守但稳定的节奏单只票K线请求之间间隔0.2秒到0.5秒。每拉50只票强制sleep两秒。如果遇到429或者403状态码退避重试第一次等待两秒第二次四秒第三次八秒呈指数退避。这套节奏虽然慢一点但一天增量更新最多几百只票完全能接受。长期稳定不被封比一时拉得快重要多了。另外requests的Session对象要复用它默认会使用连接池能省下重复握手的时间。3. 干净的数据才是策略的基石清洗与持久化很多做量化的朋友会忽略掉一个关键问题你从接口拿到的原始数据和水晶球一样干净吗我告诉你一点都不干净。停牌、缺失、编码、复权跳变这些坑每一个都能让你的策略算出离谱的结果。3.1 拿到手的数据长什么样复权、停牌、缺失值先说复权。东财的fqt参数有三个值0是不复权1是前复权2是后复权。什么叫复权当股票发生分红送股时股价会突然跳空比如除权日股价从20块直接变成15块这不是市场砸盘是股本变大了每股对应的净资产变少。如果不做复权处理你的均线、MACD这些指标会在除权日出现一个假拐点然后策略会给出莫名其妙的信号。前复权的意思是以当前价为基准把过去的价格做一个比例调整让价格曲线连续。这就是为什么我基本只选fqt1的原因——做技术分析你看到的历史价格应该是连续、无跳空的这样指标计算结果才可信。再说停牌。股票停牌当天K线接口不会返回那天的记录这会导致时间序列不够连续。如果你后续用shift(1)做昨日收益率计算停牌前后的计算结果会出错。解决办法在下游用reindex补全日期缺失值填充为上一交易日的收盘价。3.2 SQLAlchemy建表与增量更新为什么别用CSV我知道很多入门教程喜欢让学生把数据存成CSV确实简单但放到长期维护的项目里CSV有致命问题并发写入不安全、增量更新要么全量覆盖要么手动处理一张几万行的表格加载速度也会拖慢计算。所以我从一开始就用SQLAlchemy建表。股票日线表的ORM模型可以这么写from sqlalchemy import create_engine, Column, Integer, String, Float, Date, UniqueConstraint from sqlalchemy.orm import declarative_base Base declarative_base() class StockDaily(Base): __tablename__ stock_daily id Column(Integer, primary_keyTrue, autoincrementTrue) code Column(String(10), nullableFalse, indexTrue) date Column(Date, nullableFalse) open Column(Float) close Column(Float) high Column(Float) low Column(Float) volume Column(Float) amount Column(Float) pct_change Column(Float) __table_args__ (UniqueConstraint(code, date, nameuniq_code_date),) def __repr__(self): return fStockDaily {self.code} {self.date}关键在这两个点一是code和date的组合唯一约束这是防止重复数据的关键二是用ORM定义这样后续插入数据时直接用session.add不用手写SQL。3.3 增量更新策略不重复拉历史每天只拉缺失数据全量采集只需要做一次以后每天运行都走增量更新逻辑。核心思路是查一下库里每个股票代码的最大日期然后从那一天开始重新拉取。def get_latest_date(session, code): latest_date session.query(func.max(StockDaily.date)).filter(StockDaily.code code).scalar() return latest_date or datetime(2020, 1, 1).date() def update_stock(session, code, market): latest get_latest_date(session, code) beg (latest - timedelta(days10)).strftime(%Y%m%d) # 多拉几天防止漏数据 rows get_kline(code, market, begbeg) for row in rows: row_date datetime.strptime(row[date], %Y-%m-%d).date() if row_date latest: obj StockDaily(codecode, daterow_date, **row) session.add(obj) session.commit()注意我故意把开始日期往前推了10天这是为了弥补节假日无数据和接口偶尔延迟导致某天数据未返回的空档。插入时因为有唯一约束重复的日期会直接报错或跳过你可以在批量插入时用session.execute(insert().prefix_with(OR IGNORE))来做幂等插入。3.4 数据校验入库前多看一眼策略才能少踩坑数据入库前我建议做这几项基本校验能帮你拦住脏数据检查关键字段是否为None或NaN尤其是收盘价。检查同一天同一个代码是否重复出现。检查收盘价、最高价、最低价的逻辑关系——最低价应该小于等于收盘价最高价应该大于等于收盘价如果不满足这行数据一定有问题。这个校验逻辑不要做得多复杂简单跑一遍就行关键是让它在每次采集后自动执行不要依赖人工抽查。我吃过一次亏某天返回的数据里收盘价字段解析出错变成了字符串入库后所有指标全计算出NaN回测结果直接不能看。从那以后入库前的类型检查就成了固定步骤。4. 量化指标计算把数字变成交易信号数据到位后就进入整个系统最好玩的部分——把裸价格变成有语义的信号。这一层做的事情是读取股票的日K数据用pandas Vectorized操作计算各类技术指标最后按照你定义的规则生成交易信号。4.1 MA、RSI、MACD三个最基础的指标够用且好用技术指标成千上万但我个人觉得对于第一版私人分析师三个指标足够了MA均线系统判断趋势方向RSI判断超买超卖MACD确认趋势加速或减速。这仨覆盖了趋势跟踪和震荡识别两个维度。计算方式用pandas实现非常简单def calculate_indicators(df): # MA均线5日、10日、20日、60日 for n in [5, 10, 20, 60]: df[fma{n}] df[close].rolling(windown).mean() # RSI14日相对强弱指标 delta df[close].diff() gain delta.clip(lower0) loss -delta.clip(upper0) avg_gain gain.rolling(window14).mean() avg_loss loss.rolling(window14).mean() rs avg_gain / avg_loss df[rsi14] 100 - (100 / (1 rs)) # MACD12日EMA - 26日EMA信号线取9日EMA exp12 df[close].ewm(span12, adjustFalse).mean() exp26 df[close].ewm(span26, adjustFalse).mean() df[macd] exp12 - exp26 df[macd_signal] df[macd].ewm(span9, adjustFalse).mean() df[macd_hist] df[macd] - df[macd_signal] return df有几点需要特别说明adjustFalse是必须的pandas默认的EWM计算方式是调整式和股票软件里常用的指标公式不一致。要用非调整式才算得出你在同花顺里看到的MACD数值。RSI计算中如果avg_loss为0说明这段时间只有涨没有跌RS为无穷大RSI约等于100。处理时可以把结果clip在0到100之间。rolling窗口的数值和不同软件有差异比如RSI有9日和14日两种口径我一般用14日这是市场主流的默认值。4.2 向量化计算为什么不要用for循环用Python写量化计算一条铁律就是能用向量化运算就用向量化。什么是向量化简单说就是对整个Series做操作而不是一行一行地循环。比如MA5用df[close].rolling(5).mean()是一次性算完整列如果用for循环遍历每一行3000行数据要循环3000次这个差别在单只股票上感觉不到但如果你要算500只股票循环版本耗时就是向量化的几十倍。pandas里常用的向量化操作包括diff()计算前后差值。rolling().mean()/.std()/.max()/.min()滑动窗口统计。ewm().mean()指数加权移动平均。shift(n)整列向下平移n行这是算收益率、做信号延迟判断的关键。4.3 信号合成别只信单一指标条件叠加才有意义单一指标的问题是误报太高。比如RSI低于30你买进在下跌趋势中可能越买越跌。所以我会把多个条件叠加形成复合信号准确率会明显高一些。举个例子我的趋势确认买入规则def generate_signal(df): df df.copy() # 信号列1表示买入关注-1表示卖出预警0表示无操作 df[signal] 0 # 条件MA5上穿MA20且MACD柱从负转正 ma5_above_ma20 (df[ma5] df[ma20]) (df[ma5].shift(1) df[ma20].shift(1)) macd_turn_positive (df[macd_hist] 0) (df[macd_hist].shift(1) 0) df.loc[ma5_above_ma20 macd_turn_positive, signal] 1 # 条件MA5下穿MA20或MACD柱从正转负 ma5_below_ma20 (df[ma5] df[ma20]) (df[ma5].shift(1) df[ma20].shift(1)) macd_turn_negative (df[macd_hist] 0) (df[macd_hist].shift(1) 0) df.loc[ma5_below_ma20 | macd_turn_negative, signal] -1 return df这里有个关键的细节判断上穿不能只看当前值大于均线还要看前一刻是否小于均线这就是shift(1)的用途。你要是漏掉shift会把已经在均线之上的每一天都判定为金叉点信号会爆掉。这套规则写好后每天跑一次增量数据更新然后对所有自选股执行这个函数就能得到一份完整的信号清单。5. 策略回测先问代码再问自己策略写完之后最激动也最容易走偏的环节就是回测。回测的目的不是说这个策略历史收益高所以未来也高而是验证在设定的规则下信号出现后是否能获得合理的统计结果。很多人回测时掉进了高收益陷阱原因多半是没处理好前视偏差和手续费。5.1 自己写一个20行的简化回测框架我不想一上来就推荐复杂的第三方回测库其实核心逻辑很简单遍历每一天判断当前是否有信号有买入信号且没有持仓时第二天开盘买入有卖出信号且持仓时第二天开盘卖出记录每一笔交易。def run_backtest(df, init_cash100000): df df.reset_index(dropTrue) cash init_cash position 0 # 持股数量 buy_price 0 trades [] for i in range(1, len(df)): # 只能用前一天收盘后产生的信号当天开盘执行防止未来函数 signal df.loc[i - 1, signal] date df.loc[i, date] open_price df.loc[i, open] if signal 1 and position 0: position int(cash * 0.95 / open_price / 100) * 100 # 留5%备用 cash - position * open_price * 1.0003 buy_price open_price trades.append({date: date, type: buy, price: open_price}) elif signal -1 and position 0: cash position * open_price * 0.9997 # 扣除手续费 trades.append({date: date, type: sell, price: open_price, return: open_price / buy_price - 1}) position 0 # 如果最后还持仓按最后收盘价平仓 if position 0: cash position * df.iloc[-1][close] * 0.9997 final_value cash return final_value, trades这个框架有几个重要约定信号延迟执行第i-1天产生的信号第i天开盘才执行。绝不能第i天收盘算出的信号第i天收盘去买卖那是典型的未来函数。手续费买入万三、卖出万三加印花税千一我用0.0003和0.0007近似。很多人回测看着翻倍实际跑起来收益少一截就是没算手续费。仓位控制每次只投95%的现金防止因一手股票价格过高导致无法整百买入。5.2 收益率、夏普比率、最大回撤三个最关键的体检指标回测完不能只看最后赚了多少钱要看风险调整后的收益。至少算这三个指标累计收益率total_return final_value / init_cash - 1年化收益率把累计收益率换算成年化。假设回测跨度是N个交易日年化就是(1 total_return) ** (252 / N) - 1252是全年大概的交易日数量。夏普比率它衡量的是每承受一单位风险能获得多少超额回报超出一倍以上的水平就说明是策略在起作用而不是运气。# 需要构建每日收益率序列 df[daily_return] df[close].pct_change() sharpe (df[daily_return].mean() - risk_free_rate / 252) / df[daily_return].std() * (252 ** 0.5)无风险利率现在可以取一年期国债收益率大约两个点上下。注意要除以252折算成日收益率因为你的收益序列是按天计算的。最大回撤从任意高点回落到低点的最大幅度直观表达你最惨的时候亏损了多少。cum_return (1 df[daily_return]).cumprod() rolling_max cum_return.cummax() drawdown (cum_return - rolling_max) / rolling_max max_drawdown drawdown.min()最大回撤40%的策略就算总收益高你可能也很难拿住因为中间过程太煎熬了。所以回测一定要连回撤一起看。5.3 前视偏差与过拟合回测都会骗你回测最容易骗人的地方有两个。第一个是前视偏差也叫未来函数。比如你用了当天的收盘价做判断又用当天收盘价买入这在现实中无法实现因为收盘价只能在收盘后才知道。我在上面的框架里特意把信号和交易分成两天就是为了杜绝这个问题。量化界有一句话叫如果你在回测里看到了未来那最后的结果就是被未来打脸。第二个是过拟合。你去优化参数时发现MA5/MA20配RSI14收益最高换成RSI13收益就差很多。这说明策略只是在历史数据上背下了答案而不是找到了规律。常规做法是划分样本内样本外区域用样本内数据调参用样本外数据验证。样本外表现还能说的过去才说明策略有泛化能力。6. 实测上线后踩过的坑完整的排查过程记录我必须老实说上面那些代码在我本地跑得通不等于你的环境跑得通。整个系统从能跑到稳定跑中间隔了不知道多少个坑。我把印象最深的几个记录下来每一个都是我自己踩进去又爬出来的。6.1 明明浏览器能打开接口requests却返回403排查过程如下先用浏览器打开接口URL发现完全正常。再用requests直接请求返回403。这个现象说明服务器检测到了爬虫特征而不是接口本身有问题。我试着加了一个普通UA头还是403。加上Referer头恢复正常。所以关键不是UA的问题是Referer——东财的行情接口要求请求来源必须是它的页面域名这是最常见的反爬校验之一。最终我直接用了一个带常用UA和Referer的常量头一大半403问题就消失了。如果还不行再看看Cookie。有一次我发现自己session里带了一个过期的Cookie反而被拦截。处理办法是主动清空Cookie只保留必要的请求头。6.2 数据量一多SQLite写入越来越慢连接池与WAL模式前期数据量小的时候SQLite写入毫无压力。跑到第三个月数据库里积累了十几万条日K记录每次增量更新需要插入几百条数据然后整体速度突然变慢最夸张的一次更新跑了将近一分钟。排查思路是这样的先看是不是单条插入太慢——确实我当时用session.add循环插入400条数据要开400次隐式事务。优化方向是改成批量插入用session.bulk_save_objects或者session.execute(insert(), list_of_dicts)这个改动带来接近10倍的提速。然后我发现数据库的锁等待问题依然存在。SQLite默认是rollback journal模式读写互相阻塞。解决方式是用一行PRAGMA打开WAL模式from sqlalchemy import event from sqlalchemy.engine import Engine event.listens_for(Engine, connect) def set_sqlite_pragma(dbapi_connection, connection_record): cursor dbapi_connection.cursor() cursor.execute(PRAGMA journal_modeWAL) cursor.execute(PRAGMA synchronousNORMAL) cursor.close()WAL模式下读和写可以并行日常增量更新几乎不再遇到锁等待。如果你打算长期用SQLite这个设置一定要加上。6.3 除权除息日数据跳变前复权数据的隐性延迟又一个经典坑。某只股票当天除权股价从30块跳到25块。我的策略在除权前持有回测显示除权那天亏损16%但实际账户一分钱没亏因为送的红股也折算到持仓里了。问题的根因是我在历史回测时用了前复权数据但增量更新时接口默认也返回前复权这就会导致一个问题前复权数据每个交易日可能因为最新的分红事件而整体更新历史价格会随之变化你的历史信号是基于旧的价格算出来的但信号已经落库了不会重新计算。这是量化系统的经典难题用前复权历史数据做回测但每次有新分红事件时历史K线会整体重写导致历史信号和最新数据对不上。我的处理方式是数据库里同时存前复权和不复权两个价格序列。指标计算、回测验证用前复权但账户盈亏的判断用不复权——因为真实账户的买入卖出价格都是不复权价。这样虽然存储量翻倍但能避免很多逻辑混乱。6.4 定时任务编排别用sleep循环用系统的cron系统稳定之后我把它部署在服务器上希望每天早上开盘前自动更新一次数据、跑一次指标、推送结果。一开始我用Python的while True sleep实现定时后来发现这玩意有两个问题日志中断后无法恢复进程死了没人拉起来睡眠循环还占用系统资源。正确做法是用crontab。每天的定时任务配置如下# 周一至周五早上8点30分运行 30 8 * * 1-5 cd /home/quant /usr/bin/python3 run_daily.py logs/daily.log 21关键点在于日志要重定向到文件不然cron跑完了什么输出都看不到出问题都不知道原因。同时建议把run_daily.py里每次跑完打印一行OK和统计信息这样你可以用tail -f logs/daily.log快速确认系统健康。7. 关于这套系统的边界思考与经验总结写到这里核心代码和运行思路基本都交代完了。最后分享一点我在真实使用中形成的体会。这套系统最大的价值不在于给你一个稳赚不赔的信号而在于它强迫你把投资逻辑想清楚。你可以不看任何行情软件每天只打开数据库里的信号清单看看今天有没有符合条件的标的。如果没有就安心做自己的事不用手动翻几百个页面。如果出现了信号再去结合基本面、新闻做二次判断——代码负责排序人才负责决策。从爬虫的角度看这套东西也是一个小型数据工程的完整打通requests采集、SQLAlchemy持久化、pandas处理、定时任务调度每一步都是真实项目里会遇到的技能不是教程里那种循环打印hello world。如果打算上生产我有几条实际建议第一数据备份比代码重要数据库每天自动备份一次出问题一天内可恢复。第二接口文档会变东财的参数结构偶尔会调整所以解析层要单独封装接口变了只改一个文件。第三不要把全部资金押在任何单一策略上系统只能辅助决策后果要自己承担。最后分享一个小技巧把每次触发信号的截图或者数据快照存下来比如2025-03-14 600005信号1MA金叉 MACD翻红。几个月后再回头对比你会对自己的策略有更直观的认识——这套系统不仅能帮你分析股票还能帮你分析自己的决策习惯。这也许才是私人分析师最容易被低估的价值。
阅读完成 · 觉得有帮助?
咨询建站