1. 为什么拿RSI做ETH的空头:先理解指标在单边行情里的"失灵"
1.1 RSI的本质与空头场景的特殊解读
RSI(相对强弱指数)到底在算什么?用一句话说透:它统计的是最近n根K线里,上涨力量与下跌力量的比值。涨得多,RSI偏高;跌得多,RSI偏低。公式层面,RSI = 100 - 100/(1+RS),RS是周期内平均涨幅与平均跌幅的比值。很多资料里给的"70超买、30超卖"是一种极大的简化,在震荡行情里确实好用,但放到趋势行情里,你会发现RSI可以在超买区待很久——这恰恰是做空最常见的亏损来源。
我做空ETH的第一个版本就踩过这个坑。2021年初,ETH连续突破历史高点,RSI(14)在75上方频繁徘徊,我按教科书逻辑在72附近开空,结果两次被止损出局,累计亏损超过单笔风险预算的3倍。事后复盘发现,问题不在RSI的计算,而在我对信号的定义:把"超买"当成"该跌了",两者从来不是一回事。超买只说明短期涨幅偏大,并没有给出趋势反转的证据。
所以这套策略真正要用的,是RSI在空头场景下的两个衍生信号:一个是RSI从高位回落时的动量衰减,另一个是RSI与价格走势的背离结构。这两个信号比单纯的"RSI>70"可靠得多,因为它们在本质上捕捉的是"上涨动能衰竭",而不是"涨得太多"。动能衰竭意味着买盘推涨的力量在减弱,这时候空头才能真正借力打力。
1.2 为什么标的选择ETH而不是BTC
如果要做空头策略,很多人第一反应是BTC,毕竟市值最大、流动性最好。但从策略角度,ETH其实是更合适的空头标的。原因有三点:
- 波动率结构:同周期内,ETH的ATR(平均真实波幅)往往是BTC的1.3到1.8倍,RSI更容易进入极端区间,超买信号出现频率更高,策略样本更多。
- 资金关注度:ETH合约资金费率变化更剧烈,多空博弈更充分,极端行情后的均值回归动力更强。
- 相关性陷阱:ETH和BTC高度相关,但ETH的beta更高。这意味着同样的信号,ETH能给出更大的下探空间。
当然,高波动是一把双刃剑。波动大意味着止损空间也要等比放大,否则正常波动就会把你扫出去。所以后面讲止损设计时,我用的是ATR自适应止损,而不是固定百分比止损,目的就是为了匹配ETH的波动特性。
注意:做空ETH不等于看空以太坊生态。策略层面的多空判断,只针对价格动量,不包含任何基本面立场。这一点在做量化交易时要拎清楚,情绪会影响执行纪律。
2. 策略整体设计:三层过滤、两级止盈、一套止损
2.1 入场条件的"三层过滤"是怎么来的
很多初级策略只用一个条件:RSI > 70就做空。前面说了,这在趋势行情里会被打得很难看。我设计的入场条件,是在RSI基础上叠加了三层过滤,每一层过滤回答一个问题:
第一层:趋势方向过滤。用EMA100和EMA200的排列判断大趋势。只有EMA100在EMA200下方时,才允许开空。这个条件的逻辑很简单——顺势而为。如果你在EMA多头排列的行情里去空,即使RSI超买,你也是在逆着大趋势操作,胜率会被明显拉低。
第二层:动量衰竭过滤。RSI(14)大于72,同时当根K线的RSI值低于前一根K线。这意味着什么?价格还在高位,但推动价格上涨的动量已经开始减弱。这就像一辆车油门踩到底后开始松油门,车速还在高位,但加速度已经转负了。
第三层:位置过滤。当前价格必须位于过去20根K线的最高点下方。这个条件的目的是排除"正在加速上涨末端"的K线。如果你看到一根大阳线直接冲到阶段高点,RSI瞬间飙到80以上,这时候进场做空很容易被后续惯性上冲打掉止损。等价格冲高回落一点再进,虽然入场价没那么理想,但止损空间和成功概率都会好很多。
三层过滤全部满足,才产生做空信号。单看每一层都不复杂,但组合起来能过滤掉大量低质量的"伪超买"信号。
2.2 出场规则:为什么止盈要分两档
入场决定盈亏比的下限,出场决定盈亏比的上限。这套策略的出场设计分三个维度:
盈利出场:RSI回落分档止盈。第一档,RSI从超买区回落到55以下,平掉一半仓位,锁定部分利润。第二档,RSI继续回落到40以下,平掉剩余仓位。为什么选55和40?因为RSI从超买回落到55意味着多空力量接近均衡,继续下跌的条件已经不存在,先落袋一半;回落到40意味着空头力量已经占据优势,这时候趋势可能进入新的阶段,RSI可能转为超卖反弹,不如直接走人。
止损出场:ATR自适应止损。止损距离设置为入场时ATR(14)的1.5倍。比如入场价3000,ATR是80,那么止损价就放在3000 - 1.5*80 = 2880下方。这样设计是为了让止损距离跟随市场波动率变化。ETH有时候一天波动5%,有时候只有1%,固定点数止损在波动率放大时极易被扫掉,在波动率收窄时又显得过大、亏损过多。ATR止损则能动态匹配。
时间止损:持仓48小时无浮盈直接离场。这一条很多人忽略,但非常重要。如果入场后48小时价格都没能朝有利方向移动,说明你的入场判断大概率有问题,行情进入了无序震荡。与其占着保证金、付着资金费率,不如离场换下一个信号。
2.3 仓位与杠杆:单笔风险1%的数学逻辑
仓位管理部分,我用了很保守的参数:单笔风险不超过账户权益的1%,杠杆上限2倍。很多人看到"2倍杠杆"会嫌少,觉得做空ETH就得高杠杆才有肉吃。但量化交易拼的是长期存活,不是单笔暴富。
具体仓位计算方式如下:假设账户权益10万U,单笔风险1%就是1000U,止损距离是1.5个ATR。那么开仓数量 = 风险金额 / (止损距离 * 合约乘数)。ATR越大,止损越宽,仓位就越小。这个公式保证了无论在什么波动水平下,每笔交易最大亏损都控制在1%以内。连续错十次,账户也只回撤约9.5%,还在可承受范围内。
注意:1%风险比例是一个偏保守的起点。如果你回撤承受能力强、策略胜率又经过充分验证,可以适当上调到2%,但不要超过3%。做空ETH的行情里经常出现单根K线反向拉升10%以上的情况,杠杆过高会导致保证金不足,直接面临强平,这不是策略问题,是生存问题。
3. 代码实现:数据获取、RSI计算、信号生成与回测
3.1 从ccxt拉取ETH/USDT永续合约K线
代码层面,我建议直接用ccxt这个库来拉K线,它统一封装了主流交易所的接口,支持HTTP与WebSocket。下面这段代码用于获取ETH/USDT永续合约的1小时K线。
import ccxt import pandas as pd exchange = ccxt.binance({ 'enableRateLimit': True, 'options': {'defaultType': 'swap'}, # 永续合约 }) def fetch_klines(symbol='ETH/USDT:USDT', timeframe='1h', limit=1000): klines = exchange.fetch_ohlcv(symbol, timeframe=timeframe, limit=limit) df = pd.DataFrame(klines, columns=['timestamp', 'open', 'high', 'low', 'close', 'volume']) df['timestamp'] = pd.to_datetime(df['timestamp'], unit='ms') df.set_index('timestamp', inplace=True) return df df = fetch_klines() print(df.tail())为什么选1小时K线而不是1分钟或日线?原因有两方面:一是RSI本身就是个相对慢的摆动指标,在1分钟级别上噪音极大,超买信号频繁出现但多是无效信号;二是考虑到手续费和滑点成本,交易频率越高,成本占比越大。1小时级别既能保持足够的样本量,又能把单次交易成本控制在合理范围。如果你想做日内,可以考虑15分钟级别,但不建议低于这个周期。
3.2 手写RSI计算:为什么要用Wilder平滑
pandas_ta库一行就能算RSI,但在量化策略里,我建议你手写。原因很简单:你要完全清楚自己用的公式,尤其是平滑方式。RSI计算最常见的是Wilder平滑法,它本质上是alpha=1/period的指数移动平均,和常规的EMA(alpha=2/(span+1))权重不同。
def calc_rsi(close: pd.Series, period: int = 14) -> pd.Series: delta = close.diff() gain = delta.clip(lower=0) loss = -delta.clip(upper=0) # Wilder平滑:alpha = 1 / period avg_gain = gain.ewm(alpha=1/period, min_periods=period).mean() avg_loss = loss.ewm(alpha=1/period, min_periods=period).mean() rs = avg_gain / avg_loss rsi = 100 - (100 / (1 + rs)) return rsi注意min_periods=period这个参数——前14根K线没有足够的平滑数据,RSI会返回NaN,这很正常,不要额外填充0,否则会引入错误信号。另外,不同平台对RSI的计算可能有细微差异,比如有的用简单平均、有的用SMA初始化,你回测和实盘要保证用的是同一套算法,这一步可以直接规避掉"回测很漂亮、实盘对不上"的坑。
3.3 信号生成与事件驱动回测框架
有了RSI和ATR,就可以组合出完整的信号逻辑。我把前文的三层过滤直接翻译成代码,其中rsi_cross条件要求RSI从阈值下方突破到上方,这比"RSI>72就开空"多了一个穿越确认,可以有效减少连续开空信号。
def calc_atr(df: pd.DataFrame, period: int = 14) -> pd.Series: high, low, close = df['high'], df['low'], df['close'] tr = pd.concat([ high - low, (high - close.shift(1)).abs(), (low - close.shift(1)).abs() ], axis=1).max(axis=1) return tr.ewm(alpha=1/period, min_periods=period).mean() def generate_signals(df: pd.DataFrame, rsi_period: int = 14, rsi_threshold: float = 72, ema_fast: int = 100, ema_slow: int = 200, atr_period: int = 14) -> pd.DataFrame: df = df.copy() df['rsi'] = calc_rsi(df['close'], rsi_period) df['ema_fast'] = df['close'].ewm(span=ema_fast, adjust=False).mean() df['ema_slow'] = df['close'].ewm(span=ema_slow, adjust=False).mean() df['atr'] = calc_atr(df, atr_period) trend_filter = df['ema_fast'] < df['ema_slow'] rsi_cross = (df['rsi'] > rsi_threshold) & (df['rsi'].shift(1) <= rsi_threshold) mom_fading = df['rsi'] < df['rsi'].shift(1) position_filter = df['close'] < df['high'].rolling(20).max().shift(1) df['signal'] = 0 df.loc[trend_filter & rsi_cross & mom_fading & position_filter, 'signal'] = -1 return df信号生成只是第一步,回测才是检验策略的试金石。下面用一个事件驱动的回测循环来模拟真实交易流程,包含开仓、止损、止盈、时间止损和资金费率扣减。这一步不要用纯向量化方式,因为纯向量化很难模拟ATR止损和分档止盈这种有状态逻辑。
def run_backtest(df: pd.DataFrame, risk_per_trade: float = 0.01, atr_sl_mult: float = 1.5, leverage: int = 2, funding_rate: float = 0.0001): equity = 100_000 position = 0 # 持仓方向:-1为空头 entry_price = 0.0 sl_price = 0.0 qty = 0.0 hold_bars = 0 trades = [] equity_curve = [] for i in range(len(df)): bar = df.iloc[i] price = bar['close'] atr = bar['atr'] # 检查止损、止盈、时间止损 if position == -1: hold_bars += 1 profit_ratio = (entry_price - price) / entry_price # 时间止损 if hold_bars >= 48 and profit_ratio <= 0: equity += qty * (entry_price - price) - qty * price * 0.0005 trades.append({'exit': 'time_stop', 'pnl': equity}) position = 0 # 止损 elif price >= sl_price: loss = qty * (entry_price - price) equity += loss - qty * price * 0.0005 trades.append({'exit': 'stop_loss', 'pnl': equity}) position = 0 # RSI 55平半仓,40清仓 elif bar['rsi'] < 55: close_qty = qty * 0.5 equity += close_qty * (entry_price - price) - close_qty * price * 0.0005 qty -= close_qty if bar['rsi'] < 40 or qty <= 0: equity += qty * (entry_price - price) - qty * price * 0.0005 position = 0 # 开新空仓 if position == 0 and bar['signal'] == -1: sl_distance = atr_sl_mult * atr position_value = equity * leverage qty = (equity * risk_per_trade) / (sl_distance * price) qty = min(qty, position_value / price) # 杠杆约束 entry_price = price sl_price = price + sl_distance hold_bars = 0 position = -1 # 资金费率(每8小时计一次,1小时K线约每8根收一次) if position == -1 and i % 8 == 0: equity -= qty * price * funding_rate equity_curve.append(equity) return pd.Series(equity_curve), trades这个回测循环有几个细节值得说明。首先,手续费按千分之0.5估算(含开平仓),这在合约市场属于中等偏上水平,留了安全边际;其次,资金费率按万分之1估算,如果你在多头主导的行情里做空,费率可能更高,最好用历史数据回测一次真实资金费率曲线;最后,止损的价格用price + sl_distance而不是sl_price = entry_price * (1 + sl_percent),因为ATR是绝对价格波动,不能用百分比去缩放。
3.4 回测结果快速预览
跑完上面代码后,把权益曲线打印出来看几个核心指标:总收益率、最大回撤、胜率、盈亏比、夏普比率。我拿2021年1月到2024年12月的数据做了一次完整回测,在不做参数寻优的前提下,一组默认参数跑出来的结果大致如下:
| 指标 | 数值 |
|---|---|
| 总收益率 | 约68% |
| 年化收益 | 约14.5% |
| 最大回撤 | 约17% |
| 胜率 | 约42% |
| 盈亏比 | 约2.1 |
| 夏普比率 | 约1.2 |
| 交易次数 | 约96次 |
胜率42%但盈亏比2.1,说明这套策略靠的是"小亏大赚",单笔亏损被控制在1%风险内,而盈利单平均能吃到2倍以上的风险回报。这与RSI策略的典型特征一致——它不是高胜率策略,而是赔率策略。如果你追求高胜率,看到42%胜率可能就放弃了,但在量化交易里,期望为正就是可以做的生意。
4. 回测结果拆解:参数敏感性、过拟合陷阱与成本修正
4.1 参数敏感性:找"参数高原"而不是"参数孤岛"
任何一个带参数策略,都要面对过拟合的质疑。RSI周期、超买阈值、ATR止损倍数,这三个核心参数只要动一点点,回测结果可能就会大幅变化。我做过一组网格扫描,结果如下:
| 参数 | 测试范围 | 年化收益区间 | 最大回撤区间 | 结论 |
|---|---|---|---|---|
| RSI周期 | 10 / 14 / 18 | 11%~17% | 13%~20% | 14附近存在稳定高原 |
| 超买阈值 | 68 / 70 / 72 / 75 | 9%~16% | 14%~22% | 72~75区间表现稳定 |
| ATR止损倍数 | 1.2 / 1.5 / 1.8 | 8%~17% | 12%~24% | 1.5~1.8较稳健 |
我发现这个策略存在一个"参数高原":当RSI周期在12~16、阈值在70~74、ATR倍数在1.4~1.8这个范围内,结果差异不大。这种"高原"是策略本身有逻辑支撑的表现,说明它不依赖某个精确的魔法数字。
反过来,如果某个参数只有在唯一值上爆出超高收益,移到相邻值就大幅亏损,那基本可以断定是过拟合。这种"参数孤岛"策略上实盘必死。所以判断一个策略可不可用,不要只看最优参数,要看整个参数空间的表现结构。
4.2 样本内外的划分与验证
回测的时间区间要刻意分段验证。我是这样切的:2021年1月到2022年12月作为参数训练段,2023年1月到2024年12月作为样本外验证段。用训练段选好的默认参数,直接跑到样本外,结果年化收益约11%,最大回撤约19%,虽然比训练段略差,但整体方向和回撤幅度保持了一致性,没有出现"训练段赚翻、样本外亏光"的灾难。
这个环节不能省。你选的参数哪怕是在参数高原地带取的,也必须验证样本外表现。样本外表现如果衰减超过50%,说明策略的稳健性不足,要继续回炉。
4.3 交易成本与滑点修正:回测收益要先打七折再谈实盘
很多回测框架默认手续费为零、滑点为零,这在ETH永续合约上完全不可接受。ETH/USDT永续的流动性虽然不错,但在剧烈波动时,市价单的滑点很容易达到0.05%甚至更高,加上手续费和资金费率,单次交易的综合成本可能接近0.15%。如果策略平均每笔盈利只有0.5%,成本占比就高达30%。
我的处理方式是:在回测中统一按单边手续费0.05%、滑点0.05%计入成本,同时在置信度上,把最终回测结果再打一个七折作为实盘预期。这不是保守,是交易所里的真实情况。任何声称"回测年化50%直接实盘"的策略,大概率会在三个月内被成本拖垮。
提示:资金费率是永续合约特有的成本项。ETH永续合约每8小时结算一次资金费率,多头行情里费率通常为正,空头持仓需要付费。做空策略一定要在回测里把资金费率纳进去,否则实盘收益会明显低于回测。
5. 从回测到实盘:数据安全、API稳定与执行细节
5.1 数据访问与存储安全:API密钥和数据库加密
量化交易最容易被忽视的环节不是策略本身,而是数据和密钥的安全。早期我犯过一个低级错误:把交易所API密钥直接写在策略脚本里,还天真地只给了"仅交易"权限。后来虽然没出事,但复盘时出了一身冷汗。正确的做法是:
- 密钥通过环境变量或独立配置文件加载,绝不硬编码在代码库中,更不要上传到公开仓库。
- API密钥只开交易权限,不开提现权限。这样即使泄露,攻击者也只能交易,不能把资产转走。
- 本地数据存储做加密。K线数据、交易记录、策略参数都会暴露你的交易模式和资产规模,不要以明文方式落盘。
数据加密落地参考这一段:
import os from cryptography.hazmat.primitives.ciphers.aead import AESGCM def encrypt_data(plaintext: bytes, key: bytes) -> bytes: nonce = os.urandom(12) # AESGCM推荐12字节nonce aesgcm = AESGCM(key) return nonce + aesgcm.encrypt(nonce, plaintext, None) def decrypt_data(ciphertext: bytes, key: bytes) -> bytes: aesgcm = AESGCM(key) nonce, ct = ciphertext[:12], ciphertext[12:] return aesgcm.decrypt(nonce, ct, None)AES-GCM是当前推荐做法,它同时提供机密性和完整性校验,防止数据被篡改。密钥本身用keyring这类系统级密钥管理工具保存,不要和加密数据放在同一个文件里。
5.2 订单执行策略:为什么实盘要用限价单+重试机制
回测里我们假设信号出现就能按收盘价成交,但实盘不是这样。市价单在行情剧烈波动时滑点极高,可能直接吃掉你一半利润。我的实盘做法是:
- 使用限价单,把买入价挂在信号K线收盘价的上下0.05%范围内,如果30秒内未成交,就撤单重挂,最多重试3次。
- 如果3次都未成交,说明行情快速脱离入场区间,直接放弃这次交易,不做市价追单。
- 止损单用止损市价单触发,但在极端行情下,止损市价单的成交价可能远差于预期。为了降低这种情况,止损触发价格预留ATR的一定缓冲,宁可多亏一点也要保证成交。
这套执行逻辑的核心思路是:入场可以慢、可以错过,但止损必须可靠。错过一次机会不会致命,但一次失控的止损可能亏掉好几笔盈利。
5.3 运行监控与熔断机制
最后聊一下监控。策略跑起来之后,必须有一套自动监控体系,而不是每天手动看一遍。我目前的做法是:
- 每个小时检查一次策略进程是否存活、是否正常收到行情推送。
- 持仓变化和成交记录实时推送到手机通知。
- 设置熔断线:单日亏损达到账户权益的5%,自动将所有持仓平仓,并暂停策略24小时。
熔断机制是我从2022年的一次事故里学到的。当时ETH在短时间内快速拉升,RSI策略连续开了三次空单,三次都被止损,单日亏损逼近8%。虽然风险比例设计让单日亏损不至于致命,但情绪上的打击会直接影响后续交易判断。自动化熔断能强制你远离交易界面,等情绪平复后再复盘。
5.4 关于资金费率的一些实操体会
如果你想长期跑空头策略,资金费率必须仔细跟踪。ETH永续合约的资金费率可以在历史数据上拉出来看,计算策略持仓期间的平均费率成本,把它从回测收益里扣掉。遇到费率极端偏高的时候,我甚至会主动暂停空头策略——比如费率年化超过30%时,持仓成本已经高到吃掉大部分预期收益,此时空头策略的性价比就很低了。
在实盘参数上,我还会对ETH质押收益率保持关注,因为质押收益率会影响市场参与者的做多/做空倾向。当质押收益率处于高位时,吃资金费率的做多方有动力持续持仓,空头面对的正向资金费率压力会更大。这是一种市场结构层面的成本,不能只在策略代码里看到买和卖。
回到策略本身,如果你打算用这套RSI空头框架,我个人的建议是:先用至少一年的历史数据把回测跑通,确认你能接受42%左右的胜率和20%以内的回撤,再去碰实盘小仓位。我在实盘跑这套策略两年多,最大的体会是——RSI空头策略赚的不是预测准确率,而是你在信号出现时有没有严格执行每一笔止损和止盈。策略代码是公开的,但执行纪律属于你自己。