Skip to content

Instantly share code, notes, and snippets.

Show Gist options
  • Save tej87681088/e8b1b05b34892c8fd290cbc81cd16edb to your computer and use it in GitHub Desktop.
Save tej87681088/e8b1b05b34892c8fd290cbc81cd16edb to your computer and use it in GitHub Desktop.
SuperTrend_Shioaji
Display the source blob
Display the rendered blob
Raw
{
"cells": [
{
"cell_type": "markdown",
"id": "93cc65cb-d9ad-4ae2-944d-d9ce3e3b1a3d",
"metadata": {},
"source": [
"# TQuant Lab 超級趨勢策略 - 永豐 Shioaji"
]
},
{
"cell_type": "markdown",
"id": "aea787ac-148e-4a3b-b65e-b2a0db85d898",
"metadata": {},
"source": [
"## 超級趨勢策略簡介\n",
"超級趨勢策略以超級趨勢指標以及 ADX 指標構成。超級趨勢指標擁有上軌和下軌,當股價突破上軌,代表價格突破壓力,即將形成上漲趨勢;反之,當股價跌破下軌,代表價格跌破支撐,即將形成下跌趨勢。另一方面,ADX 指標介於 0~100,數值越大代表趨勢越強,幫助我們確立上漲趨勢與下跌趨勢的形成。 \n",
" \n",
"超級趨勢策略的進出場規則如下:\n",
" - Long Entry: 收盤價突破上軌,代表價格突破壓力,即將形成上漲趨勢,買入該股票。\n",
" - Short Entry: 收盤價跌破下軌,加上 ADX > 50,代表價格跌破支撐,形成下跌趨勢,將持股賣出。\n",
"\n",
"詳細的策略說明與 TQuant Lab 的回測流程請參考:[TQuant Lab 超級趨勢策略,低買高賣賺取波段價差](https://www.tejwin.com/insight/tquant-lab-%E8%B6%85%E7%B4%9A%E8%B6%A8%E5%8B%A2%E7%AD%96%E7%95%A5/)"
]
},
{
"cell_type": "markdown",
"id": "c9b23d34-4ab4-4faa-9cd1-cc4f063fc336",
"metadata": {},
"source": [
"## 利用 TQuant Lab 取得每日買賣標的"
]
},
{
"cell_type": "code",
"execution_count": 1,
"id": "b621e62e-7e39-4a66-a49a-255cbc8229aa",
"metadata": {},
"outputs": [],
"source": [
"import os\n",
"import numpy as np\n",
"import pandas as pd\n",
"\n",
"# tej_key\n",
"tej_key = 'your key'\n",
"api_base = 'https://api.tej.com.tw'\n",
"\n",
"os.environ['TEJAPI_KEY'] = tej_key \n",
"os.environ['TEJAPI_BASE'] = api_base"
]
},
{
"cell_type": "code",
"execution_count": 2,
"id": "2cb7b194-2eaf-4fb0-9e3a-45ef8fb32981",
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"Currently used TEJ API key call quota 230/100000 (0.23%)\n",
"Currently used TEJ API key data quota 787917/10000000 (7.88%)\n"
]
}
],
"source": [
"from zipline.utils.calendar_utils import get_calendar\n",
"from zipline.sources.TEJ_Api_Data import get_universe\n",
"\n",
"cal = get_calendar('TEJ').all_sessions\n",
"last_date = cal[-2] # 前一個交易日日期\n",
"\n",
"pool = get_universe(start = last_date, \n",
" end = last_date,\n",
" mkt_bd_e = 'TSE', # Listed stock in Taiwan\n",
" stktp_e = 'Common Stock', \n",
" main_ind_c = 'M2300 電子工業' # Electronics Industry\n",
" )"
]
},
{
"cell_type": "code",
"execution_count": 3,
"id": "d514b758-e8d5-413e-b81e-35c9e3678d98",
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
"407"
]
},
"execution_count": 3,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"len(pool)"
]
},
{
"cell_type": "code",
"execution_count": 4,
"id": "1a05c4bb-bb1a-424a-a953-293ad803830b",
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"Currently used TEJ API key call quota 260/100000 (0.26%)\n",
"Currently used TEJ API key data quota 900607/10000000 (9.01%)\n"
]
},
{
"data": {
"text/html": [
"<div>\n",
"<style scoped>\n",
" .dataframe tbody tr th:only-of-type {\n",
" vertical-align: middle;\n",
" }\n",
"\n",
" .dataframe tbody tr th {\n",
" vertical-align: top;\n",
" }\n",
"\n",
" .dataframe thead th {\n",
" text-align: right;\n",
" }\n",
"</style>\n",
"<table border=\"1\" class=\"dataframe\">\n",
" <thead>\n",
" <tr style=\"text-align: right;\">\n",
" <th></th>\n",
" <th>coid</th>\n",
" <th>mdate</th>\n",
" <th>Market_Cap_Dollars</th>\n",
" </tr>\n",
" </thead>\n",
" <tbody>\n",
" <tr>\n",
" <th>0</th>\n",
" <td>1471</td>\n",
" <td>2024-10-25</td>\n",
" <td>1.962910e+09</td>\n",
" </tr>\n",
" <tr>\n",
" <th>1</th>\n",
" <td>1582</td>\n",
" <td>2024-10-25</td>\n",
" <td>1.514820e+10</td>\n",
" </tr>\n",
" <tr>\n",
" <th>2</th>\n",
" <td>2059</td>\n",
" <td>2024-10-25</td>\n",
" <td>1.195978e+11</td>\n",
" </tr>\n",
" <tr>\n",
" <th>3</th>\n",
" <td>2301</td>\n",
" <td>2024-10-25</td>\n",
" <td>2.441140e+11</td>\n",
" </tr>\n",
" <tr>\n",
" <th>4</th>\n",
" <td>2302</td>\n",
" <td>2024-10-25</td>\n",
" <td>3.201330e+09</td>\n",
" </tr>\n",
" <tr>\n",
" <th>...</th>\n",
" <td>...</td>\n",
" <td>...</td>\n",
" <td>...</td>\n",
" </tr>\n",
" <tr>\n",
" <th>402</th>\n",
" <td>8215</td>\n",
" <td>2024-10-25</td>\n",
" <td>1.032572e+10</td>\n",
" </tr>\n",
" <tr>\n",
" <th>403</th>\n",
" <td>8249</td>\n",
" <td>2024-10-25</td>\n",
" <td>9.068506e+09</td>\n",
" </tr>\n",
" <tr>\n",
" <th>404</th>\n",
" <td>8261</td>\n",
" <td>2024-10-25</td>\n",
" <td>9.226725e+09</td>\n",
" </tr>\n",
" <tr>\n",
" <th>405</th>\n",
" <td>8271</td>\n",
" <td>2024-10-25</td>\n",
" <td>6.603811e+09</td>\n",
" </tr>\n",
" <tr>\n",
" <th>406</th>\n",
" <td>9912</td>\n",
" <td>2024-10-25</td>\n",
" <td>7.050700e+08</td>\n",
" </tr>\n",
" </tbody>\n",
"</table>\n",
"<p>407 rows × 3 columns</p>\n",
"</div>"
],
"text/plain": [
" coid mdate Market_Cap_Dollars\n",
"0 1471 2024-10-25 1.962910e+09\n",
"1 1582 2024-10-25 1.514820e+10\n",
"2 2059 2024-10-25 1.195978e+11\n",
"3 2301 2024-10-25 2.441140e+11\n",
"4 2302 2024-10-25 3.201330e+09\n",
".. ... ... ...\n",
"402 8215 2024-10-25 1.032572e+10\n",
"403 8249 2024-10-25 9.068506e+09\n",
"404 8261 2024-10-25 9.226725e+09\n",
"405 8271 2024-10-25 6.603811e+09\n",
"406 9912 2024-10-25 7.050700e+08\n",
"\n",
"[407 rows x 3 columns]"
]
},
"execution_count": 4,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"import TejToolAPI\n",
"\n",
"mktcap_data = TejToolAPI.get_history_data(start = last_date,\n",
" end = last_date,\n",
" ticker = pool,\n",
" columns = ['Market_Cap_Dollars']\n",
" )\n",
"\n",
"mktcap_data"
]
},
{
"cell_type": "code",
"execution_count": 5,
"id": "943deaef-f891-4cb4-aa62-6f0f3281e2e6",
"metadata": {
"scrolled": true
},
"outputs": [
{
"data": {
"text/plain": [
"['2330',\n",
" '2317',\n",
" '2454',\n",
" '2382',\n",
" '2308',\n",
" '2412',\n",
" '3711',\n",
" '2303',\n",
" '2357',\n",
" '3045',\n",
" '6669',\n",
" '2345',\n",
" '3231',\n",
" '4904',\n",
" '2327',\n",
" '3008',\n",
" '3034',\n",
" '2395',\n",
" '4938',\n",
" '3017',\n",
" '3037',\n",
" '2379',\n",
" '2301',\n",
" '3653',\n",
" '3533',\n",
" '6409',\n",
" '2376',\n",
" '2360',\n",
" '3443',\n",
" '2356',\n",
" '2474',\n",
" '2449',\n",
" '2324',\n",
" '2377',\n",
" '2383',\n",
" '2408',\n",
" '2409',\n",
" '3481',\n",
" '3702',\n",
" '3036',\n",
" '2353',\n",
" '5269',\n",
" '2385',\n",
" '2347',\n",
" '2059',\n",
" '6526',\n",
" '2354',\n",
" '3044',\n",
" '6239',\n",
" '6176',\n",
" '2368',\n",
" '6789',\n",
" '2344',\n",
" '8046',\n",
" '6770',\n",
" '2313',\n",
" '2352',\n",
" '3005',\n",
" '3035',\n",
" '2388',\n",
" '2404',\n",
" '3023',\n",
" '6285',\n",
" '5434',\n",
" '6805',\n",
" '3706',\n",
" '6139',\n",
" '3189',\n",
" '3406',\n",
" '2492',\n",
" '6412',\n",
" '6531',\n",
" '2337',\n",
" '3532',\n",
" '2458',\n",
" '6515',\n",
" '4915',\n",
" '8070',\n",
" '6414',\n",
" '2451',\n",
" '4919',\n",
" '3042',\n",
" '2312',\n",
" '6257',\n",
" '2498',\n",
" '2363',\n",
" '2362',\n",
" '3413',\n",
" '3583',\n",
" '2393',\n",
" '6442',\n",
" '6214',\n",
" '8112',\n",
" '8210',\n",
" '3376',\n",
" '3714',\n",
" '3596',\n",
" '5388',\n",
" '3030',\n",
" '6691']"
]
},
"execution_count": 5,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"tickers = mktcap_data.nlargest(100, 'Market_Cap_Dollars')['coid'].tolist()\n",
"tickers"
]
},
{
"cell_type": "code",
"execution_count": 6,
"id": "e49f10b8-ffe4-42f5-aa69-fb6e04798a29",
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"Merging daily equity files:\n",
"Currently used TEJ API key call quota 266/100000 (0.27%)\n",
"Currently used TEJ API key data quota 914122/10000000 (9.14%)\n"
]
},
{
"name": "stderr",
"output_type": "stream",
"text": [
"[2024-10-28 03:45:19.943089] INFO: zipline.data.bundles.core: Ingesting tquant.\n",
"[2024-10-28 03:45:22.401729] INFO: zipline.data.bundles.core: Ingest tquant successfully.\n"
]
}
],
"source": [
"from datetime import timedelta\n",
"\n",
"start = (last_date - timedelta(days=90)).strftime('%Y-%m-%d') # 取3個月(90天)的資料\n",
"end = last_date.strftime('%Y-%m-%d')\n",
"\n",
"os.environ['mdate'] = start + ' ' + end\n",
"os.environ['ticker'] = ' '.join(tickers)\n",
"\n",
"!zipline ingest -b tquant"
]
},
{
"cell_type": "code",
"execution_count": 7,
"id": "9a7f3390-046f-423d-a197-690964aec630",
"metadata": {},
"outputs": [],
"source": [
"from zipline.pipeline import CustomFactor\n",
"from zipline.pipeline.data import TWEquityPricing\n",
"\n",
"class Supertrend(CustomFactor):\n",
" inputs = [\n",
" TWEquityPricing.high,\n",
" TWEquityPricing.low,\n",
" TWEquityPricing.close,\n",
" ]\n",
" window_length = 50 # Use a 50-day window length\n",
" outputs = ['atr', 'basic_upperband', 'basic_lowerband', 'final_upperband', 'final_lowerband']\n",
"\n",
" def compute(self, today, assets, out, highs, lows, closes):\n",
" # Calculate TR (True Range)\n",
" high_low = highs[1:] - lows[1:]\n",
" high_close = abs(highs[1:] - closes[:-1])\n",
" low_close = abs(lows[1:] - closes[:-1])\n",
" tr = np.maximum.reduce([high_low, high_close, low_close])\n",
"\n",
" # Calculate ATR (Average True Range)\n",
" atr = np.mean(tr, axis=0)\n",
" out.atr = atr\n",
"\n",
" # Calculate basic upperband & lowerband\n",
" hl2 = (highs + lows) / 2\n",
" basic_upperband = hl2 + 4 * atr\n",
" basic_lowerband = hl2 - 4 * atr\n",
"\n",
" # Initialize final upperband & lowerband\n",
" final_upperband = np.zeros_like(basic_upperband)\n",
" final_lowerband = np.zeros_like(basic_lowerband)\n",
"\n",
" # Calculate fianl upperband & lowerband\n",
" for i in range(1, len(closes)):\n",
" final_upperband[i] = np.where(\n",
" (basic_upperband[i] < final_upperband[i-1]) | (closes[i-1] > final_upperband[i-1]),\n",
" basic_upperband[i],\n",
" final_upperband[i-1]\n",
" )\n",
" \n",
" final_lowerband[i] = np.where(\n",
" (basic_lowerband[i] > final_lowerband[i-1]) | (closes[i-1] < final_lowerband[i-1]),\n",
" basic_lowerband[i],\n",
" final_lowerband[i-1]\n",
" )\n",
" \n",
" out.basic_upperband = basic_upperband[-1]\n",
" out.basic_lowerband = basic_lowerband[-1]\n",
" out.final_upperband = final_upperband[-1]\n",
" out.final_lowerband = final_lowerband[-1]"
]
},
{
"cell_type": "code",
"execution_count": 8,
"id": "ae6c4acd-4f5d-432d-b5c7-62c2e4ab1a22",
"metadata": {},
"outputs": [],
"source": [
"class ADX(CustomFactor):\n",
" inputs = [TWEquityPricing.high, TWEquityPricing.low, TWEquityPricing.close]\n",
" window_length = 14 # ADX通常使用14天作為默認週期\n",
" outputs = ['adx']\n",
" \n",
" def compute(self, today, assets, out, highs, lows, closes):\n",
" # Calculate TR (True Range)\n",
" high_low = highs[1:] - lows[1:]\n",
" high_close = abs(highs[1:] - closes[:-1])\n",
" low_close = abs(lows[1:] - closes[:-1])\n",
" tr = np.maximum.reduce([high_low, high_close, low_close])\n",
"\n",
" # Calculate ATR (Average True Range)\n",
" atr = np.mean(tr, axis=0)\n",
" \n",
" # Calculate +DM, -DM (Directional Movement)\n",
" up_move = highs[1:] - lows[:-1]\n",
" down_move = lows[:-1] - lows[1:]\n",
" plus_dm = np.where((up_move > down_move) & (up_move > 0), up_move, 0)\n",
" minus_dm = np.where((down_move > up_move) & (down_move > 0), down_move, 0)\n",
" \n",
" # Calaulate rolling mean of +DM & -DM (smoothing)\n",
" plus_dm_smooth = np.mean(plus_dm, axis=0)\n",
" minus_dm_smooth = np.mean(minus_dm, axis=0)\n",
" \n",
" # Calculate +DI, -DI (Directional Indicator)\n",
" di_plus = 100 * np.divide(plus_dm_smooth, atr)\n",
" di_minus = 100 * np.divide(minus_dm_smooth, atr)\n",
" \n",
" # Calculate DX\n",
" dx = 100 * np.divide(np.abs(di_plus - di_minus), np.abs(di_plus + di_minus))\n",
" \n",
" # Calculate ADX (rolling mean of DX)\n",
" dx_series = pd.DataFrame(dx)\n",
" adx = dx_series.rolling(window = 50, min_periods = 1).mean().values.flatten()\n",
"\n",
" out.adx = adx"
]
},
{
"cell_type": "code",
"execution_count": 9,
"id": "1049f7b3-7f5f-41b8-bdb0-0efac8a83631",
"metadata": {},
"outputs": [],
"source": [
"from zipline.pipeline import Pipeline\n",
"from zipline.TQresearch.tej_pipeline import run_pipeline\n",
"from zipline.pipeline.filters import StaticAssets\n",
"\n",
"def make_pipeline():\n",
"\n",
" close = TWEquityPricing.close.latest\n",
" supertrend = Supertrend()\n",
" adx = ADX()\n",
" \n",
" return Pipeline(\n",
" columns={\n",
" 'close': close,\n",
" 'final_upperband': supertrend.final_upperband,\n",
" 'final_lowerband': supertrend.final_lowerband,\n",
" 'ADX': adx.adx\n",
" }\n",
" )"
]
},
{
"cell_type": "code",
"execution_count": 10,
"id": "5e8695ca-81f1-4741-a020-defb9d8715f3",
"metadata": {},
"outputs": [
{
"data": {
"text/html": [
"<div>\n",
"<style scoped>\n",
" .dataframe tbody tr th:only-of-type {\n",
" vertical-align: middle;\n",
" }\n",
"\n",
" .dataframe tbody tr th {\n",
" vertical-align: top;\n",
" }\n",
"\n",
" .dataframe thead th {\n",
" text-align: right;\n",
" }\n",
"</style>\n",
"<table border=\"1\" class=\"dataframe\">\n",
" <thead>\n",
" <tr style=\"text-align: right;\">\n",
" <th></th>\n",
" <th></th>\n",
" <th>close</th>\n",
" <th>final_upperband</th>\n",
" <th>final_lowerband</th>\n",
" <th>ADX</th>\n",
" </tr>\n",
" </thead>\n",
" <tbody>\n",
" <tr>\n",
" <th rowspan=\"2\" valign=\"top\">2024-10-25 00:00:00+00:00</th>\n",
" <th>Equity(16 [2354])</th>\n",
" <td>69.8</td>\n",
" <td>69.014286</td>\n",
" <td>63.985714</td>\n",
" <td>79.029377</td>\n",
" </tr>\n",
" <tr>\n",
" <th>Equity(73 [4919])</th>\n",
" <td>94.1</td>\n",
" <td>89.441837</td>\n",
" <td>78.708163</td>\n",
" <td>79.987217</td>\n",
" </tr>\n",
" </tbody>\n",
"</table>\n",
"</div>"
],
"text/plain": [
" close final_upperband \\\n",
"2024-10-25 00:00:00+00:00 Equity(16 [2354]) 69.8 69.014286 \n",
" Equity(73 [4919]) 94.1 89.441837 \n",
"\n",
" final_lowerband ADX \n",
"2024-10-25 00:00:00+00:00 Equity(16 [2354]) 63.985714 79.029377 \n",
" Equity(73 [4919]) 78.708163 79.987217 "
]
},
"execution_count": 10,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"last_data = run_pipeline(make_pipeline(), last_date, last_date)\n",
"buy_data = last_data[last_data['close'] > last_data['final_upperband']]\n",
"buy_data"
]
},
{
"cell_type": "code",
"execution_count": 11,
"id": "854a55a6-7925-4ed6-8e9a-2f87c03df2da",
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
"['2354', '4919']"
]
},
"execution_count": 11,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"buy_list = []\n",
"for i in range(len(buy_data)):\n",
" stock = buy_data.index.get_level_values(1)[i].symbol\n",
" buy_list.append(stock)\n",
"\n",
"buy_list"
]
},
{
"cell_type": "code",
"execution_count": 12,
"id": "19d00454-737e-4bfb-88fd-d73e5018275c",
"metadata": {},
"outputs": [
{
"data": {
"text/html": [
"<div>\n",
"<style scoped>\n",
" .dataframe tbody tr th:only-of-type {\n",
" vertical-align: middle;\n",
" }\n",
"\n",
" .dataframe tbody tr th {\n",
" vertical-align: top;\n",
" }\n",
"\n",
" .dataframe thead th {\n",
" text-align: right;\n",
" }\n",
"</style>\n",
"<table border=\"1\" class=\"dataframe\">\n",
" <thead>\n",
" <tr style=\"text-align: right;\">\n",
" <th></th>\n",
" <th></th>\n",
" <th>close</th>\n",
" <th>final_upperband</th>\n",
" <th>final_lowerband</th>\n",
" <th>ADX</th>\n",
" </tr>\n",
" </thead>\n",
" <tbody>\n",
" <tr>\n",
" <th rowspan=\"2\" valign=\"top\">2024-10-25 00:00:00+00:00</th>\n",
" <th>Equity(42 [2498])</th>\n",
" <td>45.85</td>\n",
" <td>52.910204</td>\n",
" <td>47.089796</td>\n",
" <td>80.747982</td>\n",
" </tr>\n",
" <tr>\n",
" <th>Equity(93 [6770])</th>\n",
" <td>19.70</td>\n",
" <td>21.993878</td>\n",
" <td>19.831122</td>\n",
" <td>82.509597</td>\n",
" </tr>\n",
" </tbody>\n",
"</table>\n",
"</div>"
],
"text/plain": [
" close final_upperband \\\n",
"2024-10-25 00:00:00+00:00 Equity(42 [2498]) 45.85 52.910204 \n",
" Equity(93 [6770]) 19.70 21.993878 \n",
"\n",
" final_lowerband ADX \n",
"2024-10-25 00:00:00+00:00 Equity(42 [2498]) 47.089796 80.747982 \n",
" Equity(93 [6770]) 19.831122 82.509597 "
]
},
"execution_count": 12,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"sell_data = last_data[(last_data['close'] < last_data['final_lowerband']) & (last_data['ADX'] > 50)]\n",
"sell_data"
]
},
{
"cell_type": "code",
"execution_count": 13,
"id": "8a9e0d06-8c05-4249-87f9-4810f9eaee66",
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
"['2498', '6770']"
]
},
"execution_count": 13,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"sell_list = []\n",
"for i in range(len(sell_data)):\n",
" stock = sell_data.index.get_level_values(1)[i].symbol\n",
" sell_list.append(stock)\n",
"\n",
"sell_list"
]
},
{
"cell_type": "markdown",
"id": "9cb976d3-120d-4bb0-9d26-6db63cdf3720",
"metadata": {},
"source": [
"## 串接到永豐 API\n",
"關於永豐 API 的詳細介紹可參考:[Shioaji 技術手冊](https://sinotrade.github.io/zh_TW/)"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "debd0485-df3f-4217-a68f-4b4f15822062",
"metadata": {},
"outputs": [],
"source": [
"import shioaji as sj\n",
"\n",
"api = sj.Shioaji(simulation=True) # 模擬模式\n",
"api.login(\n",
" api_key = 'your api_key', \n",
" secret_key = 'your secret_key',\n",
" contracts_cb=lambda security_type: print(f\"{repr(security_type)} fetch done.\") # 獲取商品檔 Callback\n",
")"
]
},
{
"cell_type": "markdown",
"id": "266d425c-3c74-4614-b4a7-a1cecc064ddc",
"metadata": {},
"source": [
"### 查看部位資訊\n",
"透過永豐 API 的 `list_positions` 函式,我們可以看到當前持有的部位資訊,包含:股票代碼、持有數量、平均價格、目前股價、損益等。 \n",
"\n",
"p.s. 第一次執行 `list_positions` 函式時,因為我們尚未持有部位,因此會回傳**空的資料表**。"
]
},
{
"cell_type": "code",
"execution_count": 15,
"id": "54e25daf-1626-44f9-ad79-9aee443ec461",
"metadata": {},
"outputs": [
{
"data": {
"text/html": [
"<div>\n",
"<style scoped>\n",
" .dataframe tbody tr th:only-of-type {\n",
" vertical-align: middle;\n",
" }\n",
"\n",
" .dataframe tbody tr th {\n",
" vertical-align: top;\n",
" }\n",
"\n",
" .dataframe thead th {\n",
" text-align: right;\n",
" }\n",
"</style>\n",
"<table border=\"1\" class=\"dataframe\">\n",
" <thead>\n",
" <tr style=\"text-align: right;\">\n",
" <th></th>\n",
" </tr>\n",
" </thead>\n",
" <tbody>\n",
" </tbody>\n",
"</table>\n",
"</div>"
],
"text/plain": [
"Empty DataFrame\n",
"Columns: []\n",
"Index: []"
]
},
"execution_count": 15,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"positions = api.list_positions(api.stock_account)\n",
"position_data = pd.DataFrame(s.__dict__ for s in positions)\n",
"position_data"
]
},
{
"cell_type": "markdown",
"id": "d9ee5695-d1cc-45a6-8c5b-9d590fa81d47",
"metadata": {},
"source": [
"### 取得當前持有標的清單 \n",
"將部位資訊中的 *code* 欄位取出,即可獲得當前持有的股票清單 *hold_list*。"
]
},
{
"cell_type": "code",
"execution_count": 16,
"id": "bde4829b-b7a5-482a-a87b-a50ff8ab4f30",
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
"[]"
]
},
"execution_count": 16,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"if position_data.empty:\n",
" hold_list = []\n",
"else:\n",
" hold_list = position_data['code'].to_list()\n",
" \n",
"hold_list"
]
},
{
"cell_type": "markdown",
"id": "f080e563-6a73-4e56-9dfe-488441f9ae40",
"metadata": {},
"source": [
"### 設定委託回報函式\n",
"我們設定委託回報函式 `order_cb`,並將其傳入 `set_order_callback`,在下單完成後將會回傳委託資訊與成交回報訊息。"
]
},
{
"cell_type": "code",
"execution_count": 17,
"id": "94e95db1-303b-4d61-beb1-197fae70abd0",
"metadata": {},
"outputs": [],
"source": [
"def order_cb(stat, msg):\n",
" print('my_order_callback')\n",
" print(stat, msg)\n",
"\n",
"api.set_order_callback(order_cb)"
]
},
{
"cell_type": "markdown",
"id": "8c039536-4cd1-41f3-951f-01098f466dff",
"metadata": {},
"source": [
"### 下單買賣\n",
"`place_order` 為永豐 API 的下單函式,下單時我們須提供商品資訊 *contract* 及下單資訊 *order*。*contract* 包含股票代碼,*order* 則包含買進或賣出、價格、張數、限市價單、掛單類型 ( ROD, IOC, FOK )、整股或零股等設定。\n",
"\n",
"本文的買賣條件如下:\n",
" - 若 *buy_list* 的標的不在 *hold_list* 中,則用市價單買入一張股票\n",
" - 若 *sell_list* 的標的在 *hold_list* 中,則用市價單賣出一張股票\n",
"\n",
"p.s. price_type 用 MKP (範圍市價) 可能會有以下的 error,且無法順利成交 \n",
"```python\n",
"2024-09-24 09:52:51.492 | ERROR | shioaji.shioaji:place_order:419 - {'status': {'status_code': 500}, 'response': {'detail': 'Internal Server Error'}}\n",
"```"
]
},
{
"cell_type": "code",
"execution_count": 18,
"id": "e3a60547-e7c3-40bf-940e-06a1c0a2e1f1",
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"processing 2354\n",
"2354 not in hold_list\n",
"Ready to buy!\n",
"====================================================================================================\n",
"processing 4919\n",
"4919 not in hold_list\n",
"Ready to buy!\n",
"my_order_callback\n",
"OrderState.StockOrder {'operation': {'op_type': 'New', 'op_code': '00', 'op_msg': ''}, 'order': {'id': '000401', 'seqno': '000401', 'ordno': '000106', 'account': {'account_type': 'S', 'person_id': '', 'broker_id': '9A95', 'account_id': '3369822', 'signed': True}, 'action': 'Buy', 'price': 0, 'quantity': 1, 'order_cond': 'Cash', 'order_lot': 'Common', 'custom_field': '', 'order_type': 'IOC', 'price_type': 'MKT'}, 'status': {'id': '000401', 'exchange_ts': 1730087221.832041, 'order_quantity': 1, 'modified_price': 0, 'cancel_quantity': 0, 'web_id': '137'}, 'contract': {'security_type': 'STK', 'exchange': 'TSE', 'code': '2354'}}\n",
"my_order_callback\n",
"OrderState.StockOrder {'operation': {'op_type': 'New', 'op_code': '00', 'op_msg': ''}, 'order': {'id': '000402', 'seqno': '000402', 'ordno': '0000EE', 'account': {'account_type': 'S', 'person_id': '', 'broker_id': '9A95', 'account_id': '3369822', 'signed': True}, 'action': 'Buy', 'price': 0, 'quantity': 1, 'order_cond': 'Cash', 'order_lot': 'Common', 'custom_field': '', 'order_type': 'IOC', 'price_type': 'MKT'}, 'status': {'id': '000402', 'exchange_ts': 1730087221.86957, 'order_quantity': 1, 'modified_price': 0, 'cancel_quantity': 0, 'web_id': '137'}, 'contract': {'security_type': 'STK', 'exchange': 'TSE', 'code': '4919'}}\n",
"====================================================================================================\n",
"my_order_callback\n",
"OrderState.StockDeal {'trade_id': '000402', 'seqno': '000402', 'ordno': '0000EE', 'exchange_seq': '000001', 'broker_id': '9A95', 'account_id': '3369822', 'action': 'Buy', 'code': '4919', 'order_cond': 'Cash', 'order_lot': 'Common', 'price': 95.4, 'quantity': 1, 'web_id': '137', 'custom_field': '', 'ts': 1730087222.687482}\n",
"my_order_callback\n",
"OrderState.StockDeal {'trade_id': '000401', 'seqno': '000401', 'ordno': '000106', 'exchange_seq': '000001', 'broker_id': '9A95', 'account_id': '3369822', 'action': 'Buy', 'code': '2354', 'order_cond': 'Cash', 'order_lot': 'Common', 'price': 72.9, 'quantity': 1, 'web_id': '137', 'custom_field': '', 'ts': 1730087229.40197}\n"
]
}
],
"source": [
"for i in buy_list:\n",
" print('processing %s' % i)\n",
" \n",
" if i not in hold_list:\n",
" print('%s not in hold_list' % i)\n",
" print('Ready to buy!')\n",
" \n",
" contract = api.Contracts.Stocks.TSE[i]\n",
"\n",
" order = api.Order(\n",
" action = 'Buy',\n",
" price = 0, # MKT, MKP will not use price parameter\n",
" quantity = 1,\n",
" price_type = 'MKT', # MKT or MKP\n",
" order_type = 'IOC', # ROD, IOC, FOK\n",
" order_lot = 'Common', # Common:整股, Fixing:定盤, Odd:盤後零股, IntradayOdd:盤中零股\n",
" account = api.stock_account\n",
" )\n",
"\n",
" trade = api.place_order(contract, order)\n",
" trade\n",
"\n",
" else:\n",
" print('%s in hold_list' % i)\n",
"\n",
" print('=' * 100)"
]
},
{
"cell_type": "code",
"execution_count": 19,
"id": "37fa5021-2a45-46c6-9957-14a0bb70d960",
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"processing 2498\n",
"2498 not in hold_list\n",
"====================================================================================================\n",
"processing 6770\n",
"6770 not in hold_list\n",
"====================================================================================================\n"
]
}
],
"source": [
"for i in sell_list:\n",
" print('processing %s' % i)\n",
" \n",
" if i in hold_list:\n",
" print('%s in hold_list' % i)\n",
" print('Ready to sell!')\n",
" \n",
" contract = api.Contracts.Stocks.TSE[i]\n",
"\n",
" order = api.Order(\n",
" action = 'Sell',\n",
" price = 0, # MKT, MKP will not use price parameter\n",
" quantity = 1,\n",
" price_type = 'MKT', # MKT or MKP\n",
" order_type = 'IOC', # ROD, IOC, FOK\n",
" order_lot = 'Common', # Common:整股, Fixing:定盤, Odd:盤後零股, IntradayOdd:盤中零股\n",
" account = api.stock_account\n",
" )\n",
"\n",
" trade = api.place_order(contract, order)\n",
" trade\n",
"\n",
" else:\n",
" print('%s not in hold_list' % i)\n",
"\n",
" print('=' * 100)"
]
},
{
"cell_type": "code",
"execution_count": 20,
"id": "71d58839-3b74-4f4a-8b12-666bde3d234c",
"metadata": {},
"outputs": [
{
"data": {
"text/html": [
"<div>\n",
"<style scoped>\n",
" .dataframe tbody tr th:only-of-type {\n",
" vertical-align: middle;\n",
" }\n",
"\n",
" .dataframe tbody tr th {\n",
" vertical-align: top;\n",
" }\n",
"\n",
" .dataframe thead th {\n",
" text-align: right;\n",
" }\n",
"</style>\n",
"<table border=\"1\" class=\"dataframe\">\n",
" <thead>\n",
" <tr style=\"text-align: right;\">\n",
" <th></th>\n",
" <th>id</th>\n",
" <th>code</th>\n",
" <th>direction</th>\n",
" <th>quantity</th>\n",
" <th>price</th>\n",
" <th>last_price</th>\n",
" <th>pnl</th>\n",
" <th>yd_quantity</th>\n",
" <th>cond</th>\n",
" <th>margin_purchase_amount</th>\n",
" <th>collateral</th>\n",
" <th>short_sale_margin</th>\n",
" <th>interest</th>\n",
" </tr>\n",
" </thead>\n",
" <tbody>\n",
" <tr>\n",
" <th>0</th>\n",
" <td>0</td>\n",
" <td>2354</td>\n",
" <td>Action.Buy</td>\n",
" <td>1</td>\n",
" <td>72.9</td>\n",
" <td>72.9</td>\n",
" <td>-292.0</td>\n",
" <td>0</td>\n",
" <td>StockOrderCond.Cash</td>\n",
" <td>0</td>\n",
" <td>0</td>\n",
" <td>0</td>\n",
" <td>0</td>\n",
" </tr>\n",
" <tr>\n",
" <th>1</th>\n",
" <td>1</td>\n",
" <td>4919</td>\n",
" <td>Action.Buy</td>\n",
" <td>1</td>\n",
" <td>95.4</td>\n",
" <td>95.4</td>\n",
" <td>-381.0</td>\n",
" <td>0</td>\n",
" <td>StockOrderCond.Cash</td>\n",
" <td>0</td>\n",
" <td>0</td>\n",
" <td>0</td>\n",
" <td>0</td>\n",
" </tr>\n",
" </tbody>\n",
"</table>\n",
"</div>"
],
"text/plain": [
" id code direction quantity price last_price pnl yd_quantity \\\n",
"0 0 2354 Action.Buy 1 72.9 72.9 -292.0 0 \n",
"1 1 4919 Action.Buy 1 95.4 95.4 -381.0 0 \n",
"\n",
" cond margin_purchase_amount collateral short_sale_margin \\\n",
"0 StockOrderCond.Cash 0 0 0 \n",
"1 StockOrderCond.Cash 0 0 0 \n",
"\n",
" interest \n",
"0 0 \n",
"1 0 "
]
},
"execution_count": 20,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"positions = api.list_positions(api.stock_account)\n",
"position_data = pd.DataFrame(s.__dict__ for s in positions)\n",
"position_data"
]
},
{
"cell_type": "markdown",
"id": "c2016606-1e59-403e-b865-e2989447edcf",
"metadata": {},
"source": [
"### 查看已實現損益\n",
"使用 `list_profit_loss` 函式,搭配我們欲查詢的日期區間,即可查看股票代碼、價格、數量、損益、損益比、交易日期等已實現損益資訊。"
]
},
{
"cell_type": "code",
"execution_count": 21,
"id": "6e258cb1-8f0f-4031-8ea5-61011c6f71ec",
"metadata": {},
"outputs": [
{
"data": {
"text/html": [
"<div>\n",
"<style scoped>\n",
" .dataframe tbody tr th:only-of-type {\n",
" vertical-align: middle;\n",
" }\n",
"\n",
" .dataframe tbody tr th {\n",
" vertical-align: top;\n",
" }\n",
"\n",
" .dataframe thead th {\n",
" text-align: right;\n",
" }\n",
"</style>\n",
"<table border=\"1\" class=\"dataframe\">\n",
" <thead>\n",
" <tr style=\"text-align: right;\">\n",
" <th></th>\n",
" <th>id</th>\n",
" <th>code</th>\n",
" <th>quantity</th>\n",
" <th>pnl</th>\n",
" <th>date</th>\n",
" <th>dseq</th>\n",
" <th>price</th>\n",
" <th>pr_ratio</th>\n",
" <th>cond</th>\n",
" <th>seqno</th>\n",
" </tr>\n",
" </thead>\n",
" <tbody>\n",
" <tr>\n",
" <th>0</th>\n",
" <td>0</td>\n",
" <td>2330</td>\n",
" <td>1</td>\n",
" <td>112818.0</td>\n",
" <td>20241028</td>\n",
" <td>000158</td>\n",
" <td>1060.00</td>\n",
" <td>11.96379</td>\n",
" <td>StockOrderCond.Cash</td>\n",
" <td>00055D</td>\n",
" </tr>\n",
" <tr>\n",
" <th>1</th>\n",
" <td>1</td>\n",
" <td>2454</td>\n",
" <td>1</td>\n",
" <td>109777.0</td>\n",
" <td>20241028</td>\n",
" <td>000027</td>\n",
" <td>1320.00</td>\n",
" <td>9.11017</td>\n",
" <td>StockOrderCond.Cash</td>\n",
" <td>0000AB</td>\n",
" </tr>\n",
" <tr>\n",
" <th>2</th>\n",
" <td>2</td>\n",
" <td>2890</td>\n",
" <td>1</td>\n",
" <td>-1244.0</td>\n",
" <td>20241028</td>\n",
" <td>000030</td>\n",
" <td>23.35</td>\n",
" <td>-5.07745</td>\n",
" <td>StockOrderCond.Cash</td>\n",
" <td>0000D0</td>\n",
" </tr>\n",
" <tr>\n",
" <th>3</th>\n",
" <td>3</td>\n",
" <td>6152</td>\n",
" <td>1</td>\n",
" <td>-2915.0</td>\n",
" <td>20241028</td>\n",
" <td>000028</td>\n",
" <td>15.85</td>\n",
" <td>-15.58730</td>\n",
" <td>StockOrderCond.Cash</td>\n",
" <td>0000B2</td>\n",
" </tr>\n",
" <tr>\n",
" <th>4</th>\n",
" <td>4</td>\n",
" <td>2424</td>\n",
" <td>1</td>\n",
" <td>18687.0</td>\n",
" <td>20241028</td>\n",
" <td>00002C</td>\n",
" <td>80.60</td>\n",
" <td>30.33620</td>\n",
" <td>StockOrderCond.Cash</td>\n",
" <td>0000B0</td>\n",
" </tr>\n",
" <tr>\n",
" <th>5</th>\n",
" <td>5</td>\n",
" <td>6916</td>\n",
" <td>1</td>\n",
" <td>397.0</td>\n",
" <td>20241028</td>\n",
" <td>00002D</td>\n",
" <td>25.85</td>\n",
" <td>1.56548</td>\n",
" <td>StockOrderCond.Cash</td>\n",
" <td>0000B3</td>\n",
" </tr>\n",
" <tr>\n",
" <th>6</th>\n",
" <td>6</td>\n",
" <td>2488</td>\n",
" <td>1</td>\n",
" <td>-5294.0</td>\n",
" <td>20241028</td>\n",
" <td>000026</td>\n",
" <td>47.90</td>\n",
" <td>-9.98896</td>\n",
" <td>StockOrderCond.Cash</td>\n",
" <td>0000B1</td>\n",
" </tr>\n",
" <tr>\n",
" <th>7</th>\n",
" <td>7</td>\n",
" <td>6792</td>\n",
" <td>1</td>\n",
" <td>-2067.0</td>\n",
" <td>20241028</td>\n",
" <td>00019D</td>\n",
" <td>66.60</td>\n",
" <td>-3.02237</td>\n",
" <td>StockOrderCond.Cash</td>\n",
" <td>00066C</td>\n",
" </tr>\n",
" <tr>\n",
" <th>8</th>\n",
" <td>8</td>\n",
" <td>2466</td>\n",
" <td>1</td>\n",
" <td>-1540.0</td>\n",
" <td>20241028</td>\n",
" <td>00018D</td>\n",
" <td>34.90</td>\n",
" <td>-4.24325</td>\n",
" <td>StockOrderCond.Cash</td>\n",
" <td>00066B</td>\n",
" </tr>\n",
" </tbody>\n",
"</table>\n",
"</div>"
],
"text/plain": [
" id code quantity pnl date dseq price pr_ratio \\\n",
"0 0 2330 1 112818.0 20241028 000158 1060.00 11.96379 \n",
"1 1 2454 1 109777.0 20241028 000027 1320.00 9.11017 \n",
"2 2 2890 1 -1244.0 20241028 000030 23.35 -5.07745 \n",
"3 3 6152 1 -2915.0 20241028 000028 15.85 -15.58730 \n",
"4 4 2424 1 18687.0 20241028 00002C 80.60 30.33620 \n",
"5 5 6916 1 397.0 20241028 00002D 25.85 1.56548 \n",
"6 6 2488 1 -5294.0 20241028 000026 47.90 -9.98896 \n",
"7 7 6792 1 -2067.0 20241028 00019D 66.60 -3.02237 \n",
"8 8 2466 1 -1540.0 20241028 00018D 34.90 -4.24325 \n",
"\n",
" cond seqno \n",
"0 StockOrderCond.Cash 00055D \n",
"1 StockOrderCond.Cash 0000AB \n",
"2 StockOrderCond.Cash 0000D0 \n",
"3 StockOrderCond.Cash 0000B2 \n",
"4 StockOrderCond.Cash 0000B0 \n",
"5 StockOrderCond.Cash 0000B3 \n",
"6 StockOrderCond.Cash 0000B1 \n",
"7 StockOrderCond.Cash 00066C \n",
"8 StockOrderCond.Cash 00066B "
]
},
"execution_count": 21,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"profitloss = api.list_profit_loss(api.stock_account,'2024-10-28','2024-10-28')\n",
"profitloss_data = pd.DataFrame(pnl.__dict__ for pnl in profitloss)\n",
"profitloss_data"
]
},
{
"cell_type": "markdown",
"id": "2d749d21-d7ab-4451-9c6d-7ebdea305772",
"metadata": {},
"source": [
"### 登出永豐 API\n",
"因為永豐 API 有使用流量的限制,因此建議使用完 API 服務後順手登出 API 環境。"
]
},
{
"cell_type": "code",
"execution_count": 22,
"id": "ec5874dd-8d59-4e7d-8667-328a5b33fed8",
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
"True"
]
},
"execution_count": 22,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"api.logout()"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "94cae78a-d68f-49df-a2bd-9a6d8386d2fd",
"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
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment