python交易_实战:基于技术分析的Python算法交易

译者 | Tianyu

本文是用 Python 做交易策略回测系列文章的第四篇。上个部分介绍了以下几个方面内容:

介绍了 zipline 回测框架,并展示了如何回测基本的策略导入自定义的数据并使用 zipline评估交易策略的表现这篇文章的目的是介绍如何基于技术分析(TA, Technical Analysis)来创建交易策略。在此引用维基百科的解释,技术分析是指“基于对市场的历史数据、成交价格、交易量的研究,来预测价格走势的一套方法”。

在本文中,我会介绍如何使用流行的 Python 库 TA-Lib 以及 zipline 回测框架来计算 TA 指标。我会创建 5 种策略,然后研究哪种策略在投资期限内表现最好。

安装

我用到的库有以下几个:

pyfolio 0.9.2

numpy 1.14.6

matplotlib 3.0.0

pandas 0.22.0

json 2.0.9

empyrical 0.5.0

zipline 1.3.0

辅助函数

在构造策略之前,我要先定义几个辅助函数(此处我只介绍其中一个,因为它是最重要的一个)。

这个函数用来设置回测的起始时间,因为我希望所有策略开始实施的时间保持一致,设置为2016年的第一天。不过,有些基于技术指标的策略需要一定数量的历史数据,也就是所谓的 warm-up 阶段。请一定记住一点,没有任何交易决策会发生在回测期的起始时间之前。

def get_start_date(ticker, start_date, days_prior):

start_date_dt = datetime.strptime(start_date, '%Y-%m-%d')

prior_to_start_date_dt = start_date_dt - relativedelta(days=2 * days_prior)

prior_to_start_date = prior_to_start_date_dt.strftime('%Y-%m-%d')

yahoo_financials = YahooFinancials(ticker)

df = yahoo_financials.get_historical_price_data(

prior_to_start_date, start_date, 'daily')

df = pd.DataFrame(df[ticker]['prices'])['formatted_date']

if df.iloc[-1] == start_date:

days_prior += 1

new_start_date = df.iloc[-days_prior]

return new_start_date

策略

本篇文章中,我们要解决的问题如下:

投资者有 10000 元的本金投资时限为 2016-2017投资者仅投资 Tesla 的股票假设不存在交易成本,即交易佣金为零不存在做空行为(投资者只能出售他们拥有的股票)当投资者购买股票时,他们会花掉全部本金之所以选择这段时期,是因为2018年中后的 Quandl 数据集还没有更新,我们希望代码可以尽可能简化。关于如何将数据载入 zipline 的更多细节,请参考到我之前的文章。

买入和持有的策略

我们首先来看最基本的策略 —— 买入和持有。具体的思路是,我们买入一定的资产,在整个投资期间不进行任何操作。因此在投资第一天,我们使用全部本金尽可能多地购买 Tesla 的股票,接下来什么事情都不做。

这种简单的策略可以作为其他高级策略的基准,若某种复杂的策略相比于基准策略反而损失了更多的钱,那么说明这种策略毫无用处。

%%zipline --start 2016-1-1 --end 2017-12-31 --capital-base 10000.0 -o buy_and_hold.pkl

# imports

from zipline.api import order_percent, symbol, record

from zipline.finance import commission

# parameters

SELECTED_STOCK = 'TSLA'

def initialize(context):

context.asset = symbol(SELECTED_STOCK)

context.has_ordered = False

context.set_commission(commission.PerShare(cost=0.0, min_trade_cost=0))

def handle_data(context, data):

# trading logic

if not context.has_ordered:

order_percent(context.asset, 1)

context.has_ordered = True

record(price=data.current(context.asset, 'price'))

接下来,我们载入有关该策略表现的 DataFrame:

buy_and_hold_results = pd.read_pickle('buy_and_hold.pkl')

这里可能会出现 ending_cash 为负的情况,原因是我们想要买入的股份是当天收盘时计算的,于是使用的是收盘价格。然而,这笔交易是次日执行的,价格可能会发生大幅变化。在 zipline 中,交易不会因为金额不足而被拒,但我们可以通过负的余额将其终止。我们可以想些办法避免这种情况的发生,例如手动计算第二天要买入的股份,并考虑股价上涨等因素,以防止这种情况发生。

我们使用一个辅助函数,将该策略的细节进行可视化:投资组合的变化,交易价格序列,以及每天的收益情况。

472309f790529822f6cd63b1580ec7ce0b46d414.jpeg?token=0efe096e93d111d1777f4ae82f43e210&s=082055300D0A4C4D4ED475C30100A0B3

我们还使用了另一个辅助函数来观察策略的表现,该函数将用于最后一部分:

buy_and_hold_results = pd.read_pickle('buy_and_hold.pkl')

为了简洁起见,我们不会展示每种策略的全部步骤,因为它们的执行方式都是一样的。

简单的移动平均策略

我们采用的第二种策略基于简单的移动平均数方法(SMA, Simple Moving Average)。该策略的逻辑可以归纳为以下几步:

当20天的 SMA 价格上升时,买入股份当20天的 SMA 价格下降时,卖掉全部股份用前19天和当天的数据计算移动平均数,次日执行交易决策这是我们第一次调用预设辅助函数的地方,计算起始日期,以使投资者能在2016年的第一个交易日制定交易决策。

get_start_date('TSLA', '2016-01-04', 19)# '2015-12-04'

在下面的策略中,我们使用修改后的日期作为起始日期:

%%zipline --start 2015-12-4 --end 2017-12-31 --capital-base 10000.0 -o simple_moving_average.pkl

# imports

from zipline.api import order_percent, record, symbol, order_target

from zipline.finance import commission

# parameters

MA_PERIODS = 20

SELECTED_STOCK = 'TSLA'

def initialize(context):

context.time = 0

context.asset = symbol(SELECTED_STOCK)

context.set_commission(commission.PerShare(cost=0.0, min_trade_cost=0))

context.has_position = False

def handle_data(context, data):

context.time += 1

record(time=context.time)

if context.time < MA_PERIODS:

return

price_history = data.history(context.asset, fields="price", bar_count=MA_PERIODS, frequency="1d")

ma = price_history.mean

# cross up

if (price_history[-2] < ma) & (price_history[-1] > ma) & (not context.has_position):

order_percent(context.asset, 1.0)

context.has_position = True

# cross down

elif (price_history[-2] > ma) & (price_history[-1] < ma) & (context.has_position):

order_target(context.asset, 0)

context.has_position = False

record(price=data.current(context.asset, 'price'),

moving_average=ma)

注意:data.current(context.asset, ‘price’) 等同于 price_history[-1].

下图展示了该策略:

d52a2834349b033b4a4133379b0a8ad6d739bd58.jpeg?token=eee11340e2ee609c2bd9e1a8b4fde629&s=08215D30190A4C494E5C34DB0100F0B3

下图展示了20天的移动平均价格序列。我们还对每一次交易做了标注,即在记号之后的第一个交易日执行此笔交易。

03087bf40ad162d9aebf2a1a811b15e98b13cda9.jpeg?token=267a5b6c09abaaf5e9070eb0d5ee1a56&s=08615F30111A44650AF9B4CA0000E0B3

移动平均交叉

移动平均交叉策略(Moving Average Crossover)可以看作是上一种策略的拓展版,用两个不同规格的移动窗口来代替单个的窗口。100天的移动平均数序列中,要隔很久才会出现价格的突变,而20天的移动平均数序列发生突变的速度要快很多。

该策略的逻辑如下:

当较快的移动平均值穿越较慢的移动平均值时,我们买入股份当较慢的移动平均值穿越较快的移动平均值时,我们卖出股份一定要记住一点,在这种策略中,许多不同长度窗口的组合构成了速度不同的移动平均数。

对于该策略,我们需要另外载入100天的数据,以便于准备 warm-up 阶段。

%%zipline --start 2015-8-11 --end 2017-12-31 --capital-base 10000.0 -o moving_average_crossover.pkl

# imports

from zipline.api import order_percent, record, symbol, order_target

from zipline.finance import commission

# parameters

SELECTED_STOCK = 'TSLA'

SLOW_MA_PERIODS = 100

FAST_MA_PERIODS = 20

def initialize(context):

context.time = 0

context.asset = symbol(SELECTED_STOCK)

context.set_commission(commission.PerShare(cost=0.0, min_trade_cost=0))

context.has_position = False

def handle_data(context, data):

context.time += 1

if context.time < SLOW_MA_PERIODS:

return

fast_ma = data.history(context.asset, 'price', bar_count=FAST_MA_PERIODS, frequency="1d").mean

slow_ma = data.history(context.asset, 'price', bar_count=SLOW_MA_PERIODS, frequency="1d").mean

# Trading logic

if (fast_ma > slow_ma) & (not context.has_position):

order_percent(context.asset, 1.0)

context.has_position = True

elif (fast_ma < slow_ma) & (context.has_position):

order_target(context.asset, 0)

context.has_position = False

record(price=data.current(context.asset, 'price'),

fast_ma=fast_ma,

slow_ma=slow_ma)

a5c27d1ed21b0ef4061fc15d5200eddf80cb3e03.jpeg?token=8e7a1fec60c910bfa45c95da69188bb6&s=082B5D32195A444D5454C4CA0000E0B3

接下来,我们绘制了两个移动平均价格序列。我们可以发现,该策略产生的交易行为要比 SMA 策略少得多。

c2fdfc039245d688b00da7a42a06c11bd31b24e8.jpeg?token=9e23e694a397728fefea0a1911579c2a&s=08615730191A444506E8B0CA0000E0B3

移动平均线收敛差异

MACD 的全称为 Moving Average Convergence/Divergence,即移动平均线收敛差异指标,是一种常用于股价技术分析中的指标。

MACD 由三个时间序列构成:

MACD 序列:快速(短期)和慢速(长期)的两个指数移动平均值的差值信号序列:MACD 序列的 EMA(指数移动平均值)差异序列:MACD 序列与信号序列之间的差值MACD 的参数包括计算三个移动平均数的天数,即 MACD(a, b, c),参数 a 表示快速 EMA,b 表示慢速 EMA,c 表示 MACD 序列的 EMA。最常见的参数配置为 MACD(12, 26, 9),也是本文所采用的配置。若每周有6个工作日,这三个参数分别对应2个星期、1个月、1.5个星期。

必须记住一点,由于 MACD 是基于移动平均方法进行计算的,因此它是一种滞后指标。这就解释了为什么 MACD 在股市上的作用很小,它无法得出准确的价格趋势。

该策略的基本思想如下:

当 MACD 线穿越信号线向上时,买入股份当 MACD 线穿越信号线向下时,卖出股份和之前一样,为了准备 warm-up,我们要保证有34个历史数据值来计算 MACD:

%%zipline --start 2015-11-12 --end 2017-12-31 --capital-base 10000.0 -o macd.pkl

# imports ----

from zipline.api import order_target, record, symbol, set_commission, order_percent

import matplotlib.pyplot as plt

import talib as ta

from zipline.finance import commission

# parameters ----

SELECTED_STOCK = 'TSLA'

#initialize the strategy

def initialize(context):

context.time = 0

context.asset = symbol(SELECTED_STOCK)

context.set_commission(commission.PerShare(cost=0.0, min_trade_cost=0))

context.has_position = False

def handle_data(context, data):

context.time += 1

if context.time < 34:

return

price_history = data.history(context.asset, fields="price", bar_count=34, frequency="1d")

macd, macdsignal, macdhist = ta.MACD(price_history, 12, 26, 9)

if (macdsignal[-1] < macd[-1]) and (not context.has_position):

order_percent(context.asset, 1.0)

context.has_position = True

if (macdsignal[-1] > macd[-1]) and (context.has_position):

order_target(context.asset, 0)

context.has_position = False

record(macd = macd[-1], macdsignal = macdsignal[-1], macdhist = macdhist[-1], price=price_history[-1])

54fbb2fb43166d22b994d7b6c9e7b5f29152d278.jpeg?token=9f44e68eaf4ccb705caa7c0a908b37b1&s=08215D320D0A684F4E5C30CB010030B3

接下来,我们绘制了 MACD 线和信号线,交叉点代表买入/卖出的信号。另外,你也可以试着用直方图的形式来展现 MACD 差异。

8326cffc1e178a8295cdcdd265c7cf88a877e81d.jpeg?token=baaf187c45a6216559e80f55b02bc19f&s=0271573009524C454C40B0CA000070B3

相对强弱指标(RSI)

RSI 的全称为 Relative Strength Index,即相对强弱指标,也是一种用于创建交易策略的技术指标。RSI 被看作是一种动量振荡器,它可以估测价格变化的速度和幅度。

RSI 指标评估了股价的向上力量与向下力量的比率。若向上的力量较大,则计算出来的指标上升;若向下的力量较大,则指标下降。

RSI 的结果为0到100之间的数字,一般按14天进行计算。为生成交易信号,通常要指定 RSI 的下限为30,上限为70。也就是说,30以下在超卖区,70以上为超买区。

有时候,也可能会设定一个比较居中的值,比如在涉及到做空的策略中。我们也可以选择更极端的阈值,如20和80。不过,这要求具备专业知识,或者在回测时尝试。

这种策略的思想如下:

当 RSI 低于下限(30)时,买入股份当 RSI 高于上限(70)时,卖出股份%%zipline --start 2015-12-10 --end 2017-12-31 --capital-base 10000.0 -o rsi.pkl

# imports ----

from zipline.api import order_target, record, symbol, set_commission, order_percent

import matplotlib.pyplot as plt

import talib as ta

from zipline.finance import commission

# parameters ----

SELECTED_STOCK = 'TSLA'

UPPER = 70

LOWER = 30

RSI_PERIOD = 14

#initialize the strategy

def initialize(context):

context.time = 0

context.asset = symbol(SELECTED_STOCK)

context.set_commission(commission.PerShare(cost=0.0, min_trade_cost=0))

context.has_position = False

def handle_data(context, data):

context.time += 1

if context.time < RSI_PERIOD + 1:

return

price_history = data.history(context.asset, fields="price", bar_count=RSI_PERIOD+1, frequency="1d")

rsi = ta.RSI(price_history, timeperiod=RSI_PERIOD)

if rsi[-1] < LOWER and not context.has_position:

order_percent(context.asset, 1.0)

context.has_position = True

if rsi[-1] > UPPER and context.has_position:

order_target(context.asset, 0)

context.has_position = False

record(rsi=rsi[-1], price=price_history[-1], time=context.time)

279759ee3d6d55fb63c55bd0fde6f34f21a4dd0b.jpeg?token=1765b3615821ec1fefc449f40a4a5bfb&s=082B5D321B1A44494E7C24DB0000E0B3

下图绘制了 RSI 指标和上、下限:

060828381f30e92420489e3fdcccd2031c95f71e.jpeg?token=186e6d5ddfeb2458a89ffc706c0a17b9&s=00615D302B864C495051F9C2020030B3

效果评估

最后一步,把所有的评估指标放入一个 DataFrame 中,然后观察其结果。我们会发现在回测时,基于简单移动平均方法的策略在收益方面表现最好,其夏普指数也最高,即在特定风险下,可获得的收益最高。基于 MACD 的策略排在第二位。只有这两种策略的表现超过了我们所设置的基准。

perf_df = pd.DataFrame({'Buy and Hold': buy_and_hold_perf,

'Simple Moving Average': sma_perf,

'Moving Average Crossover': mac_perf,

'MACD': macd_perf,

'RSI': rsi_perf})

perf_df.transpose

3812b31bb051f819776bf89f4a70f6e82c73e7e0.jpeg?token=10c7f9b7a28898d4651011fac8166745&s=0AAA7C22C5344C231851F8CA0100E0B1

结论

本篇文章介绍了如何利用 zipline 和 talib 进行交易策略的回测,使用的技术指标包括移动平均数、MACD、RSI 等等。但这只是一些基础,还有相当多更加复杂的策略。

另外,我们必须记住一点,一些在过去表现很好的策略不一定也适用于未来。

(*本文为AI科技大本营编译文章,转载请微信联系 1092722531)

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值