数字货币量化之止盈和止损
完善双均线策略
浮动止盈
- 算法描述
- 建仓后,时刻记录产品价格的最高点
- 计算最新价相对于前期最高点下跌幅幅度的百分比
- 如果下跌百分比大于某个阀值,止盈退出
- 结果:
- 策略收益 194.24%
- 策略年化收益:109.006%
- 策略波动率:54.675%
- 夏普比率:1.604
- 最大回撤: 37.042%
代码实战
dma_float_exit_alg.py (浮动止盈)
"""
双均线改进策略
- 浮动止盈
"""
import matplotlib.pyplot as plt
import numpy as np
import pandas as pd
import talib
from logbook import Logger
from catalyst import run_algorithm
from catalyst.api import record, symbol, order_target, order
# 需要先加载数据
# catalyst ingest-exchange -x binance -i btc_usdt -f daily
# catalyst ingest-exchange -x binance -i eth_usdt -f daily
# catalyst ingest-exchange -x binance -i ltc_usdt -f daily
# catalyst ingest-exchange -x binance -i eos_usdt -f daily
NAMESPACE = 'dma_float_exit_alg'
log = Logger(NAMESPACE)
SHORT_WIN = 5 # 短周期窗口
LONG_WIN = 20 # 长周期窗口
ATR_WIN_SIZE = 20 # ATR窗口
RISK_RATIO = 0.05 # 风险比例
INC_POS_RATE = 0.05 # 价格每上涨5%加仓
MAX_DROP_RATE = 0.3 # 从最高点回撤的百分比
def get_available_cash(context, use_compound_interest=False):
"""
获取当前可用资金
use_compound_interest: 是否使用复利
"""
if use_compound_interest:
# 使用复利
available_cash = context.portfolio.cash
else:
available_cash = min(context.portfolio.starting_cash, context.portfolio.cash)
return available_cash
def get_risk_indices(perf):
"""
计算风险指标,包括:
1. 策略收益
2. 策略年化收益
3. 策略波动率
4. 夏普比率
5. 最大回撤
"""
# 策略执行天数
n = len(perf)
# 1. 策略收益
total_returns = perf.iloc[-1]['algorithm_period_return']
# 2. 策略年化收益
total_ann_returns = (1 + total_returns) ** (250 / n) - 1
# 3. 策略波动率
algo_volatility = perf.iloc[-1]['algo_volatility']
# 4. 夏普比率
sharpe = perf.iloc[-1]['sharpe']
# 5. 最大回撤
max_drawdown = np.abs(perf.iloc[-1]['max_drawdown'])
return total_returns, total_ann_returns, algo_volatility, sharpe, max_drawdown
def initialize(context):
"""
初始化
"""
context.i = 0 # 经历过的交易周期
# 设置加密货币池
context.asset_pool = [symbol('btc_usdt'),
symbol('eth_usdt'),
symbol('ltc_usdt'),
symbol('eos_usdt')]
context.set_commission(maker=0.001, taker=0.001) # 设置手续费
context.set_slippage(slippage=0.001) # 设置滑点
# 初始化字典,用于记录上一次买入加密货币的价格
context.last_price = {asset: None for asset in context.asset_pool}
# 初始化字典,用于记录在当前交易周期中加密货币是否被交易过
# 这里限制每天只能交易一次
context.traded_today = {asset: False for asset in context.asset_pool}
# 初始化字典,用于记录上一次买入加密货币的日期
context.entry_dates = {asset: None for asset in context.asset_pool}
def handle_data(context, data):
"""
在每个交易周期上运行的策略
"""
context.i += 1 # 记录交易周期
for asset in context.traded_today:
# 每个交易周期重置交易flag
context.traded_today[asset] = False
if context.i < LONG_WIN + 1:
# 如果交易周期过短,无法计算均线,则跳过循环
log.warning('交易周期过短,无法计算指标')
return
# 获取当前周期内有效的加密货币
context.available_asset_pool = [asset
for asset in context.asset_pool
if asset.start_date <= data.current_dt]
context.up_cross_signaled = set() # 初始化金叉的交易对集合
context.down_cross_signaled = set() # 初始化死叉的交易对集合
for asset in context.available_asset_pool:
# 遍历每一个加密货币对
# 获得历史价格
hitory_data = data.history(asset,
'close',
bar_count=LONG_WIN + 1,
frequency='1D',
)
if len(hitory_data) >= LONG_WIN + 1:
# 保证新的货币有足够的时间计算均线
# 计算双均线
short_avgs = hitory_data.rolling(window=SHORT_WIN).mean()
long_avgs = hitory_data.rolling(window=LONG_WIN).mean()
# 双均线策略
# 短期均线上穿长期均线
if (short_avgs[-2] < long_avgs[-2]) and (short_avgs[-1] >= long_avgs[-1]):
# 形成金叉
context.up_cross_signaled.add(asset)
# 短期均线下穿长期均线
if (short_avgs[-2] > long_avgs[-2]) and (short_avgs[-1] <= long_avgs[-1]):
# 形成死叉
context.down_cross_signaled.add(asset)
# 卖出均线死叉信号的持仓交易对
for asset in context.portfolio.positions:
if context.traded_today[asset]:
# 当前交易周期中该产品被交易过,跳过循环
continue
if asset in context.down_cross_signaled:
order_target(asset, 0)
# 卖出货币对,重置购买价和买入日期
context.last_price[asset] = None
context.entry_dates[asset] = None
context.traded_today[asset] = True
# 检查有无符合浮动止盈的持仓品种
for asset in context.portfolio.positions:
if context.traded_today[asset]:
# 当前交易周期中该产品被交易过,跳过循环
continue
if context.portfolio.positions[asset].amount > 0:
last_entry_date = context.entry_dates[asset] # 上一次买入的日期
bar_count = (data.current_dt - last_entry_date).days + 1
# 计算截止到当前交易周期的最高价最大值(Highest High Value, HHV)
history_data = data.history(asset,
['high', 'close'],
bar_count=bar_count,
frequency='1D',
)
if len(history_data) > 1:
hhv = history_data['high'].max()
drop_rate = (hhv - history_data.iloc[-1]['close']) / hhv # 以当前交易周期的收盘价计算跌幅比例
if drop_rate > MAX_DROP_RATE:
log.info('止盈操作...')
order_target(asset, 0)
context.last_price[asset] = None
context.entry_dates[asset] = None
context.traded_today[asset] = True
# 买入均线金叉信号的持仓股
for asset in context.up_cross_signaled:
if context.traded_today[asset]:
# 当前交易周期中该产品被交易过,跳过循环
continue
if asset not in context.portfolio.positions:
history_data = data.history(asset,
['high', 'low', 'close'],
bar_count=ATR_WIN_SIZE + 1,
frequency='1D',
)
atr = talib.ATR(history_data['high'], history_data['low'], history_data['close'],
timeperiod=ATR_WIN_SIZE)
available_cash = get_available_cash(context)
if available_cash > 0:
# 如果有可用现金
amount_to_buy = available_cash * RISK_RATIO / atr[-1] # 计算购买的数量
close_price = data.current(asset, 'close')
if (amount_to_buy >= asset.min_trade_size) and (available_cash >= amount_to_buy * close_price):
# 购买的数量大于最小购买数量并且有足够的现金购买
order(asset, amount_to_buy)
# 买入货币对,记录购买日期和购买价
context.entry_dates[asset] = data.current_dt
context.last_price[asset] = close_price
context.traded_today[asset] = True
# 检查有无符合加仓条件的持仓品种
for asset in context.portfolio.positions:
if context.traded_today[asset]:
# 当前交易周期中该产品被交易过,跳过循环
continue
if context.portfolio.positions[asset].amount > 0:
close_price = data.current(asset, 'close')
last_price = context.last_price[asset]
if (close_price - last_price) / last_price >= INC_POS_RATE:
available_cash = get_available_cash(context)
if available_cash > 0:
# 如果有可用现金
# 按盈利比例均匀加仓
log.info('加仓操作...')
cash_for_each_asset = available_cash / len(context.available_asset_pool)
amount_to_buy = cash_for_each_asset / close_price
if amount_to_buy >= asset.min_trade_size:
# 购买的数量大于最小购买数量
order(asset, amount_to_buy)
# 买入货币对,记录购买日期和购买价
context.entry_dates[asset] = data.current_dt
context.last_price[asset] = close_price
context.traded_today[asset] = True
# 持仓比例
pos_level = context.portfolio.positions_value / context.portfolio.portfolio_value
# 记录每个交易周期的现金
record(cash=context.portfolio.cash, pos_level=pos_level)
# 输出信息
log.info('日期:{},资产:{:.2f},持仓比例:{:.6f}%,持仓产品:{}'.format(
data.current_dt, context.portfolio.portfolio_value, pos_level * 100,
', '.join([asset.asset_name for asset in context.portfolio.positions]))
)
def analyze(context, perf):
# 保存交易记录
perf.to_csv('./perf_results/dma_float_exit_performance.csv')
# 获取交易所的计价货币
exchange = list(context.exchanges.values())[0]
quote_currency = exchange.quote_currency.upper()
# 图1:可视化资产值
ax1 = plt.subplot(311)
perf['portfolio_value'].plot(ax=ax1)
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(312)
perf['pos_level'].plot(ax=ax2)
ax2.set_ylabel('Position Level')
start, end = 0, 1
ax2.yaxis.set_ticks(np.arange(start, end, (end - start) / 5))
# 图3:可视化现金数量
ax3 = plt.subplot(313, sharex=ax1)
perf['cash'].plot(ax=ax3)
ax3.set_ylabel('Cash\n({})'.format(quote_currency))
start, end = ax3.get_ylim()
ax3.yaxis.set_ticks(np.arange(0, end, end / 5))
plt.tight_layout()
plt.show()
# 评价策略
total_returns, total_ann_returns, algo_volatility, sharpe, max_drawdown = get_risk_indices(perf)
log.info('策略收益: {:.3f}%, 策略年化收益: {:.3f}%, 策略波动率: {:.3f}%, 夏普比率: {:.3f}, 最大回撤: {:.3f}%'.format(
total_returns * 100, total_ann_returns * 100, algo_volatility * 100, sharpe, max_drawdown * 100
))
if __name__ == '__main__':
run_algorithm(
capital_base=100000,
data_frequency='daily',
initialize=initialize,
handle_data=handle_data,
analyze=analyze,
exchange_name='binance',
algo_namespace=NAMESPACE,
quote_currency='usdt',
start=pd.to_datetime('2019-02-01', utc=True),
end=pd.to_datetime('2019-12-22', utc=True)
)

输出打印:
[2019-12-24 15:41:35.332583] INFO: dma_float_exit_alg: 日期:2019-03-02 23:59:00+00:00,资产:100000.00,持仓比例:0.000000%,持仓产品:
[2019-12-24 15:41:35.372880] INFO: dma_float_exit_alg: 日期:2019-03-03 23:59:00+00:00,资产:99871.71,持仓比例:64.194880%,持仓产品:LTC / USDT
[2019-12-24 15:41:35.410091] INFO: dma_float_exit_alg: 日期:2019-03-04 23:59:00+00:00,资产:97085.37,持仓比例:63.167278%,持仓产品:LTC / USDT
[2019-12-24 15:41:35.445014] INFO: dma_float_exit_alg: 加仓操作...
[2019-12-24 15:41:35.445281] INFO: dma_float_exit_alg: 日期:2019-03-05 23:59:00+00:00,资产:106114.18,持仓比例:66.301217%,持仓产品:LTC / USDT
[2019-12-24 15:41:35.480165] INFO: dma_float_exit_alg: 加仓操作...
...
[2019-12-24 15:41:51.127684] INFO: dma_float_exit_alg: 日期:2019-12-09 23:59:00+00:00,资产:160908.44,持仓比例:0.000000%,持仓产品:
[2019-12-24 15:41:51.256723] INFO: dma_float_exit_alg: 日期:2019-12-10 23:59:00+00:00,资产:160592.28,持仓比例:98.386190%,持仓产品:BTC / USDT, EOS / USDT
[2019-12-24 15:41:51.334755] INFO: dma_float_exit_alg: 日期:2019-12-11 23:59:00+00:00,资产:159748.71,持仓比例:98.377668%,持仓产品:BTC / USDT, EOS / USDT
[2019-12-24 15:41:51.378688] INFO: dma_float_exit_alg: 日期:2019-12-12 23:59:00+00:00,资产:159770.24,持仓比例:98.377887%,持仓产品:BTC / USDT, EOS / USDT
[2019-12-24 15:41:51.418849] INFO: dma_float_exit_alg: 日期:2019-12-13 23:59:00+00:00,资产:161364.86,持仓比例:0.000000%,持仓产品:
[2019-12-24 15:41:51.453142] INFO: dma_float_exit_alg: 日期:2019-12-14 23:59:00+00:00,资产:161364.86,持仓比例:0.000000%,持仓产品:
[2019-12-24 15:41:51.486555] INFO: dma_float_exit_alg: 日期:2019-12-15 23:59:00+00:00,资产:161364.86,持仓比例:0.000000%,持仓产品:
[2019-12-24 15:41:51.524502] INFO: dma_float_exit_alg: 日期:2019-12-16 23:59:00+00:00,资产:161364.86,持仓比例:0.000000%,持仓产品:
风险指标:
[2019-12-24 15:44:30.216254] INFO: dma_float_exit_alg: 策略收益: 61.365%, 策略年化收益: 44.495%, 策略波动率: 50.878%, 夏普比率: 0.981, 最大回撤: 39.403%
我们可以看到,这个策略收益是非常可观的,最大回撤也减少了很多。
为者常成,行者常至
自由转载-非商用-非衍生-保持署名(创意共享3.0许可证)