#!/usr/bin/env python # coding: utf-8 # # # Zipline Trading Controls 交易限制函數 # > ## 在 Zipline 中可以加入六種不同的交易限制: # > 交易限制功能可以確保演算法如您所預期的的方式執行,並有助於避免預期外交易所帶來的不良後果。 # > # > 1. [set_do_not_order_list](#set_do_not_order_list):預先設定一個不希望交易到的股票清單。 # > 2. [set_long_only](#set_long_only):預先設定投資組合不能持有任何短部位(short positions)。 # > 3. [set_max_leverage](#set_max_leverage):設定投資組合的槓桿限制。 # > 4. [set_max_order_count](#set_max_order_count):限制一天內能夠下幾張 order。 # > 5. [set_max_order_size](#set_max_order_size):限制特定股票(或全部)的單次下單股數及金額。 # > 6. [set_max_position_size](#set_max_position_size):限制特定股票(或全部)在帳上的股數及市值。 # > # > ## 閱讀本篇之前請先閱讀以下文章: # > # > 1. [TSMC buy and hold strategy.ipynb](https://github.com/tejtw/TQuant-Lab/blob/main/lecture/TSMC%20buy%20and%20hold%20strategy.ipynb) # > # > 2. [Zipline Order(order & order_target).ipynb](https://github.com/tejtw/TQuant-Lab/blob/main/lecture/Zipline%20Order%20(order%20%26%20order_target).ipynb) # > # > 3. [Zipline Order(value & target_value).ipynb](https://github.com/tejtw/TQuant-Lab/blob/main/lecture/Zipline%20Order%20(value%20%26%20target_value).ipynb) # > # > 4. [Zipline Order(percent & target_percent).ipynb](https://github.com/tejtw/TQuant-Lab/blob/main/lecture/Zipline%20Order%20(percent%20%26%20target_percent).ipynb) # > # > 5. [Zipline Slippage.ipynb](https://github.com/tejtw/TQuant-Lab/blob/main/lecture/Zipline%20Slippage.ipynb) # ### 補充說明: # - 交易限制系列函數通常在`initialize`階段使用。 # - 可以一次加入多個交易限制。 # - 因為交易限制函數皆是 zipline api 方法,需先`from zipline.api import <欲使用的方法>` 或 `from zipline.api import *`。 # ## 設定環境 # In[1]: 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" # In[2]: get_ipython().system('zipline ingest -b tquant') # In[3]: 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') # # # set_do_not_order_list # # [Return to Menu](#menu) # # ### zipline.api.set_do_not_order_list(restricted_list, on_error='fail') # 預先設定一個不希望交易到的股票清單 # # # - restricted_list (*container[Asset], SecurityList*) # - 預先限制不能交易的股票清單。 # - container 中必須是`Asset`物件(`zipline.assets.Asset`,例如:Equity(0 [1101]),透過`symbol("1101")`可將 symbol 轉成`Asset`物件) # # # - on_error (*str, optional*): # - on_error有兩種選項:'fail' 和 'log'。前者直接中斷程式並顯示錯誤訊息,後者會照樣執行但記錄錯誤。預設為'fail'。 # - 若要使用on_error = 'log',請同時進行以下設定,才能顯示 log: # # ```python # 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') # ``` # # #### 範例1 # # 在以下程式中,我們把 **1101 加入限制清單**,**on_error(發生時處理方法)使用'log'**,然後設定在7/26下單。 # ```python # 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。 # In[4]: 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) # In[5]: # 7/27 照樣買入了1101。 transactions # #### 範例2 # 但如果 **on_error 設定成 'fail'**,整個程式會被中止,然後顯示一樣的錯誤訊息。 # # ```python # set_do_not_order_list(restricted_list = [symbol('1101')], on_error='fail') # ``` # In[6]: 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) # # # set_long_only # 預先設定投資組合**不能**持有任何**短部位(short positions)** # # [Return to Menu](#menu) # # ### zipline.api.set_long_only(on_error='fail') # # on_error (*str, optional*): # - on_error有兩種選項:'fail' 和 'log'。前者直接中斷程式並顯示錯誤訊息,後者會照樣執行但記錄錯誤。預設為'fail'。 # - 若要使用on_error = 'log',請同時進行以下設定,才能顯示 log: # # ```python # 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 股票。 # # ```python # 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) # ... # ``` # # - 當帳上持有 1000 股時賣出 500 股(7/26)是不會有問題的,但是如果再賣出 800 股(7/30),就會變成 - 300 股(short position),所以就會跳出了警示: # ```python # 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',股票依然成功賣出。 # In[7]: 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) # In[8]: # 7/31持有股數(amount)變成-300 positions[0:6] # In[9]: # 持有short_position的股票個數 performance.shorts_count # # # set_max_leverage # 設定投資組合的槓桿限制 # # [Return to Menu](#menu) # # ### zipline.api.set_max_leverage(max_leverage) # # max_leverage (*float*) # - 投資組合的最高槓桿,必須 > 0。 # - 這邊的槓桿指的是`gross leverage`。 # - 計算方式:`(long_exposure - short_exposure)/(long_exposure + short_exposure + ending_cash)`。 # - ending_cash:當日結束時帳上持有現金。計算方式:starting_cash - capital_used。 # - long_exposure:sum ( 持有股數 * 收盤價 ) = sum ( amount * last_sale_price ),where amount > 0。 # - short_exposure:sum ( 持有股數 * 收盤價 ) = sum ( amount * last_sale_price ),where amount < 0。所以short_exposure <= 0。 # - 參考連結:[lecture/TSMC buy and hold strategy.ipynb](https://github.com/tejtw/TQuant-Lab/blob/main/lecture/TSMC%20buy%20and%20hold%20strategy.ipynb) # #
# # #### 補充說明 # - 當投資組合的槓桿**超過**`max_leverage`時,程式會 **fail**。 # - 無法選擇以'log'方式呈現。 # # #### 範例 # - 在以下這個範例,我們設定`set_max_leverage(3.0)`。 # - 在7/24先 long 價值一百萬的 2330 股票,short 價值一百萬的 2317 股票。 # ```python # def handle_data(context, data): # if context.i == 0: # 7/24 # order_value(symbol('2330'), 1e6) # order_value(symbol('2317'), -1e6) # ... # ``` # - 在7/26 long 價值五十萬的 2454 股票。 # ```python # if context.i == 2: # 7/26 # order_value(symbol('2454'), 5e5) # ``` # In[10]: 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) # `gross_leverage` 可以直接從`run_algorithm`的結果進行觀察。(`performance['gross_leverage']`) # - 計算方式如下,以7/25為例: # # ``` # 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`,代表當日結束時帳上持有現金。 # - 以上欄位定義的參考連結:[lecture/TSMC buy and hold strategy.ipynb](https://github.com/tejtw/TQuant-Lab/blob/main/lecture/TSMC%20buy%20and%20hold%20strategy.ipynb) # In[11]: performance.loc['2018-07-25':'2018-07-27',['gross_leverage','portfolio_value','ending_cash']] # In[12]: positions[0:2] # - 假設我們設定`set_max_leverage(2.0)`,在7/26時因為股價波動,leverage 超過 2.0,程式就會被終止,跳出錯誤訊息。 # - 那如果`set_max_leverage(2.4)`,且在7/27 long價值 50 萬的 2454,leverage 會達到 2.48,程式也一樣會被終止,跳出錯誤訊息。 # # # set_max_order_count # 限制一天內能夠下幾張 order # # [Return to Menu](#menu) # # ### zipline.api.set_max_order_count(max_count, on_error='fail') # - max_count (*int*):一天內最多的下單數。 # # - on_error (*str, optional*): # - on_error有兩種選項:'fail' 和 'log'。前者直接中斷程式並顯示錯誤訊息,後者會照樣執行但記錄錯誤。預設為'fail'。 # - 若要使用on_error = 'log',請同時進行以下設定,才能顯示 log: # # ```python # 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%。 # - 下單: # - 7/24 long 2330 及 2357 兩檔股票。 # - 7/25 long 2454、2317 及 1101 三檔股票。 # # ```python # 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) # ... # ``` # # #### 補充說明 # - 訂單數的計算方式:若要計算2018/7/25的訂單數,則從`performance`的`orders`取出`created = '2018-07-25'`的訂單進行計算。 # - 也就是說,如果因**滑價**或**條件單**的關係,訂單被拆成好幾天成交,則該張訂單只有在**成立那天**才會納入計算。 # # [Return to Menu](#menu) # In[13]: 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) # #### 範例說明 # - 在7/24下單大量的 2330 和 2357,因為`VolumeShareSlippage`的限制,所以會拆成數天成交。 # - 這個會導致7/25不只有當天下的三張訂單,還有前一天 2330 和 2357 的單,共五張單。 # - 但是程式沒有跳出任何錯誤或警告,因為第一天下兩張單,第二天下三張單,程式判定都沒有超過 3,所以沒有問題。 # In[14]: orders.loc['2018-07-25'] # 如果設定`set_max_order_count(max_count=2, on_error='log')`,則在7/25會出現以下訊息: # # ```python # 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}) # ``` # # # set_max_order_size # 這個函數限制特定股票(或全部)的**單次下單股數和金額** # # [Return to Menu](#menu) # # ### zipline.api.set_max_order_size(asset=None, max_shares=None, max_notional=None, on_error='fail') # - asset (*Asset, optional*) # - 必須是`Asset`物件(`zipline.assets.Asset`,例如:Equity(0 [1101]),透過`symbol("1101")`可將 symbol 轉成`Asset`物件) # - 若**不為 None** 會對**指定股票**進行限制。 # - 若設定為 **None** 則會讓**所有股票**皆適用這個限制條件。 # - max_shares (*int, optional*)-最大單次下單股數。 # - max_notional (*float, optional*)-最大單次下單金額。 # - on_error (*str, optional*): # - on_error有兩種選項:'fail' 和 'log'。前者直接中斷程式並顯示錯誤訊息,後者會照樣執行但記錄錯誤。預設為'fail'。 # - 若要使用on_error = 'log',請同時進行以下設定,才能顯示log: # # ```python # 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`的限制,細節在下面的範例說明。 # # #### 範例 # - 在以下這個範例,我們限制 1101 的 max_shares = 1000,且限制 2330 的 max_shares = 2000 、max_notional = 481000。 # ```python # 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') # ... # ``` # - 在7/25 long 1000股的 1101 股票及 2000 股的 2330 股票。 # ```python # def handle_data(context, data): # if context.i == 1: # 2018-07-25 # order(symbol('1101'), 1000) # order(symbol('2330'), 2000) # ... # ``` # - 在8/16 long 2005 股的 2330 股票。 # ```python # if context.i == 17: # 2018-08-16 # order(symbol('2330'), 2005) # ``` # In[15]: 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) # #### 觀察7/25下的 1101 # - 在7/25下單 1000 股。 # - 但7/26遇到除權事件,調整後實際上買了1100股 > 限制的 1000 股,但是因為下單時是 1000 股,所以沒有問題。 # In[16]: orders.query('symbol == "1101"') # #### 觀察7/25下的 2330 # - 在7/25下單 2000 股的 2330 ,當下的 notional = 2000 * 240.5(當天收盤)= 481000,符合設定的兩個限制。 # - 7/26成交時是以 241 元成交,當下的 notional = 2000 * 241 = 482000,超出原本設定的 `max_notional`,但並不會跳錯訊息。 # In[17]: closing_price.query('(coid == "2330") & (mdate in ["2018-07-25","2018-07-26"])') # In[18]: transactions.loc['2018-07-26'] # #### 觀察8/16下的 2330 # 在8/16下 2005 股的 2330,雖然當天 notional = 239 * 2005 = 479195 沒有超過預先設定的 481000,但已經超過 2000 股的限制。因為on_error = 'log',程式繼續運作,照樣成交,但是跳出錯誤訊息: # # ```python # 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}) # ``` # In[19]: closing_price.query('(coid == "2330") & (mdate in ["2018-08-16","2018-08-17"])') # In[20]: transactions.loc['2018-08-17'] # # # set_max_position_size # 限制特定股票(或全部)在帳上的股數及市值 # # [Return to Menu](#menu) # # ### zipline.api.set_max_position_size(asset=None, max_shares=None, max_notional=None, on_error='fail') # - asset (*Asset, optional*) # - 必須是`Asset`物件(`zipline.assets.Asset`,例如:Equity(0 [1101])) # - 若**不為 None** 會對**指定股票**進行限制。 # - 若設定為 **None** 則會讓**所有股票**皆適用這個限制條件。 # - max_shares (*int, optional*)-最大**持股**股數。 # - max_notional (*float, optional*)-最大**持股**市值。 # - on_error (*str, optional*): # - on_error有兩種選項:'fail' 和 'log'。前者直接中斷程式並顯示錯誤訊息,後者會照樣執行但記錄錯誤。預設為'fail'。 # - 若要使用on_error = 'log',請同時進行以下設定,才能顯示log: # # ```python # 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()`非常類似,差別在於它是限制某支(或全部)股票在**帳上**的股數及市值,而非針對**單筆訂單**。 # - 這函數**只會在下單當下進行檢查**並判斷帳上的部位(position)會不會超過限制,並不是一直追蹤帳上的部位。 # - 若是我們同一檔股票下兩張訂單,則每張訂單將會**個別判定**。 # - 下面用一些比較特殊的例子,同時應用`max_order_size`和`max_position_size`來解釋運作規則。 # # #### 範例 # - 在範例中,我們做以下限制:  # - 限制 2330 單筆訂單的 max_shares = 2000、max_notional = 481000。 # - 限制 1101 持股部位的 max_shares = 1050。 # - 限制 2330 持股部位的 max_shares = 2000、max_notional = 600000。 # # ```python # 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') # ... # ``` # - 下單設定: # - 在7/24 long 1000股的 1101 股票。 # ```python # def handle_data(context, data): # if context.i ==0: # 2018-07-24 # order(symbol('1101'), 1000) # ``` # - 在7/25 下兩張訂單,分別 long 2000 股及 1000 股的 2330 股票。 # ```python # if context.i == 1: # 2018-07-25 # order(symbol('2330'), 2000) # order(symbol('2330'), 1000) # ``` # - 在7/31 long 500股的 2330 股票。 # ```python # if context.i == 5: # 2018-07-31 # order(symbol('2330'), 500) # ``` # In[21]: 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) # #### 觀察7/24下的訂單 # - 雖然7/24下的 1000 股 1101 在7/26除權之後變成 1100 股,超過限制的 1050 股,但沒有錯誤訊息(`MaxPositionSize`)。 # - 此外**因為在下單時,系統判定買 1000 股會讓手上 position 從 0 變成 1000 股,沒有超過 1050**,所以沒有錯誤訊息。 # - 同樣道理,如果單子因為除權事件造成成交股數和下單時股數不同,還是以**下單時股數**為主。 # In[22]: orders[0:2] # In[23]: positions[0:2] # #### 觀察7/25下的訂單 # - 在7/25連續下了兩單,分別是 2000 股和 1000 股的 2330,雖然總和是 3000 股,超過限制的 2000 股,但也沒有錯誤訊息(`MaxOrderSize`)。**因為兩單都沒有超過max_order_size 的 2000 股限制**。 # - 而因為這兩張單子隔天(7/26)才會成交,下單時 position = 0,`max_position_size`認為其中一單是讓 position 從 0 變成 2000,另一單是讓 position 從 0 變成 1000,都沒有超過總股數 2000 的限制(`max_notional`部分也是同樣概念),所以也沒有錯誤訊息(`MaxPositionSize`)。 # - 接下來幾天股價上升,notional也早就超過`max_notional(600000)`,但也沒有錯誤訊息,因為並不是一直追蹤帳上的部位。 # #### 觀察7/31下的訂單 # - 7/31下單 500 股 2330 時跳出以下錯誤訊息,因為帳上 3000 股,再加 500,就會超過2000(`max_notional`部分也是同樣概念)。 # - 而且因為**超過了兩個限制**,同一筆交易跳出**兩行**錯誤訊息(但因為我們用on_error = 'log',所有訂單還是成交)。 # # ```python # 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}) # ``` # In[24]: orders.query('symbol=="2330"') # In[25]: positions['mv'] = positions['amount'] * positions['last_sale_price'] positions.loc[positions.symbol=='2330'] # [Return to Menu](#menu)