{ "cells": [ { "cell_type": "markdown", "id": "2b076a6d", "metadata": {}, "source": [ "\n", "# 手續費模型(Commission Models)" ] }, { "cell_type": "markdown", "id": "34c071e5", "metadata": {}, "source": [ "- 交易成本(transaction costs)被廣泛認為是影響投資績效的重要因素。它們不僅影響投資績效,還影響了將資產轉換成現金的難易度。\n", "\n", "- 在真實世界的交易中存在許多種類的交易成本,其中一種是**直接成本(direct cost)**。直接成本包含了**手續費(commission)、證交稅**等等,這些成本是在交易前已經**提前知道的成本**。此類成本皆可以使用 Zipline 中的 **commission models** 進行模擬。這也是回測(backtesting)的一大目的,考量投資策略在真實世界運行的可能性。" ] }, { "cell_type": "markdown", "id": "e98b4f1a", "metadata": {}, "source": [ "## zipline.api.set_commission(self, equities=None, futures=None)\n", "\n", "設定回測時所使用的手續費模型。\n", "\n", "\n", "> ### Parameters:\n", "> - equities *(EquityCommissionModel, optional)* -用於交易股票的手續費模型。\n", "> - EquityCommissionModel:`zipline.finance.commission`\n", "> - futures *(FutureCommissionModel, optional)* -用於交易期貨的手續費模型。(目前不支援)\n", "> - FutureCommissionModel:`zipline.finance.commission`\n", "> \n", "> ### Raises:\n", "> **SetCommissionPostInit**-`set_commission` **只能**在 `initialize` 階段使用。\n", "> \n", "> ### Notes:\n", "> - `set_commission` 只能一次用**一種**方法。\n", "> - 手續費計算時,**價格以成交日收盤價為準,數量也以成交時為準**。也就是說,如果因為股數變動造成 amount 有任何變化,計算上都是用成交時新的 amount。\n", "> \n", "> ### See also:\n", "> - `zipline.finance.commission.PerShare`\n", "> - `zipline.finance.commission.PerTrade`\n", "> - `zipline.finance.commission.PerDollar`\n", "> - `zipline.finance.commission.Custom_TW_Commission`\n", "> \n", "> ### Examples:\n", "> ```python\n", "> from zipline.api import set_commission\n", "> from zipline.finance import commission\n", "> \n", "> def initialize(context):\n", "> set_commission(commission.<其中一種commission models>)\n", "> ```" ] }, { "cell_type": "markdown", "id": "e8c80c22", "metadata": {}, "source": [ "## class zipline.finance.commission.CommissionModel\n", "> 手續費模型的抽象基類(Abstract Base Class)。\n", "> \n", "> 手續費模型是用來計算每筆交易需繳交多少手續費。Zipline 目前有五種模型:\n", "> \n", "> 1. `PerDollar`:按照交易金額抽成計算。\n", "> 2. `PerTrade`:一筆交易收取一筆固定費用。\n", "> 3. `PerShare`:按照下單的股數計算費用,同時還可以設定一個最低費用。\n", "> 4. `Custom_TW_Commission`:台灣專用的手續費模型。\n", "> 5. `NoCommission`:不收任何手續費。\n", "\n", "## class zipline.finance.commission.PerDollar(cost=0.0015)\n", "> - 按照交易金額抽成計算。\n", "> - 僅適用於 equities。\n", "> \n", "> ### Parameters:\n", "> - cost *(float, optional)*-每交易一元的股票所需支付的固定費用。預設為 0.0015 元。\n", "\n", "## class zipline.finance.commission.PerTrade(cost=0.0)\n", "> - 一筆交易收取一筆固定費用。\n", "> - 適用於 equities 與 futures。\n", "> \n", "> ### Parameters:\n", "> - cost *(float, optional)*-每進行一筆交易所需支付的固定費用。預設為 0 元。\n", "\n", "## class zipline.finance.commission.PerShare(cost=0.001, min_trade_cost=0.0)\n", "> - 按照下單的股數計算費用,同時還可以設定一個最低費用。\n", "> - 僅適用於 equities。\n", "> - 為**預設模型**。\n", "> \n", "> ### Parameters:\n", "> - cost *(float, optional)*-每交易一股的股票所需支付的固定費用。預設為 0.001 元。\n", "> - min_trade_cost *(float, optional)*-最低費用,預設為無最低費用。\n", "\n", "## class zipline.finance.commission.Custom_TW_Commission(min_trade_cost=20, discount=1.0, tax = 0.003)\n", "> - 台灣股票適用的手續費模型,考量券商手續費(費率:0.001425)及證交稅,同時還可以設定一個最低費用。\n", "> - 台灣交易股票時的情況的情況:在台灣交易股票時,主要有兩個直接成本:**手續費**及**證交稅**。`Custom_TW_Commission` 可模擬這兩項成本。\n", "> - 手續費\n", "> - **買進**或**賣出**時皆須繳交。\n", "> - 計算方式為:成交價 × 成交股數 × 0.1425 % × 折扣(折扣預設是 1,沒有折扣)。\n", "> - 手續費有**最低價格**(預設是 20 元)。\n", "> \n", "> - 證交稅\n", "> - **賣出**時才要繳交。\n", "> - 計算方式為:成交價 × 成交股數 × 證交稅率(證交稅率預設是 0.3%)。\n", "> - 僅適用於 equities。\n", "> \n", "> ### Parameters:\n", "> - min_trade_cost *(float, optional)*-最低費用。預設為 20 元。\n", "> - discount *(float, optional)*-券商手續費折扣比率。預設為 1,代表沒有折扣。\n", "> - tax *(float, optional)*-證交稅率,預設為 0.003。\n", "> \n", "> ### Notes:\n", "> - 手續費計算時,**價格以成交日收盤價為準,數量也以成交時為準**,也就是說,如果因為股數變動造成 amount 有任何變化,計算上都是用成交時新的 amount。\n", "\n", "## class zipline.finance.commission.NoCommission()\n", "> - 不收任何手續費,此模型主要用來測試。\n", "> - 適用於 equities 與 futures。" ] }, { "cell_type": "markdown", "id": "530a1d1a", "metadata": {}, "source": [ "### Examples-CommissionModel\n", "以下範例比較各種模型計算方法。" ] }, { "cell_type": "markdown", "id": "2ae06196", "metadata": {}, "source": [ "#### Import settings" ] }, { "cell_type": "code", "execution_count": 1, "id": "ee5cae9e", "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "Merging daily equity files:\n" ] }, { "name": "stderr", "output_type": "stream", "text": [ "[2023-11-27 03:31:44.753277] INFO: zipline.data.bundles.core: Ingesting tquant.\n" ] } ], "source": [ "import pandas as pd\n", "import datetime\n", "import tejapi\n", "import os\n", "import warnings\n", "from logbook import Logger, StderrHandler, INFO\n", "warnings.filterwarnings('ignore')\n", "\n", "# set log\n", "log_handler = StderrHandler(format_string='[{record.time:%Y-%m-%d %H:%M:%S.%f}]: ' +\n", " '{record.level_name}: {record.func_name}: {record.message}',\n", " level=INFO)\n", "log_handler.push_application()\n", "log = Logger('CommissionModel')\n", "\n", "# tej_key\n", "os.environ['TEJAPI_KEY'] = \"your key\" \n", "os.environ['TEJAPI_BASE'] = \"https://api.tej.com.tw\"\n", "\n", "# date\n", "# set date\n", "start='2022-12-01'\n", "end='2022-12-31'\n", "os.environ['mdate'] = '20221201 20221231'\n", "\n", "tz = 'UTC'\n", "start_dt, end_dt = pd.Timestamp(start, tz = tz), pd.Timestamp(end, tz = tz)\n", "\n", "# calendar\n", "calendar_name='TEJ'\n", "\n", "# bundle_name\n", "bundle_name = 'tquant'\n", "\n", "# ticker\n", "os.environ['ticker'] = '1216 IR0001'\n", "\n", "# ingest\n", "!zipline ingest -b tquant" ] }, { "cell_type": "code", "execution_count": 2, "id": "b1e1a97c", "metadata": {}, "outputs": [], "source": [ "from zipline.finance import commission, slippage\n", "from zipline.api import *\n", "\n", "from zipline import run_algorithm\n", "from zipline.utils.calendar_utils import get_calendar\n", "\n", "from zipline.utils.run_algo import (get_transaction_detail,\n", " get_record_vars)" ] }, { "cell_type": "markdown", "id": "304f53e5", "metadata": {}, "source": [ "#### 比較各種模型計算方法\n", "- 注意四個 `initialize` 函數的差別。\n", " - `initialize_perdollar`:commission.PerTrade(cost=0.5)\n", " - `initialize_pertrade`:commission.PerTrade(cost=0.5)\n", " - `initialize_pershare`:commission.PerShare(cost=0.001, min_trade_cost=5.0)\n", " - `initialize_Custom`:commission.Custom_TW_Commission(min_trade_cost=20, discount = 1.0, tax = 0.003)\n", "- 這個範例將滑價模型設定為:`slippage.FixedSlippage(spread=0.00)`。其中,spread 設定為 0,這樣會比較好觀察結果,因為滑價會導致成交價格改變。若有滑價則會使用考慮滑價後的價格計算手續費。" ] }, { "cell_type": "code", "execution_count": 3, "id": "bd78b018", "metadata": {}, "outputs": [], "source": [ "def initialize_perdollar(context):\n", " context.i = 0\n", " context.tickers = ['1216']\n", " context.asset = [symbol(ticker) for ticker in context.tickers] \n", " set_slippage(slippage.FixedSlippage(spread=0.00))\n", " \n", " # set_commission\n", " set_commission(equities=commission.PerDollar(cost=0.001))\n", " \n", " set_benchmark(symbol('IR0001'))\n", " \n", "def initialize_pertrade(context):\n", " context.i = 0\n", " context.tickers = ['1216']\n", " context.asset = [symbol(ticker) for ticker in context.tickers] \n", " set_slippage(slippage.FixedSlippage(spread=0.00))\n", " \n", " # set_commission\n", " set_commission(equities=commission.PerTrade(cost=0.5))\n", " \n", " set_benchmark(symbol('IR0001'))\n", " \n", "def initialize_pershare(context):\n", " context.i = 0\n", " context.tickers = ['1216']\n", " context.asset = [symbol(ticker) for ticker in context.tickers] \n", " set_slippage(slippage.FixedSlippage(spread=0.00))\n", " \n", " # set_commission\n", " set_commission(equities=commission.PerShare(cost=0.001, min_trade_cost=5.0))\n", " \n", " set_benchmark(symbol('IR0001'))\n", " \n", "def initialize_Custom(context):\n", " context.i = 0\n", " context.tickers = ['1216']\n", " context.asset = [symbol(ticker) for ticker in context.tickers] \n", " set_slippage(slippage.FixedSlippage(spread=0.00))\n", " \n", " # set_commission\n", " set_commission(equities=commission.Custom_TW_Commission(min_trade_cost=20,\n", " discount = 1.0,\n", " tax = 0.003))\n", " \n", " set_benchmark(symbol('IR0001'))\n", " \n", "def handle_data(context, data):\n", "\n", " if context.i == 0:\n", " for asset in context.asset:\n", " order_target(asset, 1000)\n", " if context.i == 2:\n", " for asset in context.asset:\n", " order_target(asset, 0)\n", " \n", " record(close=data.current(context.asset, 'close'))\n", " context.i += 1\n", "\n", "capital_base = 1e5" ] }, { "cell_type": "code", "execution_count": 4, "id": "16f41ed9", "metadata": {}, "outputs": [], "source": [ "closing_price = tejapi.get('TWN/APIPRCD',\n", " coid=['1216'],\n", " opts={'columns':['mdate','coid','close_d']},\n", " mdate={'gte':start_dt,'lte':end_dt },\n", " paginate=True)\n", "\n", "perdollar = run_algorithm(start=start_dt,\n", " end=end_dt,\n", " initialize=initialize_perdollar,\n", " handle_data=handle_data,\n", " capital_base=capital_base,\n", " trading_calendar=get_calendar(calendar_name),\n", " bundle=bundle_name)\n", "\n", "pertrade = run_algorithm(start=start_dt,\n", " end=end_dt,\n", " initialize=initialize_pertrade,\n", " handle_data=handle_data,\n", " capital_base=capital_base,\n", " trading_calendar=get_calendar(calendar_name),\n", " bundle=bundle_name)\n", "\n", "pershare = run_algorithm(start=start_dt,\n", " end=end_dt,\n", " initialize=initialize_pershare,\n", " handle_data=handle_data,\n", " capital_base=capital_base,\n", " trading_calendar=get_calendar(calendar_name),\n", " bundle=bundle_name)\n", "\n", "Custom = run_algorithm(start=start_dt,\n", " end=end_dt,\n", " initialize=initialize_Custom,\n", " handle_data=handle_data,\n", " capital_base=capital_base,\n", " trading_calendar=get_calendar(calendar_name),\n", " bundle=bundle_name)" ] }, { "cell_type": "markdown", "id": "6fe6f9b2", "metadata": {}, "source": [ "#### Explanations\n", "12/1" ] }, { "cell_type": "code", "execution_count": 5, "id": "54d27163", "metadata": { "scrolled": true }, "outputs": [ { "data": { "text/html": [ "
\n", "\n", "\n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", "
mdatecoidclose_d
None
02022-12-01121665.9
12022-12-02121665.0
\n", "
" ], "text/plain": [ " mdate coid close_d\n", "None \n", "0 2022-12-01 1216 65.9\n", "1 2022-12-02 1216 65.0" ] }, "execution_count": 5, "metadata": {}, "output_type": "execute_result" } ], "source": [ "# 收盤價\n", "closing_price[0:2]" ] }, { "cell_type": "markdown", "id": "f489bc60", "metadata": {}, "source": [ "***PerDollar***\n", "\n", "同樣在 12/1 下單一張統一(1216)股票,如果用 PerDollar 算法,費用就是下一個交易日 12/2 的收盤價 65 * 1000 股 * 0.001 = 65。" ] }, { "cell_type": "code", "execution_count": 6, "id": "14274138", "metadata": {}, "outputs": [ { "data": { "text/plain": [ "[{'id': '363000dc2c1e4e5eae7ed2ec4b85ee43',\n", " 'dt': Timestamp('2022-12-02 13:30:00+0800', tz='Asia/Taipei'),\n", " 'reason': None,\n", " 'created': Timestamp('2022-12-01 13:30:00+0800', tz='Asia/Taipei'),\n", " 'amount': 1000,\n", " 'filled': 1000,\n", " 'commission': 65.0,\n", " 'stop': None,\n", " 'limit': None,\n", " 'stop_reached': False,\n", " 'limit_reached': False,\n", " 'sid': Equity(0 [1216]),\n", " 'status': }]" ] }, "execution_count": 6, "metadata": {}, "output_type": "execute_result" } ], "source": [ "# PerDollar算法:費用65元\n", "perdollar['orders'][1]" ] }, { "cell_type": "markdown", "id": "0f0ca6a7", "metadata": {}, "source": [ "***PerTrade***\n", "\n", "如果是 PerOrder,就固定是預先設定好的 0.5 元。" ] }, { "cell_type": "code", "execution_count": 7, "id": "0333a12d", "metadata": { "scrolled": true }, "outputs": [ { "data": { "text/plain": [ "[{'id': '61a8b52d81de4e4b98d295823efb2464',\n", " 'dt': Timestamp('2022-12-02 13:30:00+0800', tz='Asia/Taipei'),\n", " 'reason': None,\n", " 'created': Timestamp('2022-12-01 13:30:00+0800', tz='Asia/Taipei'),\n", " 'amount': 1000,\n", " 'filled': 1000,\n", " 'commission': 0.5,\n", " 'stop': None,\n", " 'limit': None,\n", " 'stop_reached': False,\n", " 'limit_reached': False,\n", " 'sid': Equity(0 [1216]),\n", " 'status': }]" ] }, "execution_count": 7, "metadata": {}, "output_type": "execute_result" } ], "source": [ "# pertrade算法:費用0.5元\n", "pertrade['orders'][1]" ] }, { "cell_type": "markdown", "id": "56fcb7d1", "metadata": {}, "source": [ "***PerShare***\n", "\n", "PerShare 雖然設定一股抽 0.001,但是因為 1000 股 * 0.001 = 1 小於最低費用 min_trade_cost = 5,所以費用是 5。" ] }, { "cell_type": "code", "execution_count": 8, "id": "35e9a418", "metadata": { "scrolled": true }, "outputs": [ { "data": { "text/plain": [ "[{'id': '4fba6e4d88a34bc6b6072d2155242915',\n", " 'dt': Timestamp('2022-12-02 13:30:00+0800', tz='Asia/Taipei'),\n", " 'reason': None,\n", " 'created': Timestamp('2022-12-01 13:30:00+0800', tz='Asia/Taipei'),\n", " 'amount': 1000,\n", " 'filled': 1000,\n", " 'commission': 5.0,\n", " 'stop': None,\n", " 'limit': None,\n", " 'stop_reached': False,\n", " 'limit_reached': False,\n", " 'sid': Equity(0 [1216]),\n", " 'status': }]" ] }, "execution_count": 8, "metadata": {}, "output_type": "execute_result" } ], "source": [ "# pershare算法:費用5元\n", "pershare['orders'][1]" ] }, { "cell_type": "markdown", "id": "41a13fc3", "metadata": {}, "source": [ "***Custom_TW_Commission***" ] }, { "cell_type": "code", "execution_count": 9, "id": "e8f58bf9", "metadata": { "scrolled": true }, "outputs": [ { "data": { "text/plain": [ "[{'id': 'a05f2ae2969c42bc809b112195eb3dbb',\n", " 'dt': Timestamp('2022-12-02 13:30:00+0800', tz='Asia/Taipei'),\n", " 'reason': None,\n", " 'created': Timestamp('2022-12-01 13:30:00+0800', tz='Asia/Taipei'),\n", " 'amount': 1000,\n", " 'filled': 1000,\n", " 'commission': 93,\n", " 'stop': None,\n", " 'limit': None,\n", " 'stop_reached': False,\n", " 'limit_reached': False,\n", " 'sid': Equity(0 [1216]),\n", " 'status': }]" ] }, "execution_count": 9, "metadata": {}, "output_type": "execute_result" } ], "source": [ "# 買進:65 * 1000 股 * 0.001425 = 93\n", "Custom['orders'][1]" ] }, { "cell_type": "code", "execution_count": 10, "id": "d94d832d", "metadata": { "scrolled": true }, "outputs": [ { "data": { "text/plain": [ "[{'id': '5b0c6088e7a74a43b40d95ab4b7be5d4',\n", " 'dt': Timestamp('2022-12-06 13:30:00+0800', tz='Asia/Taipei'),\n", " 'reason': None,\n", " 'created': Timestamp('2022-12-05 13:30:00+0800', tz='Asia/Taipei'),\n", " 'amount': -1000,\n", " 'filled': -1000,\n", " 'commission': 287,\n", " 'stop': None,\n", " 'limit': None,\n", " 'stop_reached': False,\n", " 'limit_reached': False,\n", " 'sid': Equity(0 [1216]),\n", " 'status': }]" ] }, "execution_count": 10, "metadata": {}, "output_type": "execute_result" } ], "source": [ "# 賣出:64.6 * 1000 股 * 0.001425 + 64.6 * 1000 股 * 0.003 = 93 + 194 = 287\n", "Custom['orders'][3]" ] }, { "cell_type": "markdown", "id": "9737d8f1", "metadata": {}, "source": [ "[Go Top](#top)" ] }, { "cell_type": "code", "execution_count": null, "id": "1eee486b-46de-41f0-a2ff-5a2630f76431", "metadata": {}, "outputs": [], "source": [] } ], "metadata": { "kernelspec": { "display_name": "Python 3 (ipykernel)", "language": "python", "name": "python3" }, "language_info": { "codemirror_mode": { "name": "ipython", "version": 3 }, "file_extension": ".py", "mimetype": "text/x-python", "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", "version": "3.11.5" } }, "nbformat": 4, "nbformat_minor": 5 }