在 Zipline 中可以加入六種不同的交易限制:¶
交易限制功能可以確保演算法如您所預期的的方式執行,並有助於避免預期外交易所帶來的不良後果。
- set_do_not_order_list:預先設定一個不希望交易到的股票清單。
- set_long_only:預先設定投資組合不能持有任何短部位(short positions)。
- set_max_leverage:設定投資組合的槓桿限制。
- set_max_order_count:限制一天內能夠下幾張 order。
- set_max_order_size:限制特定股票(或全部)的單次下單股數及金額。
- set_max_position_size:限制特定股票(或全部)在帳上的股數及市值。
閱讀本篇之前請先閱讀以下文章:¶
initialize
階段使用。from zipline.api import <欲使用的方法>
或 from zipline.api import *
。import pandas as pd
import numpy as np
import datetime
import tejapi
import time
import os
import warnings
warnings.filterwarnings('ignore')
# tej_key
tej_key ='your key'
tejapi.ApiConfig.api_key = tej_key
os.environ['TEJAPI_BASE'] = "https://api.tej.com.tw"
os.environ['TEJAPI_KEY'] = tej_key
# date
start='2018-07-24'
end='2018-08-24'
os.environ['mdate'] = '20180724 20180824'
tz = 'UTC'
start_dt, end_dt = pd.Timestamp(start, tz = tz), pd.Timestamp(end, tz = tz)
# calendar
calendar_name='TEJ'
# bundle_name
bundle_name = 'tquant'
os.environ['ticker'] = "2330 1216 1101 IR0001 2317 5844 2454 2357"
!zipline ingest -b tquant
Merging daily equity files:
[2023-09-25 08:25:07.529281] INFO: zipline.data.bundles.core: Ingesting tquant.
from zipline.api import *
from zipline import run_algorithm
from zipline.finance import commission, slippage
from zipline.utils.calendar_utils import get_calendar
from zipline.utils.run_algo import get_transaction_detail
from logbook import Logger, StderrHandler, INFO
# 設定log顯示方式
log_handler = StderrHandler(format_string='[{record.time:%Y-%m-%d %H:%M:%S.%f}]: ' +
'{record.level_name}: {record.func_name}: {record.message}',
level=INFO)
log_handler.push_application()
log = Logger('Algorithm')
預先設定一個不希望交易到的股票清單
restricted_list (container[Asset], SecurityList)
Asset
物件(zipline.assets.Asset
,例如:Equity(0 [1101]),透過symbol("1101")
可將 symbol 轉成Asset
物件)on_error (str, optional):
on_error有兩種選項:'fail' 和 'log'。前者直接中斷程式並顯示錯誤訊息,後者會照樣執行但記錄錯誤。預設為'fail'。
若要使用on_error = 'log',請同時進行以下設定,才能顯示 log:
from logbook import Logger, StderrHandler, INFO
# 設定log顯示方式
log_handler = StderrHandler(format_string='[{record.time:%Y-%m-%d %H:%M:%S.%f}]: ' +
'{record.level_name}: {record.func_name}: {record.message}',
level=INFO)
log_handler.push_application()
log = Logger('Algorithm')
在以下程式中,我們把 1101 加入限制清單,on_error(發生時處理方法)使用'log',然後設定在7/26下單。
def initialize(context):
...
set_do_not_order_list(restricted_list = [symbol('1101')], on_error='log')
...
def handle_data(context, data):
...
if context.i == 2: # 2018-07-26
order(symbol('1101'), 100)
...
可以看到執行時跳出
ERROR: handle_violation: Order for 100 shares of Equity(0 [1101]) at 2018-07-26 05:30:00+00:00 violates trading constraint RestrictedListOrder({})
但是觀察 transactions ,可以發現7/27時照樣買入了 1101。
def initialize(context):
context.i = 0
set_do_not_order_list(restricted_list = [symbol('1101')], on_error='log')
set_slippage(slippage.FixedSlippage(spread = 0.0))
set_commission(commission.PerDollar(cost=commission_cost))
set_benchmark(symbol('IR0001'))
def handle_data(context, data):
if context.i == 0: # 2018-07-24
order(symbol('2330'), 100)
if context.i == 2: # 2018-07-26
order(symbol('1101'), 100)
if context.i == 4: # 2018-07-30
order(symbol('1216'), 100)
context.i += 1
commission_cost = 0.001425
capital_base = 1e6
performance = run_algorithm(start=start_dt,
end=end_dt,
initialize=initialize,
handle_data=handle_data,
capital_base=capital_base,
trading_calendar=get_calendar(calendar_name),
bundle=bundle_name)
positions, transactions, orders = get_transaction_detail(performance)
[2023-09-25 08:25:09.801010]: ERROR: handle_violation: Order for 100 shares of Equity(0 [1101]) at 2018-07-26 05:30:00+00:00 violates trading constraint RestrictedListOrder({}) [2023-09-25 08:25:09.812006]: INFO: earn_dividends: Equity(1 [1216]), cash_dividend amount: 5.5, pay_date: 2018-09-07, div_owed: 550.0 [2023-09-25 08:25:09.841082]: INFO: handle_simulation_end: Simulated 24 trading days first open: 2018-07-24 01:01:00+00:00 last close: 2018-08-24 05:30:00+00:00
# 7/27 照樣買入了1101。
transactions
sid | symbol | amount | dt | price | order_id | asset | commission | |
---|---|---|---|---|---|---|---|---|
2018-07-25 13:30:00+08:00 | 3 | 2330 | 100 | 2018-07-25 13:30:00+08:00 | 240.5 | f1148fd31aa84be5b4d7e27c9c858a39 | Equity(3 [2330]) | None |
2018-07-27 13:30:00+08:00 | 0 | 1101 | 100 | 2018-07-27 13:30:00+08:00 | 40.3 | d4971027bce4491db4eeb0cf8655c4bc | Equity(0 [1101]) | None |
2018-07-31 13:30:00+08:00 | 1 | 1216 | 100 | 2018-07-31 13:30:00+08:00 | 80.8 | ac333a81f710428e83c5ccacc8ce72e2 | Equity(1 [1216]) | None |
但如果 on_error 設定成 'fail',整個程式會被中止,然後顯示一樣的錯誤訊息。
set_do_not_order_list(restricted_list = [symbol('1101')], on_error='fail')
def initialize(context):
context.i = 0
set_do_not_order_list(restricted_list = [symbol('1101')], on_error='fail')
set_slippage(slippage.FixedSlippage(spread = 0.0))
set_commission(commission.PerDollar(cost=commission_cost))
set_benchmark(symbol('IR0001'))
def handle_data(context, data):
if context.i == 0: # 2018-07-24
order(symbol('2330'), 100)
if context.i == 2: # 2018-07-26
order(symbol('1101'), 100)
if context.i == 4: # 2018-07-30
order(symbol('1216'), 100)
context.i += 1
commission_cost = 0.001425
capital_base = 1e6
performance = run_algorithm(start=start_dt,
end=end_dt,
initialize=initialize,
handle_data=handle_data,
capital_base=capital_base,
trading_calendar=get_calendar(calendar_name),
bundle=bundle_name)
positions, transactions, orders = get_transaction_detail(performance)
--------------------------------------------------------------------------- TradingControlViolation Traceback (most recent call last) Input In [6], in <cell line: 25>() 22 commission_cost = 0.001425 23 capital_base = 1e6 ---> 25 performance = run_algorithm(start=start_dt, 26 end=end_dt, 27 initialize=initialize, 28 handle_data=handle_data, 29 capital_base=capital_base, 30 trading_calendar=get_calendar(calendar_name), 31 bundle=bundle_name) 33 positions, transactions, orders = get_transaction_detail(performance) File ~\Anaconda3\envs\zipline-tej\lib\site-packages\zipline\utils\run_algo.py:446, in run_algorithm(start, end, initialize, capital_base, handle_data, before_trading_start, analyze, data_frequency, bundle, bundle_timestamp, trading_calendar, metrics_set, benchmark_returns, treasury_returns, default_extension, extensions, strict_extensions, environ, custom_loader, blotter) 443 benchmark_spec = BenchmarkSpec.from_returns(benchmark_returns) 444 treasury_spec = TreasurySpec.from_returns(treasury_returns) #20230210(by MRC) for treasury return column in perf --> 446 return _run( 447 handle_data=handle_data, 448 initialize=initialize, 449 before_trading_start=before_trading_start, 450 analyze=analyze, 451 algofile=None, 452 algotext=None, 453 defines=(), 454 data_frequency=data_frequency, 455 capital_base=capital_base, 456 bundle=bundle, 457 bundle_timestamp=bundle_timestamp, 458 start=start, 459 end=end, 460 output=os.devnull, 461 trading_calendar=trading_calendar, 462 print_algo=False, 463 metrics_set=metrics_set, 464 local_namespace=False, 465 environ=environ, 466 blotter=blotter, 467 custom_loader=custom_loader, 468 benchmark_spec=benchmark_spec, 469 treasury_spec=treasury_spec #20230209 (by MRC) for treasury return column in perf 470 ) File ~\Anaconda3\envs\zipline-tej\lib\site-packages\zipline\utils\run_algo.py:225, in _run(handle_data, initialize, before_trading_start, analyze, algofile, algotext, defines, data_frequency, capital_base, bundle, bundle_timestamp, start, end, output, trading_calendar, print_algo, metrics_set, local_namespace, environ, blotter, custom_loader, benchmark_spec, treasury_spec) 222 raise _RunAlgoError(str(e)) 224 try: --> 225 perf = TradingAlgorithm( 226 namespace=namespace, 227 data_portal=data, 228 get_pipeline_loader=choose_loader, 229 trading_calendar=trading_calendar, 230 sim_params=SimulationParameters( 231 start_session=start, 232 end_session=end, 233 trading_calendar=trading_calendar, 234 capital_base=capital_base, 235 data_frequency=data_frequency, 236 ), 237 metrics_set=metrics_set, 238 blotter=blotter, 239 benchmark_returns=benchmark_returns, 240 benchmark_sid=benchmark_sid, 241 treasury_returns=treasury_returns, #20230209 (by MRC) for treasury return column in perf 242 treasury_sid=treasury_sid, #20230209 (by MRC) for treasury return column in perf 243 **{ 244 "initialize": initialize, 245 "handle_data": handle_data, 246 "before_trading_start": before_trading_start, 247 "analyze": analyze, 248 } 249 if algotext is None 250 else { 251 "algo_filename": getattr(algofile, "name", "<algorithm>"), 252 "script": algotext, 253 }, 254 ).run() 257 except NoBenchmark: 258 raise _RunAlgoError( 259 ( 260 "No ``benchmark_spec`` was provided, and" (...) 268 ), 269 ) File ~\Anaconda3\envs\zipline-tej\lib\site-packages\zipline\algorithm.py:685, in TradingAlgorithm.run(self, data_portal) 683 try: 684 perfs = [] --> 685 for perf in self.get_generator(): 686 perfs.append(perf) 688 # convert perf dict to pandas dataframe File ~\Anaconda3\envs\zipline-tej\lib\site-packages\zipline\gens\tradesimulation.py:230, in AlgorithmSimulator.transform(self) 228 for dt, action in self.clock: 229 if action == BAR: --> 230 for capital_change_packet in every_bar(dt): 231 yield capital_change_packet 232 elif action == SESSION_START: File ~\Anaconda3\envs\zipline-tej\lib\site-packages\zipline\gens\tradesimulation.py:144, in AlgorithmSimulator.transform.<locals>.every_bar(dt_to_use, current_data, handle_data) 141 for commission in new_commissions: 142 metrics_tracker.process_commission(commission) --> 144 handle_data(algo, current_data, dt_to_use) 146 # grab any new orders from the blotter, then clear the list. 147 # this includes cancelled orders. 148 new_orders = blotter.new_orders File ~\Anaconda3\envs\zipline-tej\lib\site-packages\zipline\utils\events.py:206, in EventManager.handle_data(self, context, data, dt) 204 with self._create_context(data): 205 for event in self._events: --> 206 event.handle_data( 207 context, 208 data, 209 dt, 210 ) File ~\Anaconda3\envs\zipline-tej\lib\site-packages\zipline\utils\events.py:229, in Event.handle_data(self, context, data, dt) 225 """ 226 Calls the callable only when the rule is triggered. 227 """ 228 if self.rule.should_trigger(dt): --> 229 self.callback(context, data) File ~\Anaconda3\envs\zipline-tej\lib\site-packages\zipline\algorithm.py:477, in TradingAlgorithm.handle_data(self, data) 475 def handle_data(self, data): 476 if self._handle_data: --> 477 self._handle_data(self, data) Input In [6], in handle_data(context, data) 11 order(symbol('2330'), 100) 13 if context.i == 2: # 2018-07-26 ---> 14 order(symbol('1101'), 100) 16 if context.i == 4: # 2018-07-30 17 order(symbol('1216'), 100) File ~\Anaconda3\envs\zipline-tej\lib\site-packages\zipline\utils\api_support.py:55, in api_method.<locals>.wrapped(*args, **kwargs) 51 if algo_instance is None: 52 raise RuntimeError( 53 "zipline api method %s must be called during a simulation." % f.__name__ 54 ) ---> 55 return getattr(algo_instance, f.__name__)(*args, **kwargs) File ~\Anaconda3\envs\zipline-tej\lib\site-packages\zipline\utils\api_support.py:132, in disallowed_in_before_trading_start.<locals>.decorator.<locals>.wrapped_method(self, *args, **kwargs) 130 if self._in_before_trading_start: 131 raise exception --> 132 return method(self, *args, **kwargs) File ~\Anaconda3\envs\zipline-tej\lib\site-packages\zipline\algorithm.py:1447, in TradingAlgorithm.order(self, asset, amount, limit_price, stop_price, style) 1444 if not self._can_order_asset(asset): 1445 return None -> 1447 amount, style = self._calculate_order( 1448 asset, amount, limit_price, stop_price, style 1449 ) 1450 return self.blotter.order(asset, amount, style) File ~\Anaconda3\envs\zipline-tej\lib\site-packages\zipline\algorithm.py:1458, in TradingAlgorithm._calculate_order(self, asset, amount, limit_price, stop_price, style) 1455 amount = self.round_order(amount) 1457 # Raises a ZiplineError if invalid parameters are detected. -> 1458 self.validate_order_params(asset, amount, limit_price, stop_price, style) 1460 # Convert deprecated limit_price and stop_price parameters to use 1461 # ExecutionStyle objects. 1462 style = self.__convert_order_params_for_blotter( 1463 asset, limit_price, stop_price, style 1464 ) File ~\Anaconda3\envs\zipline-tej\lib\site-packages\zipline\algorithm.py:1503, in TradingAlgorithm.validate_order_params(self, asset, amount, limit_price, stop_price, style) 1498 raise UnsupportedOrderParameters( 1499 msg="Passing both stop_price and style is not supported." 1500 ) 1502 for control in self.trading_controls: -> 1503 control.validate( 1504 asset, 1505 amount, 1506 self.portfolio, 1507 self.get_datetime(), 1508 self.trading_client.current_data, 1509 ) File ~\Anaconda3\envs\zipline-tej\lib\site-packages\zipline\finance\controls.py:149, in RestrictedListOrder.validate(self, asset, amount, portfolio, algo_datetime, algo_current_data) 145 """ 146 Fail if the asset is in the restricted_list. 147 """ 148 if self.restrictions.is_restricted(asset, algo_datetime): --> 149 self.handle_violation(asset, amount, algo_datetime) File ~\Anaconda3\envs\zipline-tej\lib\site-packages\zipline\finance\controls.py:82, in TradingControl.handle_violation(self, asset, amount, datetime, metadata) 79 constraint = self._constraint_msg(metadata) 81 if self.on_error == "fail": ---> 82 raise TradingControlViolation( 83 asset=asset, amount=amount, datetime=datetime, constraint=constraint 84 ) 85 elif self.on_error == "log": 86 log.error( 87 "Order for {amount} shares of {asset} at {dt} " 88 "violates trading constraint {constraint}", (...) 92 constraint=constraint, 93 ) TradingControlViolation: Order for 100 shares of Equity(0 [1101]) at 2018-07-26 05:30:00+00:00 violates trading constraint RestrictedListOrder({}).
預先設定投資組合不能持有任何短部位(short positions)
on_error (str, optional):
from logbook import Logger, StderrHandler, INFO
# 設定log顯示方式
log_handler = StderrHandler(format_string='[{record.time:%Y-%m-%d %H:%M:%S.%f}]: ' +
'{record.level_name}: {record.func_name}: {record.message}',
level=INFO)
log_handler.push_application()
log = Logger('Algorithm')
set_long_only(on_error='log')
,並且在7/24買入 1000 股 2330 股票、7/26賣出 500 股 2330 股票且在7/30賣出 800 股 2330 股票。def initialize(context):
...
set_long_only(on_error='log')
...
def handle_data(context, data):
if context.i == 0: # 7/24
order(symbol('2330'), 1000)
if context.i == 2: # 7/26
order(symbol('2330'), -500)
if context.i == 4: # 7/30
order(symbol('2330'), -800)
...
ERROR: handle_violation: Order for -800 shares of Equity(14 [2330]) at 2018-07-30 05:30:00+00:00 violates trading
constraint LongOnly({})
但因為 on_error 設定是 'log',股票依然成功賣出。
def initialize(context):
context.i = 0
set_long_only(on_error='log')
set_slippage(slippage.FixedSlippage(spread = 0.0))
set_commission(commission.PerDollar(cost=commission_cost))
set_benchmark(symbol('IR0001'))
def handle_data(context, data):
if context.i == 0: # 7/24
order(symbol('2330'), 1000)
if context.i == 2: # 7/26
order(symbol('2330'), -500)
if context.i == 4: # 7/30
order(symbol('2330'), -800)
context.i += 1
commission_cost = 0.001425
capital_base = 1e6
performance = run_algorithm(start=start_dt,
end=end_dt,
initialize=initialize,
handle_data=handle_data,
capital_base=capital_base,
trading_calendar=get_calendar(calendar_name),
bundle=bundle_name)
positions, transactions, orders = get_transaction_detail(performance)
[2023-09-25 08:25:16.371644]: ERROR: handle_violation: Order for -800 shares of Equity(3 [2330]) at 2018-07-30 05:30:00+00:00 violates trading constraint LongOnly({}) [2023-09-25 08:25:16.395643]: INFO: handle_simulation_end: Simulated 24 trading days first open: 2018-07-24 01:01:00+00:00 last close: 2018-08-24 05:30:00+00:00
# 7/31持有股數(amount)變成-300
positions[0:6]
sid | symbol | asset | amount | cost_basis | last_sale_price | |
---|---|---|---|---|---|---|
2018-07-25 13:30:00+08:00 | 3 | 2330 | Equity(3 [2330]) | 1000 | 240.842713 | 240.5 |
2018-07-26 13:30:00+08:00 | 3 | 2330 | Equity(3 [2330]) | 1000 | 240.842713 | 241.0 |
2018-07-27 13:30:00+08:00 | 3 | 2330 | Equity(3 [2330]) | 500 | 241.191125 | 244.5 |
2018-07-30 13:30:00+08:00 | 3 | 2330 | Equity(3 [2330]) | 500 | 241.191125 | 245.5 |
2018-07-31 13:30:00+08:00 | 3 | 2330 | Equity(3 [2330]) | -300 | 245.065200 | 246.0 |
2018-08-01 13:30:00+08:00 | 3 | 2330 | Equity(3 [2330]) | -300 | 245.065200 | 248.0 |
# 持有short_position的股票個數
performance.shorts_count
2018-07-24 13:30:00+08:00 0 2018-07-25 13:30:00+08:00 0 2018-07-26 13:30:00+08:00 0 2018-07-27 13:30:00+08:00 0 2018-07-30 13:30:00+08:00 0 2018-07-31 13:30:00+08:00 1 2018-08-01 13:30:00+08:00 1 2018-08-02 13:30:00+08:00 1 2018-08-03 13:30:00+08:00 1 2018-08-06 13:30:00+08:00 1 2018-08-07 13:30:00+08:00 1 2018-08-08 13:30:00+08:00 1 2018-08-09 13:30:00+08:00 1 2018-08-10 13:30:00+08:00 1 2018-08-13 13:30:00+08:00 1 2018-08-14 13:30:00+08:00 1 2018-08-15 13:30:00+08:00 1 2018-08-16 13:30:00+08:00 1 2018-08-17 13:30:00+08:00 1 2018-08-20 13:30:00+08:00 1 2018-08-21 13:30:00+08:00 1 2018-08-22 13:30:00+08:00 1 2018-08-23 13:30:00+08:00 1 2018-08-24 13:30:00+08:00 1 Name: shorts_count, dtype: int64
設定投資組合的槓桿限制
max_leverage (float)
gross leverage
。(long_exposure - short_exposure)/(long_exposure + short_exposure + ending_cash)
。max_leverage
時,程式會 fail。set_max_leverage(3.0)
。def handle_data(context, data):
if context.i == 0: # 7/24
order_value(symbol('2330'), 1e6)
order_value(symbol('2317'), -1e6)
...
if context.i == 2: # 7/26
order_value(symbol('2454'), 5e5)
def initialize(context):
context.i = 0
set_slippage(slippage.FixedSlippage(spread = 0.0))
set_max_leverage(3.0)
set_commission(commission.PerDollar(cost=commission_cost))
set_benchmark(symbol('IR0001'))
def handle_data(context, data):
if context.i == 0: # 7/24
order_value(symbol('2330'), 1e6)
order_value(symbol('2317'), -1e6)
if context.i == 2: # 7/26
order_value(symbol('2454'), 5e5)
context.i += 1
commission_cost = 0.001425
capital_base = 1e6
performance = run_algorithm(start=start_dt,
end=end_dt,
initialize=initialize,
handle_data=handle_data,
capital_base=capital_base,
trading_calendar=get_calendar(calendar_name),
bundle=bundle_name)
positions, transactions, orders = get_transaction_detail(performance)
[2023-09-25 08:25:18.380745]: INFO: handle_simulation_end: Simulated 24 trading days first open: 2018-07-24 01:01:00+00:00 last close: 2018-08-24 05:30:00+00:00
gross_leverage
可以直接從run_algorithm
的結果進行觀察。(performance['gross_leverage']
)
leverage
= ( long_exposure - short_exposure ) / ( long_exposure + short_exposure + ending_cash )
= ( 2330 的 amount * 2330 的 last_sale_price - 2317 的 amount * 2317 的 last_sale_price ) /
( ending_cash + 2330 的 amount * 2330 的 last_sale_price + 2317 的 amount * 2317 的 last_sale_price )
= ( 4149 * 240.5 - (- 11737 ) * 82.7 ) / ( 970010.30973 + 4149 * 240.5 + (- 11737 ) * 82.7 )
= 1.974022
last_sale_price
及amount
皆來自performance
中的positions
。last_sale_price
代表標的最近一筆的收盤價。amount
代表該標的總持有股數。ending_cash
來自performance
中的ending_cash
,代表當日結束時帳上持有現金。performance.loc['2018-07-25':'2018-07-27',['gross_leverage','portfolio_value','ending_cash']]
gross_leverage | portfolio_value | ending_cash | |
---|---|---|---|
2018-07-25 13:30:00+08:00 | 1.974022 | 9.971949e+05 | 970010.309730 |
2018-07-26 13:30:00+08:00 | 2.000191 | 9.898798e+05 | 970010.309730 |
2018-07-27 13:30:00+08:00 | 2.481604 | 1.007198e+06 | 460768.172293 |
positions[0:2]
sid | symbol | asset | amount | cost_basis | last_sale_price | |
---|---|---|---|---|---|---|
2018-07-25 13:30:00+08:00 | 3 | 2330 | Equity(3 [2330]) | 4149 | 240.842712 | 240.5 |
2018-07-25 13:30:00+08:00 | 2 | 2317 | Equity(2 [2317]) | -11737 | 82.582153 | 82.7 |
set_max_leverage(2.0)
,在7/26時因為股價波動,leverage 超過 2.0,程式就會被終止,跳出錯誤訊息。set_max_leverage(2.4)
,且在7/27 long價值 50 萬的 2454,leverage 會達到 2.48,程式也一樣會被終止,跳出錯誤訊息。
限制一天內能夠下幾張 order
max_count (int):一天內最多的下單數。
on_error (str, optional):
on_error有兩種選項:'fail' 和 'log'。前者直接中斷程式並顯示錯誤訊息,後者會照樣執行但記錄錯誤。預設為'fail'。
若要使用on_error = 'log',請同時進行以下設定,才能顯示 log:
from logbook import Logger, StderrHandler, INFO
# 設定log顯示方式
log_handler = StderrHandler(format_string='[{record.time:%Y-%m-%d %H:%M:%S.%f}]: ' +
'{record.level_name}: {record.func_name}: {record.message}',
level=INFO)
log_handler.push_application()
log = Logger('Algorithm')
set_max_order_count(max_count=3, on_error='log')
,限制最大下單數為 3。set_slippage(slippage.VolumeShareSlippage(volume_limit=0.025, price_impact=0.1))
,成交量限制為 2.5%。def handle_data(context, data):
if context.i == 0: # 7/24
order_value(symbol('2330'), 5e8)
order_value(symbol('2357'), 3e8)
if context.i == 1: # 7/25
order_value(symbol('2454'), 5e5)
order_value(symbol('2317'), 5e5)
order_value(symbol('1101'), 5e5)
...
performance
的orders
取出created = '2018-07-25'
的訂單進行計算。def initialize(context):
context.i = 0
set_slippage(slippage.VolumeShareSlippage(volume_limit=0.025, price_impact=0.1))
set_max_order_count(max_count=3, on_error='log')
set_commission(commission.PerDollar(cost=commission_cost))
set_benchmark(symbol('IR0001'))
def handle_data(context, data):
if context.i == 0: # 7/24
order_value(symbol('2330'), 5e8)
order_value(symbol('2357'), 3e8)
if context.i == 1: # 7/25
order_value(symbol('2454'), 5e5)
order_value(symbol('2317'), 5e5)
order_value(symbol('1101'), 5e5)
context.i += 1
commission_cost = 0.001425
capital_base = 1e9
performance = run_algorithm(start=start_dt,
end=end_dt,
initialize=initialize,
handle_data=handle_data,
capital_base=capital_base,
trading_calendar=get_calendar(calendar_name),
bundle=bundle_name)
positions, transactions, orders = get_transaction_detail(performance)
[2023-09-25 08:25:21.394726]: INFO: handle_simulation_end: Simulated 24 trading days first open: 2018-07-24 01:01:00+00:00 last close: 2018-08-24 05:30:00+00:00
VolumeShareSlippage
的限制,所以會拆成數天成交。orders.loc['2018-07-25']
sid | symbol | id | dt | reason | created | amount | filled | commission | stop | limit | stop_reached | limit_reached | asset | status | |
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
2018-07-25 13:30:00+08:00 | 3 | 2330 | 2f3d2c75c6a14f0982e20325263d801e | 2018-07-25 13:30:00+08:00 | None | 2018-07-24 13:30:00+08:00 | 2074688 | 538250 | 184476.532188 | None | None | False | False | Equity(3 [2330]) | 0 |
2018-07-25 13:30:00+08:00 | 4 | 2357 | 0c8c942572bb4ca9a8ce29ad629133c4 | 2018-07-25 13:30:00+08:00 | None | 2018-07-24 13:30:00+08:00 | 1138519 | 53175 | 19778.347944 | None | None | False | False | Equity(4 [2357]) | 0 |
2018-07-25 13:30:00+08:00 | 5 | 2454 | e69b7d9d8b164b4d9939038ca0c855a0 | 2018-07-25 13:30:00+08:00 | None | 2018-07-25 13:30:00+08:00 | 1904 | 0 | 0.000000 | None | None | False | False | Equity(5 [2454]) | 0 |
2018-07-25 13:30:00+08:00 | 2 | 2317 | 8ba7f1517291473b8bf6649451c2c63f | 2018-07-25 13:30:00+08:00 | None | 2018-07-25 13:30:00+08:00 | 6045 | 0 | 0.000000 | None | None | False | False | Equity(2 [2317]) | 0 |
2018-07-25 13:30:00+08:00 | 0 | 1101 | 62895a53e99b4f5d96a2f178705220fd | 2018-07-25 13:30:00+08:00 | None | 2018-07-25 13:30:00+08:00 | 11086 | 0 | 0.000000 | None | None | False | False | Equity(0 [1101]) | 0 |
如果設定set_max_order_count(max_count=2, on_error='log')
,則在7/25會出現以下訊息:
ERROR: handle_violation: Order for 11086 shares of Equity(0 [1101]) at 2018-07-25 05:30:00+00:00 violates trading constraint MaxOrderCount({'max_count': 2})
這個函數限制特定股票(或全部)的單次下單股數和金額
asset (Asset, optional)
Asset
物件(zipline.assets.Asset
,例如:Equity(0 [1101]),透過symbol("1101")
可將 symbol 轉成Asset
物件)max_shares (int, optional)-最大單次下單股數。
max_notional (float, optional)-最大單次下單金額。
on_error (str, optional):
on_error有兩種選項:'fail' 和 'log'。前者直接中斷程式並顯示錯誤訊息,後者會照樣執行但記錄錯誤。預設為'fail'。
若要使用on_error = 'log',請同時進行以下設定,才能顯示log:
from logbook import Logger, StderrHandler, INFO
# 設定log顯示方式
log_handler = StderrHandler(format_string='[{record.time:%Y-%m-%d %H:%M:%S.%f}]: ' +
'{record.level_name}: {record.func_name}: {record.message}',
level=INFO)
log_handler.push_application()
log = Logger('Algorithm')
max_shares
是以下單時股數為準。max_notional
計算方法為下單時的股數 * 下單當天收盤價,所以成交時的股數和金額可能還是會超過set_max_order_size
的限制,細節在下面的範例說明。def initialize(context):
...
set_max_order_size(asset= symbol('1101'), max_shares=1000, on_error='log')
set_max_order_size(asset= symbol('2330'), max_shares=2000, max_notional=481000, on_error='log')
...
def handle_data(context, data):
if context.i == 1: # 2018-07-25
order(symbol('1101'), 1000)
order(symbol('2330'), 2000)
...
if context.i == 17: # 2018-08-16
order(symbol('2330'), 2005)
def initialize(context):
context.i = 0
set_max_order_size(asset= symbol('1101'), max_shares=1000, on_error='log')
set_max_order_size(asset= symbol('2330'), max_shares=2000, max_notional=481000, on_error='log')
set_slippage(slippage.FixedSlippage(spread = 0.0))
set_commission(commission.PerDollar(cost=0.01))
set_benchmark(symbol('IR0001'))
def handle_data(context, data):
if context.i == 1: # 2018-07-25
order(symbol('1101'), 1000)
order(symbol('2330'), 2000)
if context.i == 17: # 2018-08-16
order(symbol('2330'), 2005)
context.i += 1
commission_cost = 0.001425
capital_base = 1e6
performance = run_algorithm(start=start_dt,
end=end_dt,
initialize=initialize,
handle_data=handle_data,
capital_base=capital_base,
trading_calendar=get_calendar(calendar_name),
bundle=bundle_name)
closing_price = tejapi.fastget('TWN/APIPRCD',
coid=['1101','2330'],
opts={'columns':['mdate','coid','close_d']},
mdate={'gte':start_dt,'lte':end_dt },
paginate=True)
positions, transactions, orders = get_transaction_detail(performance)
[2023-09-25 08:25:23.931322]: ERROR: handle_violation: Order for 2005 shares of Equity(3 [2330]) at 2018-08-16 05:30:00+00:00 violates trading constraint MaxOrderSize({'asset': Equity(3 [2330]), 'max_shares': 2000, 'max_notional': 481000}) [2023-09-25 08:25:23.949157]: INFO: handle_simulation_end: Simulated 24 trading days first open: 2018-07-24 01:01:00+00:00 last close: 2018-08-24 05:30:00+00:00
orders.query('symbol == "1101"')
sid | symbol | id | dt | reason | created | amount | filled | commission | stop | limit | stop_reached | limit_reached | asset | status | |
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
2018-07-25 13:30:00+08:00 | 0 | 1101 | ae91c552e8444d46ba046678ef6d5735 | 2018-07-25 13:30:00+08:00 | None | 2018-07-25 13:30:00+08:00 | 1000 | 0 | 0.0 | None | None | False | False | Equity(0 [1101]) | 0 |
2018-07-26 13:30:00+08:00 | 0 | 1101 | ae91c552e8444d46ba046678ef6d5735 | 2018-07-26 13:30:00+08:00 | None | 2018-07-25 13:30:00+08:00 | 1100 | 1100 | 445.5 | None | None | False | False | Equity(0 [1101]) | 1 |
max_notional
,但並不會跳錯訊息。closing_price.query('(coid == "2330") & (mdate in ["2018-07-25","2018-07-26"])')
mdate | coid | close_d | |
---|---|---|---|
25 | 2018-07-25 | 2330 | 240.5 |
26 | 2018-07-26 | 2330 | 241.0 |
transactions.loc['2018-07-26']
sid | symbol | amount | dt | price | order_id | asset | commission | |
---|---|---|---|---|---|---|---|---|
2018-07-26 13:30:00+08:00 | 0 | 1101 | 1100 | 2018-07-26 13:30:00+08:00 | 40.5 | ae91c552e8444d46ba046678ef6d5735 | Equity(0 [1101]) | None |
2018-07-26 13:30:00+08:00 | 3 | 2330 | 2000 | 2018-07-26 13:30:00+08:00 | 241.0 | 2c71621d421a4b51838e0bc65f9e949b | Equity(3 [2330]) | None |
在8/16下 2005 股的 2330,雖然當天 notional = 239 * 2005 = 479195 沒有超過預先設定的 481000,但已經超過 2000 股的限制。因為on_error = 'log',程式繼續運作,照樣成交,但是跳出錯誤訊息:
ERROR: handle_violation: Order for 2005 shares of Equity(14 [2330]) at 2018-08-16 05:30:00+00:00 violates trading
constraint MaxOrderSize({'asset': Equity(14 [2330]), 'max_shares': 2000, 'max_notional': 481000})
closing_price.query('(coid == "2330") & (mdate in ["2018-08-16","2018-08-17"])')
mdate | coid | close_d | |
---|---|---|---|
41 | 2018-08-16 | 2330 | 239.0 |
42 | 2018-08-17 | 2330 | 239.5 |
transactions.loc['2018-08-17']
sid | symbol | amount | dt | price | order_id | asset | commission | |
---|---|---|---|---|---|---|---|---|
2018-08-17 13:30:00+08:00 | 3 | 2330 | 2005 | 2018-08-17 13:30:00+08:00 | 239.5 | 2363ca6eedd441e0ab297fe80d2f5fef | Equity(3 [2330]) | None |
限制特定股票(或全部)在帳上的股數及市值
asset (Asset, optional)
Asset
物件(zipline.assets.Asset
,例如:Equity(0 [1101]))max_shares (int, optional)-最大持股股數。
max_notional (float, optional)-最大持股市值。
on_error (str, optional):
on_error有兩種選項:'fail' 和 'log'。前者直接中斷程式並顯示錯誤訊息,後者會照樣執行但記錄錯誤。預設為'fail'。
若要使用on_error = 'log',請同時進行以下設定,才能顯示log:
from logbook import Logger, StderrHandler, INFO
# 設定log顯示方式
log_handler = StderrHandler(format_string='[{record.time:%Y-%m-%d %H:%M:%S.%f}]: ' +
'{record.level_name}: {record.func_name}: {record.message}',
level=INFO)
log_handler.push_application()
log = Logger('Algorithm')
set_max_order_size()
非常類似,差別在於它是限制某支(或全部)股票在帳上的股數及市值,而非針對單筆訂單。max_order_size
和max_position_size
來解釋運作規則。在範例中,我們做以下限制:
限制 2330 單筆訂單的 max_shares = 2000、max_notional = 481000。
限制 1101 持股部位的 max_shares = 1050。
限制 2330 持股部位的 max_shares = 2000、max_notional = 600000。
def initialize(context):
...
set_max_order_size(asset= symbol('2330'), max_shares=2000, max_notional=481000, on_error='log')
set_max_position_size(asset= symbol('1101'), max_shares=1050, on_error='log')
set_max_position_size(asset= symbol('2330'), max_shares=2000, max_notional=600000, on_error='log')
...
下單設定:
def handle_data(context, data):
if context.i ==0: # 2018-07-24
order(symbol('1101'), 1000)
if context.i == 1: # 2018-07-25
order(symbol('2330'), 2000)
order(symbol('2330'), 1000)
if context.i == 5: # 2018-07-31
order(symbol('2330'), 500)
def initialize(context):
context.i = 0
set_max_order_size(asset= symbol('2330'), max_shares=2000, max_notional=481000, on_error='log')
set_max_position_size(asset= symbol('1101'), max_shares=1050, on_error='log')
set_max_position_size(asset= symbol('2330'), max_shares=2000, max_notional=600000, on_error='log')
set_slippage(slippage.FixedSlippage(spread = 0.0))
set_commission(commission.PerDollar(cost=0.01))
set_benchmark(symbol('IR0001'))
def handle_data(context, data):
if context.i ==0: # 2018-07-24
order(symbol('1101'), 1000)
if context.i == 1: # 2018-07-25
order(symbol('2330'), 2000)
order(symbol('2330'), 1000)
if context.i == 5: # 2018-07-31
order(symbol('2330'), 500)
context.i += 1
commission_cost = 0.001425
capital_base = 1e6
performance = run_algorithm(start=start_dt,
end=end_dt,
initialize=initialize,
handle_data=handle_data,
capital_base=capital_base,
trading_calendar=get_calendar(calendar_name),
bundle=bundle_name)
closing_price = tejapi.fastget('TWN/APIPRCD',
coid=['1101','2330'],
opts={'columns':['mdate','coid','close_d']},
mdate={'gte':start_dt,'lte':end_dt },
paginate=True)
positions, transactions, orders = get_transaction_detail(performance)
[2023-09-25 08:25:28.995945]: INFO: earn_dividends: Equity(0 [1101]), cash_dividend amount: 1.5, pay_date: 2018-08-24, div_owed: 1500.0 [2023-09-25 08:25:28.996945]: INFO: handle_split: after split: asset: Equity(0 [1101]), amount: 1100, cost_basis: 41.4, last_sale_price: 45.1 [2023-09-25 08:25:28.996945]: INFO: handle_split: returning cash: 7.33 [2023-09-25 08:25:29.004945]: ERROR: handle_violation: Order for 500 shares of Equity(3 [2330]) at 2018-07-31 05:30:00+00:00 violates trading constraint MaxPositionSize({'asset': Equity(3 [2330]), 'max_shares': 2000, 'max_notional': 600000}) [2023-09-25 08:25:29.005946]: ERROR: handle_violation: Order for 500 shares of Equity(3 [2330]) at 2018-07-31 05:30:00+00:00 violates trading constraint MaxPositionSize({'asset': Equity(3 [2330]), 'max_shares': 2000, 'max_notional': 600000}) [2023-09-25 08:25:29.037109]: INFO: handle_simulation_end: Simulated 24 trading days first open: 2018-07-24 01:01:00+00:00 last close: 2018-08-24 05:30:00+00:00
MaxPositionSize
)。orders[0:2]
sid | symbol | id | dt | reason | created | amount | filled | commission | stop | limit | stop_reached | limit_reached | asset | status | |
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
2018-07-24 13:30:00+08:00 | 0 | 1101 | e6212ce9c29b467d9d4372a7f9ec8bb2 | 2018-07-24 13:30:00+08:00 | None | 2018-07-24 13:30:00+08:00 | 1000 | 0 | 0.0 | None | None | False | False | Equity(0 [1101]) | 0 |
2018-07-25 13:30:00+08:00 | 0 | 1101 | e6212ce9c29b467d9d4372a7f9ec8bb2 | 2018-07-25 13:30:00+08:00 | None | 2018-07-24 13:30:00+08:00 | 1000 | 1000 | 451.0 | None | None | False | False | Equity(0 [1101]) | 1 |
positions[0:2]
sid | symbol | asset | amount | cost_basis | last_sale_price | |
---|---|---|---|---|---|---|
2018-07-25 13:30:00+08:00 | 0 | 1101 | Equity(0 [1101]) | 1000 | 45.551 | 45.1 |
2018-07-26 13:30:00+08:00 | 0 | 1101 | Equity(0 [1101]) | 1100 | 41.400 | 40.5 |
MaxOrderSize
)。因為兩單都沒有超過max_order_size 的 2000 股限制。max_position_size
認為其中一單是讓 position 從 0 變成 2000,另一單是讓 position 從 0 變成 1000,都沒有超過總股數 2000 的限制(max_notional
部分也是同樣概念),所以也沒有錯誤訊息(MaxPositionSize
)。max_notional(600000)
,但也沒有錯誤訊息,因為並不是一直追蹤帳上的部位。max_notional
部分也是同樣概念)。ERROR: handle_violation: Order for 500 shares of Equity(3 [2330]) at 2018-07-31 05:30:00+00:00 violates trading constraint MaxPositionSize({'asset': Equity(3 [2330]), 'max_shares': 2000, 'max_notional': 600000})
ERROR: handle_violation: Order for 500 shares of Equity(3 [2330]) at 2018-07-31 05:30:00+00:00 violates trading constraint MaxPositionSize({'asset': Equity(3 [2330]), 'max_shares': 2000, 'max_notional': 600000})
orders.query('symbol=="2330"')
sid | symbol | id | dt | reason | created | amount | filled | commission | stop | limit | stop_reached | limit_reached | asset | status | |
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
2018-07-25 13:30:00+08:00 | 3 | 2330 | b04afe6c14f440c4bbc460cdb6fea2fd | 2018-07-25 13:30:00+08:00 | None | 2018-07-25 13:30:00+08:00 | 2000 | 0 | 0.0 | None | None | False | False | Equity(3 [2330]) | 0 |
2018-07-25 13:30:00+08:00 | 3 | 2330 | 1097aa119bbc4f2ebe42eb627c02ed0f | 2018-07-25 13:30:00+08:00 | None | 2018-07-25 13:30:00+08:00 | 1000 | 0 | 0.0 | None | None | False | False | Equity(3 [2330]) | 0 |
2018-07-26 13:30:00+08:00 | 3 | 2330 | b04afe6c14f440c4bbc460cdb6fea2fd | 2018-07-26 13:30:00+08:00 | None | 2018-07-25 13:30:00+08:00 | 2000 | 2000 | 4820.0 | None | None | False | False | Equity(3 [2330]) | 1 |
2018-07-26 13:30:00+08:00 | 3 | 2330 | 1097aa119bbc4f2ebe42eb627c02ed0f | 2018-07-26 13:30:00+08:00 | None | 2018-07-25 13:30:00+08:00 | 1000 | 1000 | 2410.0 | None | None | False | False | Equity(3 [2330]) | 1 |
2018-07-31 13:30:00+08:00 | 3 | 2330 | 7eaa433315ec438b9837695951a0b126 | 2018-07-31 13:30:00+08:00 | None | 2018-07-31 13:30:00+08:00 | 500 | 0 | 0.0 | None | None | False | False | Equity(3 [2330]) | 0 |
2018-08-01 13:30:00+08:00 | 3 | 2330 | 7eaa433315ec438b9837695951a0b126 | 2018-08-01 13:30:00+08:00 | None | 2018-07-31 13:30:00+08:00 | 500 | 500 | 1240.0 | None | None | False | False | Equity(3 [2330]) | 1 |
positions['mv'] = positions['amount'] * positions['last_sale_price']
positions.loc[positions.symbol=='2330']
sid | symbol | asset | amount | cost_basis | last_sale_price | mv | |
---|---|---|---|---|---|---|---|
2018-07-26 13:30:00+08:00 | 3 | 2330 | Equity(3 [2330]) | 3000 | 243.41 | 241.0 | 723000.0 |
2018-07-27 13:30:00+08:00 | 3 | 2330 | Equity(3 [2330]) | 3000 | 243.41 | 244.5 | 733500.0 |
2018-07-30 13:30:00+08:00 | 3 | 2330 | Equity(3 [2330]) | 3000 | 243.41 | 245.5 | 736500.0 |
2018-07-31 13:30:00+08:00 | 3 | 2330 | Equity(3 [2330]) | 3000 | 243.41 | 246.0 | 738000.0 |
2018-08-01 13:30:00+08:00 | 3 | 2330 | Equity(3 [2330]) | 3500 | 244.42 | 248.0 | 868000.0 |
2018-08-02 13:30:00+08:00 | 3 | 2330 | Equity(3 [2330]) | 3500 | 244.42 | 244.5 | 855750.0 |
2018-08-03 13:30:00+08:00 | 3 | 2330 | Equity(3 [2330]) | 3500 | 244.42 | 247.0 | 864500.0 |
2018-08-06 13:30:00+08:00 | 3 | 2330 | Equity(3 [2330]) | 3500 | 244.42 | 245.5 | 859250.0 |
2018-08-07 13:30:00+08:00 | 3 | 2330 | Equity(3 [2330]) | 3500 | 244.42 | 241.5 | 845250.0 |
2018-08-08 13:30:00+08:00 | 3 | 2330 | Equity(3 [2330]) | 3500 | 244.42 | 247.5 | 866250.0 |
2018-08-09 13:30:00+08:00 | 3 | 2330 | Equity(3 [2330]) | 3500 | 244.42 | 247.0 | 864500.0 |
2018-08-10 13:30:00+08:00 | 3 | 2330 | Equity(3 [2330]) | 3500 | 244.42 | 245.0 | 857500.0 |
2018-08-13 13:30:00+08:00 | 3 | 2330 | Equity(3 [2330]) | 3500 | 244.42 | 240.5 | 841750.0 |
2018-08-14 13:30:00+08:00 | 3 | 2330 | Equity(3 [2330]) | 3500 | 244.42 | 243.5 | 852250.0 |
2018-08-15 13:30:00+08:00 | 3 | 2330 | Equity(3 [2330]) | 3500 | 244.42 | 241.5 | 845250.0 |
2018-08-16 13:30:00+08:00 | 3 | 2330 | Equity(3 [2330]) | 3500 | 244.42 | 239.0 | 836500.0 |
2018-08-17 13:30:00+08:00 | 3 | 2330 | Equity(3 [2330]) | 3500 | 244.42 | 239.5 | 838250.0 |
2018-08-20 13:30:00+08:00 | 3 | 2330 | Equity(3 [2330]) | 3500 | 244.42 | 239.5 | 838250.0 |
2018-08-21 13:30:00+08:00 | 3 | 2330 | Equity(3 [2330]) | 3500 | 244.42 | 241.0 | 843500.0 |
2018-08-22 13:30:00+08:00 | 3 | 2330 | Equity(3 [2330]) | 3500 | 244.42 | 242.0 | 847000.0 |
2018-08-23 13:30:00+08:00 | 3 | 2330 | Equity(3 [2330]) | 3500 | 244.42 | 244.5 | 855750.0 |
2018-08-24 13:30:00+08:00 | 3 | 2330 | Equity(3 [2330]) | 3500 | 244.42 | 243.5 | 852250.0 |