GitHub:https://github.com/enigmampc/catalyst
官方文档:https://enigma.co/catalyst/index.html
参考视频:网易云课堂《从零搭建数字货币量化交易系统》
系统环境:macOS High Sierra 10.13.6
这篇我们在Catalyst的官方示例dual_moving_average.py的基础上研究双均线策略。
一. 策略要点
1. 短期移动均线上穿长期移动均线,买入
2. 短期移动均线下穿长期移动均线,卖出
如图所示(注意:绿涨红跌,这是国际惯例):
3. 买入和卖出的时机(交易逻辑)
如图所示:第一根K线的短周期均线在长周期均线下方,第二根K线的短周期均线上穿长周期均线,当且仅当第二根K线结束我们才能确认这个买点形成,在catalyst中,我们可以在第二根K线结束时买入。
二. 代码详解
1. 常数设置和程序初始化
这里我们用bitfinex
交易所的BCH/USD交易对,注意:BCH在bitfinex
交易所的名称是BAB,所以symbol是bab_usd
NAMESPACE = 'dual_moving_average'
log = Logger(NAMESPACE)
SIGNAL_BUY = 'buy' # 买入信号
SIGNAL_SELL = 'sell' # 卖出信号
SIGNAL_INIT = '' # 观望信号
SHORT_WIN = 5 # 短周期窗口
LONG_WIN = 20 # 长周期窗口
def initialize(context):
"""
初始化
"""
context.i = 0 # 经历过的交易周期
context.asset = symbol('bab_usd') # 交易对
context.base_price = None # 初始价格
context.signal = SIGNAL_INIT # 交易信号
2. 策略实现
def handle_data(context, data):
# 经历过的交易周期大于长周期均线窗口才开始计算
context.i += 1
if context.i < LONG_WIN + 1:
return
# 获取历史数据,返回series
history_data = data.history(context.asset,
'close',
bar_count=LONG_WIN + 1,
frequency="1D",
)
# 获取当前持仓数量
pos_amount = context.portfolio.positions[context.asset].amount
# 计算双均线
"""
pandas.Series.mean: Return the mean of the values for the requested axis.
scalar or Series (if level specified)
pandas.Rolling.mean: Calculate the rolling mean of the values.
Returned object type is determined by the caller of the rolling calculation.
"""
short_avgs = history_data.rolling(window=SHORT_WIN).mean()
long_avgs = history_data.rolling(window=LONG_WIN).mean()
# 策略逻辑
# 短期均线上穿长期均线,买入
if (short_avgs[-2]) < (long_avgs[-2]) and (short_avgs[-1]) >= (long_avgs[-1]) and pos_amount == 0:
# target买入百分比,1代表买入100%,0.5代表买入50%,0代表卖出
order_target_percent(asset=context.asset, target=1)
# 设置交易信号为买入
context.signal = SIGNAL_BUY
# 短期均线下穿长期均线,卖出
if (short_avgs[-2] > long_avgs[-2]) and (short_avgs[-1]) <= (long_avgs[-1]) and pos_amount > 0:
# target买入百分比,1代表买入100%,0.5代表买入50%,0代表卖出
order_target_percent(asset=context.asset, target=0)
# 设置交易信号为卖出
context.signal = SIGNAL_SELL
# 获取当前价格
price = data.current(context.asset, 'price')
# 如果初始价格没设置,把当前的价格设置为初始价格
if context.base_price is None:
context.base_price = price
# 计算价格变化百分比,作为基准
price_change = (price - context.base_price) / context.base_price
# 记录每个交易周期的信息
record(price=price, # 价格
cash=context.portfolio.cash, # 现金
price_change=price_change, # 价格变化率
short_mavg=short_avgs[-1], # 短期均线
long_mavg=long_avgs[-1], # 长期均线
signal=context.signal) # 交易信号
# 输出信息
print('日期:{}, 价格:{:.4f}, 资产:{:.2f}, 持仓量:{:.8f}, {}'.format(
data.current_dt, price, context.portfolio.portfolio_value, pos_amount, context.signal
))
# 重置交易信号
context.signal = SIGNAL_INIT
3. 策略分析和可视化
def analyze(context, perf):
# 保存交易记录
perf.to_csv('performance.csv')
# 获取计价货币(USDT)
exchange = list(context.exchanges.values())[0]
quote_currency = exchange.quote_currency.upper()
# 图1:输出资产值
ax1 = plt.subplot(411)
perf.loc[:, ['portfolio_value']].plot(ax=ax1)
# ax1.legend_.remove()
# 设置y轴
ax1.set_ylabel('Portfolio Value\n({})'.format(quote_currency))
# 设置区间
start, end = ax1.get_ylim()
# 设置刻度
ax1.yaxis.set_ticks(np.arange(start, end, (end - start) / 5))
# 图2:输出资产货币价格、移动均线和买卖点
ax2 = plt.subplot(412, sharex=ax1)
# perf[['price', 'short_mavg', 'long_mavg']].plot(ax=ax2)
perf.loc[:, ['price', 'short_mavg', 'long_mavg']].plot(ax=ax2)
# ax2.legend_.remove()
ax2.set_ylabel('{asset}\n({quote})'.format(
asset=context.asset.symbol,
quote=quote_currency
))
start, end = ax2.get_ylim()
ax2.yaxis.set_ticks(np.arange(start, end, (end - start) / 5))
# 提取交易时间点
transaction_df = extract_transactions(perf) # 交易dataframe
if not transaction_df.empty:
buy_df = transaction_df[transaction_df['amount'] > 0] # 取到amount>0,买入点
sell_df = transaction_df[transaction_df['amount'] < 0] # 取到amount<0,卖出点
ax2.scatter(
buy_df.index.to_pydatetime(),
perf.loc[buy_df.index, 'price'], # 找到index
marker='^',
s=100,
c='green',
label=''
)
ax2.scatter(
sell_df.index.to_pydatetime(),
perf.loc[sell_df.index, 'price'],
marker='v',
s=100,
c='red',
label=''
)
# 图3:比较价格变化率和资产变化率(即比较策略收益率和基准收益率)
ax3 = plt.subplot(413, sharex=ax1)
perf.loc[:, ['algorithm_period_return', 'price_change']].plot(ax=ax3)
# ax3.legend_.remove()
ax3.set_ylabel('Percent Change')
start, end = ax3.get_ylim()
ax3.yaxis.set_ticks(np.arange(start, end, (end - start) / 5))
# 图4:现金数量
ax4 = plt.subplot(414, sharex=ax1)
perf.cash.plot(ax=ax4)
ax4.set_ylabel('Cash\n({})'.format(quote_currency))
start, end = ax4.get_ylim()
ax4.yaxis.set_ticks(np.arange(0, end, end / 5))
plt.show()
4. 主函数
因为BCH是在2018年11月13日上币的,所以开始时间选择这天。
if __name__ == '__main__':
run_algorithm(
capital_base=1000,
data_frequency='daily',
initialize=initialize,
handle_data=handle_data,
analyze=analyze,
exchange_name='bitfinex',
algo_namespace=NAMESPACE,
quote_currency='usd',
start=pd.to_datetime('2018-11-13', utc=True),
end=pd.to_datetime('2019-10-16', utc=True),
)
5. 运行程序
首先导入数据:
catalyst ingest-exchange -x bitfinex -i bab_usd -f daily
运行程序:
可以看到,在2018年12月20日有买入信号,就是前文给出买点的那个图。
6. 输出图像
第一个图是持有BCH资产的价值走势
第二个图中绿色三角形代表买入点,红色三角形代表卖出点
第三个图是策略收益与基准收益的对比
第四个图是现金变动情况