資料導入階段,我們使用 os.environ
設置環境變數,分別設定:
於本次案例我們抓取台積電資料,時間區間設定為 2005-07-02 到 2023-07-02 之間。
import os
import pandas as pd
import numpy as np
import tejapi
import matplotlib.pyplot as plt
os.environ['TEJAPI_BASE'] = 'https://api.tej.com.tw'
os.environ['TEJAPI_KEY'] = 'your key'
os.environ['mdate'] = '20050702 20230702'
os.environ['ticker'] = '2330'
!zipline ingest -b tquant
Merging daily equity files:
[2023-07-26 06:00:14.412552] INFO: zipline.data.bundles.core: Ingesting tquant.
from zipline.api import (set_slippage,
set_commission,
set_benchmark,
attach_pipeline,
symbol,
pipeline_output,
record,
order,
order_target
)
from zipline.pipeline.filters import StaticSids
from zipline.finance import slippage, commission
from zipline import run_algorithm
from zipline.pipeline import CustomFactor, Pipeline
from zipline.pipeline.data import EquityPricing
from zipline.pipeline.factors import ExponentialWeightedMovingAverage
Pipeline()
提供使用者快速處理多檔標的的量化指標與價量資料的功能,於本次案例我們用以處理:
NdaysMaxHigh
)NdaysMinLow
)def make_pipeline():
ema = ExponentialWeightedMovingAverage(inputs = [EquityPricing.close],window_length = 7,decay_rate = 1/7)
high = NdaysMaxHigh(inputs = [EquityPricing.close], window_length = 8) # window_length 設定為 8,因為 factor 會包含當日價格。
low = NdaysMinLow(inputs = [EquityPricing.close], window_length = 8)
close = EquityPricing.close.latest
return Pipeline(
columns = {
'ema':ema,
'highesthigh':high,
'lowestlow':low,
'latest':close
}
)
class NdaysMaxHigh(CustomFactor):
def compute(self, today, assets, out, data):
out[:] = np.nanmax(data[:-2], axis=0)
class NdaysMinLow(CustomFactor):
def compute(self, today, assets, out, data):
out[:] = np.nanmin(data[:-2], axis=0)
inintialize
函式用於定義交易開始前的每日交易環境,與此例中我們設置:
def initialize(context):
set_slippage(slippage.VolumeShareSlippage())
set_commission(commission.PerShare(cost=0.00285))
set_benchmark(symbol('2330'))
attach_pipeline(make_pipeline(), 'mystrategy')
handle_data
函式用於處理每天的交易策略或行動,其中:
def handle_data(context, data):
pipe = pipeline_output('mystrategy')
for i in pipe.index:
ema = pipe.loc[i, 'ema']
highesthigh = pipe.loc[i, 'highesthigh']
lowestlow = pipe.loc[i, 'lowestlow']
close = pipe.loc[i, 'latest']
bias = close - ema
residual_position = context.portfolio.positions[i].amount # 當日該資產的股數
condition1 = (close > highesthigh) and (bias > 0) and (residual_position > 0) # 賣出訊號
condition2 = (close < lowestlow) and (bias < 0) # 買入訊號
record( # 用以紀錄以下資訊至最終產出的 result 表格中
con1 = condition1,
con2 = condition2,
price = close,
ema = ema,
bias = bias,
highesthigh = highesthigh,
lowestlow = lowestlow
)
if condition1:
order_target(i, 0)
elif condition2:
order(i, 10)
else:
pass
多半用於繪製績效圖表,於本案例使用 matplotlib 將視覺化買賣點與投組價值變化。
import matplotlib.pyplot as plt
def analyze(context, perf):
fig = plt.figure()
ax1 = fig.add_subplot(211)
perf.portfolio_value.plot(ax=ax1)
ax1.set_ylabel("Portfolio value (NTD)")
ax2 = fig.add_subplot(212)
ax2.set_ylabel("Price (NTD)")
perf.price.plot(ax=ax2)
ax2.plot( # 繪製買入訊號
perf.index[perf.con2],
perf.loc[perf.con2, 'price'],
'^',
markersize=5,
color='red'
)
ax2.plot( # 繪製賣出訊號
perf.index[perf.con1],
perf.loc[perf.con1, 'price'],
'v',
markersize=5,
color='green'
)
plt.legend(loc=0)
plt.gcf().set_size_inches(18,8)
plt.show()
使用 run_algorithm
執行上述所編撰的交易策略,設置交易期間為 2015-01-05 到 2022-07-02,所使用資料集為 tquant,初始資金為 10,000 元。其中輸出的 results 就是每日績效與交易的明細表。
results = run_algorithm(start = pd.Timestamp('20150106', tz='UTC'),
end = pd.Timestamp('20221125', tz='UTC'),
initialize=initialize,
bundle='tquant',
analyze=analyze,
capital_base=1e4,
handle_data = handle_data
)
C:\Users\Admin\anaconda3\envs\zipline-tej\lib\site-packages\empyrical\stats.py:706: RuntimeWarning: invalid value encountered in true_divide np.divide( C:\Users\Admin\anaconda3\envs\zipline-tej\lib\site-packages\empyrical\stats.py:797: RuntimeWarning: invalid value encountered in true_divide np.divide(average_annual_return, annualized_downside_risk, out=out)
results # 績效與交易明細
period_open | period_close | longs_count | shorts_count | long_value | short_value | benchmark_return | long_exposure | treasury_return | pnl | ... | treasury_period_return | algorithm_period_return | alpha | beta | sharpe | sortino | max_drawdown | max_leverage | trading_days | period_label | |
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
2015-01-06 13:30:00+08:00 | 2015-01-06 09:01:00+08:00 | 2015-01-06 13:30:00+08:00 | 0 | 0 | 0.0 | 0.0 | -0.043009 | 0.0 | 0.0 | 0.0000 | ... | 0.0 | 0.000000 | NaN | NaN | NaN | NaN | 0.000000 | 0.000000 | 1 | 2015-01 |
2015-01-07 13:30:00+08:00 | 2015-01-07 09:01:00+08:00 | 2015-01-07 13:30:00+08:00 | 0 | 0 | 0.0 | 0.0 | 0.003741 | 0.0 | 0.0 | 0.0000 | ... | 0.0 | 0.000000 | 0.000000 | 0.000000 | NaN | NaN | 0.000000 | 0.000000 | 2 | 2015-01 |
2015-01-08 13:30:00+08:00 | 2015-01-08 09:01:00+08:00 | 2015-01-08 13:30:00+08:00 | 1 | 0 | 1380.0 | 0.0 | 0.029856 | 1380.0 | 0.0 | -0.0285 | ... | 0.0 | -0.000003 | -0.000267 | -0.000034 | -9.165151 | -9.165151 | -0.000003 | 0.138000 | 3 | 2015-01 |
2015-01-09 13:30:00+08:00 | 2015-01-09 09:01:00+08:00 | 2015-01-09 13:30:00+08:00 | 1 | 0 | 2680.0 | 0.0 | -0.028990 | 2680.0 | 0.0 | -40.0285 | ... | 0.0 | -0.004006 | -0.176612 | 0.024018 | -7.944789 | -7.942903 | -0.004006 | 0.269078 | 4 | 2015-01 |
2015-01-12 13:30:00+08:00 | 2015-01-12 09:01:00+08:00 | 2015-01-12 13:30:00+08:00 | 1 | 0 | 2640.0 | 0.0 | -0.014918 | 2640.0 | 0.0 | -40.0000 | ... | 0.0 | -0.008006 | -0.280942 | 0.027797 | -11.599942 | -10.043474 | -0.008006 | 0.269078 | 5 | 2015-01 |
... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... |
2022-11-21 13:30:00+08:00 | 2022-11-21 09:01:00+08:00 | 2022-11-21 13:30:00+08:00 | 0 | 0 | 0.0 | 0.0 | -0.010267 | 0.0 | 0.0 | 0.0000 | ... | 0.0 | 0.420579 | -0.047226 | 0.608176 | 0.299338 | 0.475617 | -0.487303 | 4.620656 | 1928 | 2022-11 |
2022-11-22 13:30:00+08:00 | 2022-11-22 09:01:00+08:00 | 2022-11-22 13:30:00+08:00 | 0 | 0 | 0.0 | 0.0 | 0.018672 | 0.0 | 0.0 | 0.0000 | ... | 0.0 | 0.420579 | -0.048526 | 0.607770 | 0.299260 | 0.475494 | -0.487303 | 4.620656 | 1929 | 2022-11 |
2022-11-23 13:30:00+08:00 | 2022-11-23 09:01:00+08:00 | 2022-11-23 13:30:00+08:00 | 0 | 0 | 0.0 | 0.0 | 0.002037 | 0.0 | 0.0 | 0.0000 | ... | 0.0 | 0.420579 | -0.048655 | 0.607767 | 0.299183 | 0.475370 | -0.487303 | 4.620656 | 1930 | 2022-11 |
2022-11-24 13:30:00+08:00 | 2022-11-24 09:01:00+08:00 | 2022-11-24 13:30:00+08:00 | 0 | 0 | 0.0 | 0.0 | 0.008130 | 0.0 | 0.0 | 0.0000 | ... | 0.0 | 0.420579 | -0.049228 | 0.607697 | 0.299105 | 0.475247 | -0.487303 | 4.620656 | 1931 | 2022-11 |
2022-11-25 13:30:00+08:00 | 2022-11-25 09:01:00+08:00 | 2022-11-25 13:30:00+08:00 | 0 | 0 | 0.0 | 0.0 | 0.004032 | 0.0 | 0.0 | 0.0000 | ... | 0.0 | 0.420579 | -0.049504 | 0.607683 | 0.299028 | 0.475124 | -0.487303 | 4.620656 | 1932 | 2022-11 |
1932 rows × 46 columns
import pyfolio as pf
returns, positions, transactions = pf.utils.extract_rets_pos_txn_from_zipline(results)
returns
2015-01-06 00:00:00+08:00 0.000000 2015-01-07 00:00:00+08:00 0.000000 2015-01-08 00:00:00+08:00 -0.000003 2015-01-09 00:00:00+08:00 -0.004003 2015-01-12 00:00:00+08:00 -0.004016 ... 2022-11-21 00:00:00+08:00 0.000000 2022-11-22 00:00:00+08:00 0.000000 2022-11-23 00:00:00+08:00 0.000000 2022-11-24 00:00:00+08:00 0.000000 2022-11-25 00:00:00+08:00 0.000000 Name: returns, Length: 1932, dtype: float64
positions
sid | Equity(0 [2330]) | cash |
---|---|---|
index | ||
2015-01-08 00:00:00+08:00 | 1380.0 | 8619.971500 |
2015-01-09 00:00:00+08:00 | 2680.0 | 7279.943000 |
2015-01-12 00:00:00+08:00 | 2640.0 | 7279.943000 |
2015-01-13 00:00:00+08:00 | 2650.0 | 7279.943000 |
2015-01-14 00:00:00+08:00 | 3900.0 | 5979.914500 |
... | ... | ... |
2022-10-27 00:00:00+08:00 | 23130.0 | -8834.038001 |
2022-10-28 00:00:00+08:00 | 22770.0 | -8834.038001 |
2022-10-31 00:00:00+08:00 | 23400.0 | -8834.038001 |
2022-11-01 00:00:00+08:00 | 23490.0 | -8834.038001 |
2022-11-02 00:00:00+08:00 | 23700.0 | -8834.038001 |
814 rows × 2 columns
transactions
sid | symbol | price | order_id | amount | commission | dt | txn_dollars | |
---|---|---|---|---|---|---|---|---|
2015-01-08 05:30:00+00:00 | Equity(0 [2330]) | Equity(0 [2330]) | 138.0 | 5b47c89e254346ed9f127077896d873c | 10 | None | 2015-01-08 13:30:00+08:00 | -1380.0 |
2015-01-09 05:30:00+00:00 | Equity(0 [2330]) | Equity(0 [2330]) | 134.0 | a373704586884a3ca3467097c90cbc6a | 10 | None | 2015-01-09 13:30:00+08:00 | -1340.0 |
2015-01-14 05:30:00+00:00 | Equity(0 [2330]) | Equity(0 [2330]) | 130.0 | 993d2e38cbe746c6a421a938d4a0309b | 10 | None | 2015-01-14 13:30:00+08:00 | -1300.0 |
2015-01-16 05:30:00+00:00 | Equity(0 [2330]) | Equity(0 [2330]) | 137.0 | 4e21811e2d704c62adb931b97401cbcd | 10 | None | 2015-01-16 13:30:00+08:00 | -1370.0 |
2015-01-21 05:30:00+00:00 | Equity(0 [2330]) | Equity(0 [2330]) | 141.0 | 6d70cf5df23e46108915ec8cfa55278d | -40 | None | 2015-01-21 13:30:00+08:00 | 5640.0 |
... | ... | ... | ... | ... | ... | ... | ... | ... |
2022-10-17 05:30:00+00:00 | Equity(0 [2330]) | Equity(0 [2330]) | 397.0 | b86bed51d0ab4a57b12fc4f188c11073 | 10 | None | 2022-10-17 13:30:00+08:00 | -3970.0 |
2022-10-25 05:30:00+00:00 | Equity(0 [2330]) | Equity(0 [2330]) | 371.0 | b181b5b033ce483fae906dabbbc33ac7 | 10 | None | 2022-10-25 13:30:00+08:00 | -3710.0 |
2022-10-26 05:30:00+00:00 | Equity(0 [2330]) | Equity(0 [2330]) | 376.0 | 04000f2ffdda40c386bef4365a8adce7 | 10 | None | 2022-10-26 13:30:00+08:00 | -3760.0 |
2022-10-27 05:30:00+00:00 | Equity(0 [2330]) | Equity(0 [2330]) | 385.5 | 54c6e4bd7a6c42b7bed5834f122ce3f7 | 10 | None | 2022-10-27 13:30:00+08:00 | -3855.0 |
2022-11-03 05:30:00+00:00 | Equity(0 [2330]) | Equity(0 [2330]) | 384.0 | 75b8bffb7af4409b98538a74dc7e0217 | -60 | None | 2022-11-03 13:30:00+08:00 | 23040.0 |
417 rows × 8 columns
benchmark_rets = results['benchmark_return']
from pyfolio.utils import extract_rets_pos_txn_from_zipline
returns, positions, transactions = extract_rets_pos_txn_from_zipline(results)
returns.head()
2015-01-06 00:00:00+08:00 0.000000 2015-01-07 00:00:00+08:00 0.000000 2015-01-08 00:00:00+08:00 -0.000003 2015-01-09 00:00:00+08:00 -0.004003 2015-01-12 00:00:00+08:00 -0.004016 Name: returns, dtype: float64
positions.head()
sid | Equity(0 [2330]) | cash |
---|---|---|
index | ||
2015-01-08 00:00:00+08:00 | 1380.0 | 8619.9715 |
2015-01-09 00:00:00+08:00 | 2680.0 | 7279.9430 |
2015-01-12 00:00:00+08:00 | 2640.0 | 7279.9430 |
2015-01-13 00:00:00+08:00 | 2650.0 | 7279.9430 |
2015-01-14 00:00:00+08:00 | 3900.0 | 5979.9145 |
transactions.head()
sid | symbol | price | order_id | amount | commission | dt | txn_dollars | |
---|---|---|---|---|---|---|---|---|
2015-01-08 05:30:00+00:00 | Equity(0 [2330]) | Equity(0 [2330]) | 138.0 | 5b47c89e254346ed9f127077896d873c | 10 | None | 2015-01-08 13:30:00+08:00 | -1380.0 |
2015-01-09 05:30:00+00:00 | Equity(0 [2330]) | Equity(0 [2330]) | 134.0 | a373704586884a3ca3467097c90cbc6a | 10 | None | 2015-01-09 13:30:00+08:00 | -1340.0 |
2015-01-14 05:30:00+00:00 | Equity(0 [2330]) | Equity(0 [2330]) | 130.0 | 993d2e38cbe746c6a421a938d4a0309b | 10 | None | 2015-01-14 13:30:00+08:00 | -1300.0 |
2015-01-16 05:30:00+00:00 | Equity(0 [2330]) | Equity(0 [2330]) | 137.0 | 4e21811e2d704c62adb931b97401cbcd | 10 | None | 2015-01-16 13:30:00+08:00 | -1370.0 |
2015-01-21 05:30:00+00:00 | Equity(0 [2330]) | Equity(0 [2330]) | 141.0 | 6d70cf5df23e46108915ec8cfa55278d | -40 | None | 2015-01-21 13:30:00+08:00 | 5640.0 |
使用 show_perf_stats()
製作績效表,可以快速計算投資常用績效與風險指標。
import pyfolio as pf
pf.plotting.show_perf_stats(
returns,
benchmark_rets,
positions=positions,
transactions=transactions)
Start date | 2015-01-06 | |
---|---|---|
End date | 2022-11-25 | |
Total months | 92 | |
Backtest | ||
Annual return | 4.686% | |
Cumulative returns | 42.058% | |
Annual volatility | 29.778% | |
Sharpe ratio | 0.30 | |
Calmar ratio | 0.10 | |
Stability | 0.71 | |
Max drawdown | -48.73% | |
Omega ratio | 1.13 | |
Sortino ratio | 0.48 | |
Skew | 2.52 | |
Kurtosis | 45.68 | |
Tail ratio | 0.98 | |
Daily value at risk | -3.716% | |
Gross leverage | 0.97 | |
Daily turnover | 0.0% | |
Alpha | -0.05 | |
Beta | 0.61 |
使用 plot_rolling_returns()
繪製,本案例的基準為台積電買入持有。
pf.plotting.plot_rolling_returns(returns, factor_returns=benchmark_rets)
<AxesSubplot:ylabel='Cumulative returns'>