欢迎加入专注于财经数据与量化投研的【数据科学实战】知识星球!在这里,您将获取持续更新的《财经数据宝典》和《量化投研宝典》,这两部宝典相辅相成,为您在量化投研道路上提供明确指引。 我们提供了精选的国内外量化投研的 120+ 篇高质量文章,并每日更新最新研究成果,涵盖策略开发、因子分析、风险管理等核心领域。 无论您是量化投资新手还是经验丰富的研究者,星球社区都能帮您少走弯路,事半功倍,共同探索数据驱动的投资世界!
引言
加密货币市场以其高波动性而闻名,这既是机遇也是挑战。如何在这样的市场中建立一个有纪律的交易系统?本文将探讨一个基于夏普比率(Sharpe Ratio)的简单交易策略,该策略使用 Python、Backtrader 和 CCXT 库实现,可以帮助我们在比特币、以太坊等加密货币市场中做出更理性的交易决策。
策略核心思想
夏普比率是衡量风险调整后收益的重要指标。我们的策略基于以下简单逻辑:
- 当短期夏普比率非常正面(如 > 2)时,市场表现强劲且稳定 → 生成做多信号
- 当短期夏普比率非常负面(如 < -2)时,市场表现持续不佳 → 生成做空信号
策略实现代码
首先,我们需要一个函数来计算滚动夏普比率并生成交易信号:
import numpy as np
import pandas as pd
def periods_per_year(timeframe: str) -> float:
"""根据时间框架计算年化系数"""
n, u = int(timeframe[:-1]), timeframe[-1]
if u == "d": return365.0 / n
if u == "h": return365.0 * 24.0 / n
if u == "m": return365.0 * 24.0 * 60.0 / n
if u == "w": return52.0 / n
raise ValueError(f"不支持的时间框架: {timeframe}")
def compute_sharpe_signals(df: pd.DataFrame, timeframe: str, window: int,
upper: float=2.0, lower: float=-2.0):
"""计算滚动夏普比率并生成交易信号"""
df = df.copy()
df["ret"] = df["Close"].pct_change() # 计算每个周期的收益率
roll_mean = df["ret"].rolling(window).mean()
roll_std = df["ret"].rolling(window).std()
ann = np.sqrt(periods_per_year(timeframe))
df["rolling_sharpe"] = (roll_mean / roll_std).replace([np.inf, -np.inf], np.nan)
df["signal"] = 0
df.loc[df["rolling_sharpe"] > upper, "signal"] = 1
df.loc[df["rolling_sharpe"] < lower, "signal"] = -1
df["signal_shifted"] = df["signal"].shift(1).fillna(0).astype(int) # 今天决策,下一个周期行动
return df.dropna()
风险管理设计
任何交易策略都需要风险管理。在我们的策略中,添加了以下保护措施:
- 最大持有期限:最多持有 7 个交易周期(使用日线时为 7 天)
- 交易成本:考虑了手续费(0.04%)和滑点(0.05%)
这确保我们不仅追随信号,也在保护资金。
class SharpeSignalStrategy(bt.Strategy):
"""基于夏普比率的交易策略"""
params = dict(hold_bars=7, stop_loss_pct=0.20, invest_pct=1.0, printlog=False)
def __init__(self):
self.sig = self.datas[0].signal_shifted
self.entry_bar = None
self.entry_price = None
self.stop_order = None
def place_stop(self, side: str):
"""设置止损单"""
# 取消旧的止损单,然后相对于入场价格设置新的止损
if self.stop_order:
self.cancel(self.stop_order)
self.stop_order = None
if side == 'long':
price = self.entry_price * (1 - self.p.stop_loss_pct)
self.stop_order = self.sell(exectype=bt.Order.Stop, price=price)
else:
price = self.entry_price * (1 + self.p.stop_loss_pct)
self.stop_order = self.buy(exectype=bt.Order.Stop, price=price)
def notify_order(self, order):
"""订单状态更新"""
if order.status == order.Completed:
self.entry_price = order.executed.price
self.entry_bar = len(self)
self.place_stop('long'if order.isbuy() else'short')
def next(self):
"""每个交易周期的策略逻辑"""
s = int(self.sig[0])
price = float(self.data.close[0])
# 达到最大持有期限时退出
if self.position and self.entry_bar isnotNone:
if (len(self) - self.entry_bar) >= self.p.hold_bars:
self.close()
if self.stop_order:
self.cancel(self.stop_order)
self.stop_order = None
self.entry_bar = None
self.entry_price = None
return
# 空仓时入场
ifnot self.position and s != 0:
notional = self.broker.getvalue() * self.p.invest_pct
size = max(int(notional // price), 1)
self.buy(size=size) if s == 1else self.sell(size=size)
数据获取
我们使用 CCXT 库从币安获取 USDC 交易对的历史数据:
import ccxt, time
from datetime import datetime, timezone
import
pandas as pd
def timeframe_to_ms(tf: str) -> int:
"""将时间框架转换为毫秒"""
n, u = int(tf[:-1]), tf[-1]
return n * {"m": 60_000, "h": 3_600_000, "d": 86_400_000, "w": 604_800_000}[u]
def fetch_ohlcv_paginated(exchange, symbol, timeframe, since_ms, until_ms=None,
limit=1000, pause=1.0):
"""分页获取 OHLCV 数据"""
out, cursor, tf_ms = [], since_ms, timeframe_to_ms(timeframe)
until_ms = min(until_ms, int(time.time() * 1000))
while cursor < until_ms:
for attempt in range(5):
try:
batch = exchange.fetch_ohlcv(symbol, timeframe=timeframe,
since=cursor, limit=limit)
break
except (ccxt.NetworkError, ccxt.ExchangeError):
time.sleep(pause * (attempt + 1))
else:
break
ifnot batch: break
out.extend([r for r in batch if r[0] < until_ms])
cursor = batch[-1][0] + tf_ms
time.sleep(pause)
if len(batch) < limit and cursor + tf_ms >= until_ms: break
return out
def get_ccxt_dataframe(symbol="ETH/USDC", timeframe="1d", start_date="2020-01-01",
end_date=None):
"""获取并处理 CCXT 数据为 DataFrame"""
ex = ccxt.binance({"enableRateLimit": True, "options": {"defaultType": "spot"}})
ex.load_markets()
assert symbol in ex.markets, f"{symbol} 不在币安现货市场中"
end_dt = datetime.now(timezone.utc) if end_date isNoneelse datetime.fromisoformat(end_date)
start_dt = datetime.fromisoformat(start_date).replace(tzinfo=timezone.utc)
raw = fetch_ohlcv_paginated(ex, symbol, timeframe, int(start_dt.timestamp() * 1000),
int(end_dt.timestamp() * 1000))
df = pd.DataFrame(raw, columns=["datetime","Open","High","Low","Close","Volume"])
df["datetime"] = pd.to_datetime(df["datetime"], unit="ms", utc=True)
return df.set_index("datetime")
回测示例
下面是一个实际的回测示例参数:
run_backtest(
symbol="BTC/USDC",
timeframe="1d",
start_date="2021-01-01",
end_date="2023-01-01",
window=30,
upper_threshold=2.0,
lower_threshold=-2.0,
hold_days=7,
stop_loss_pct=0.20,
)
在测试中,我们可以得到以下结果:
最终组合价值:185,329.35
策略总回报率:61.70%
买入并持有回报率:-41.32%
夏普比率:0.8086785623645067
最大回撤:34.2717594145437%
策略变体尝试
我们可以调整策略参数来适应不同的市场条件:
# 日内交易实验:更多信号,不同的年化系数
run_backtest(
symbol="ETH/USDC",
timeframe="15m",
start_date="2022-01-01",
window=96, # 约等于 15 分钟周期的 1 天
upper_threshold=2.0,
lower_threshold=-2.0,
hold_days=3,
stop_loss_pct=0.15
)
# 更严格的过滤器:更少的交易,更高的确信度
run_backtest(
symbol="BTC/USDC",
timeframe="1d",
start_date="2020-01-01",
window=45,
upper_threshold=2.5,
lower_threshold=-2.5,
hold_days=5,
stop_loss_pct=0.25
)
策略观察与分析
通过对回测结果的分析,我们可以得出以下观察:
- 由于夏普比率 > 2 或 < -2 的情况并不常见,所以该策略交易频率较低
- 与简单的买入持有相比,该策略可能减少了回撤,但在强劲的牛市中可能表现不佳
- 这符合预期:基于夏普的过滤器更注重风险控制,而非最大化原始收益
策略局限性与未来改进方向
本策略仍有以下局限性:
- 阈值选择较为主观:我们选择了 ±2.0,但这些值可以并且应该经过调整
- 回顾窗口很重要:30 天只是一种选择,更短或更长的窗口表现会不同
- 尚未准备好实盘:这是研究回测,不是生产代码。实盘交易需要订单簿数据、执行逻辑和稳健测试
- 尚无仓位管理逻辑:目前每次交易都是全仓,基于风险的仓位管理可能改善结果
可能的扩展方向:
总结
本文展示了如何将夏普比率——一个经典的风险调整性能指标——转化为加密货币市场中的简单交易信号。结果并不神奇,但这正是重点:在早期研究中,实验比优化更重要。通过在 Backtrader 中使用币安数据构建类似的小型原型,我们可以快速测试想法,从结果中学习,并决定哪些方向值得深入探索。
对于 Python 量化交易爱好者来说,这种方法提供了一个良好的起点,帮助你建立自己的交易系统。记住,成功的量化交易不仅仅是复杂的算法,更在于纪律、风险管理和持续的学习与改进。
参考文章
加入专注于财经数据与量化投研的知识星球【数据科学实战】,获取本文完整研究解析、代码实现细节。财经数据与量化投研知识社区
核心权益如下:
- 赠送《财经数据宝典》完整文档,汇集多年财经数据维护经验
-
赠送《量化投研宝典》完整文档,汇集多年量化投研领域经验
- 赠送《PyBroker-入门及实战》视频课程,手把手学习量化策略开发
- 每日分享高质量量化投研文章(已更新120+篇)、代码和相关资料
星球已有丰富内容积累,包括量化投研论文、财经高频数据、 PyBroker 视频教程、定期直播、数据分享和答疑解难。适合对量化投研和财经数据分析有兴趣的学习者及从业者。欢迎加入我们!