Table of Contents
在之前的教學中,我們已經學會如何創建自己的 streamlit app了,詳細資料可以參考這篇文章,接下來這篇文章將用利用 TEJAPI 資料庫串聯 STREAMLIT 套件將網格交易的策略撰寫出來,並利用套件中的選擇日期、下拉式選單、數值選取工具來與圖表以及資料表做連動,使資料變成可以互動的 APP。
網格交易是一種交易策略,會藉由設定兩個參數上界與下界來選取出一個區間,再將我們的股價區分成一格一格的價格區間,當價格下跌並觸碰到下方的格子時就買進股票,當價格上漲並超過上面的格子時則賣出股票。
此策略是一種懶人策略,不太需要手動的去操作,除此之外還可以跟著價格的動盪賺錢,但是要注意幾個點使用此策略的話資金運用的效率會變得比手動交易來的低。
本文使用 Windows 並以 VScode 作為編輯器
# STREAMLIT套件
import streamlit as st
import pandas as pd
# 可以互動的PLOT套件
import plotly.graph_objects as go
# 設置日期格式的套件
import datetime
from datetime import datetime as dt
from datetime import timedelta
import tejapi
# 登入TEJ API
tejapi.ApiConfig.api_key = your_key
#把時間取消保留日期 (無視)
tejapi.ApiConfig.ignoretz = True
Sidebar版面小工具設置
col1, col2 = st.columns(2)
with col1:
# 將股票起使日期設置為變數d1
d1 = st.date_input(
"股票起始日期",
# 並將預設資料設定為2022年的1/1
datetime.date(2022, 1, 1))
with col2:
# 將股票起使日期設置為變數d2
d2= st.date_input(
"股票結束日期",
datetime.date(2023, 2, 3))
由於當天的股票資料還沒收盤,抓取當天資料會出現錯誤,因此這邊改成抓取昨天的資料,並將資料的欄位opts這邊只留下coid(股價名稱資料)
#輸入股價
# 使用date套件的date獲取今天的日期資料
current_date = dt.now().date()
# 使用date套件的timedelta獲取昨天的日期資料
previous_date = current_date - timedelta(days=1)
data = tejapi.get('TWN/APIPRCD',
mdate=previous_date,
opts={'columns':['coid']},
paginate=True)
coids = data['coid'].tolist()
stock_code = st.selectbox('選擇股票代碼', data)
st.write('你選擇股票是: ', stock_code)
這邊將預設的值設定為上界值為150、下界值為100、網格區間設定為
upper_bound = st.number_input('上界值:',value=150)
lower_bound = st.number_input('下界值:',value=100)
interval = st.number_input('網格區間:',value=10)
在右側的面板中我們配置了三項工具來方便我們觀察數據的走勢,包括買進賣出的訊號圖表、資料集、報酬率
剛剛在左側已經有了股票名稱、起始時間、結束時間,這邊將這些小工具參數填入我們的TEJAPI資料庫中,選取出我們需要的資料
再創建買進跟賣出的訊號的欄位,將下跌並碰觸到網格的資料填入買入訊號,將上漲超過網格的資料填入賣出訊號欄位
stock_id = {stock_code}
gte, lte = {d1}, {d2}
tejdata= tejapi.get('TWN/APIPRCD',
paginate = True,
coid = stock_id,
mdate = {'gte':gte, 'lte':lte},
chinese_column_name=True
)
df = tejdata
df.reset_index(drop=True, inplace=True)
# 創建 Buy_Signal 和 Sell_Signal 列,預設為 False
df['Buy_Signal'] = False
df['Sell_Signal'] = False
# 在適當的條件下,將 Buy_Signal 和 Sell_Signal 設置為 True
grid = list(range(lower_bound, upper_bound + interval, interval))
for i in range(1, len(df)):
for price in grid:
if df['收盤價'][i-1] > price >= df['收盤價'][i]:
df.at[i, 'Buy_Signal'] = True
if df['收盤價'][i-1] < price <= df['收盤價'][i]:
df.at[i, 'Sell_Signal'] = True
tab1, tab2, tab3 = st.tabs(["交易訊號", "資料集", "投資績效表"])
將收盤價繪製成股票走勢圖並寫進tab1當中,再將買賣訊號加入在圖表當中
with tab1:
st.title(stock_code)
fig = go.Figure()
fig.add_trace(go.Scatter(x=df['資料日'], y=df['收盤價'], name=stock_code))
# 創建網格,將線條顏色設置為灰色
for price in grid:
fig.add_shape(type="line", x0=df['資料日'].iloc[0], x1=df['資料日'].iloc[-1], y0=price, y1=price, line=dict(color='gray'))
# 加入買進訊號的散點圖
buy_signals = df[df['Buy_Signal']]
fig.add_trace(go.Scatter(x=buy_signals['資料日'], y=buy_signals['收盤價'], mode='markers', name='Buy Signal', marker=dict(color='green', size=10, symbol='triangle-up')))
# 加入賣出訊號的散點圖
sell_signals = df[df['Sell_Signal']]
fig.add_trace(go.Scatter(x=sell_signals['資料日'], y=sell_signals['收盤價'], mode='markers', name='Sell Signal', marker=dict(color='red', size=10, symbol='triangle-down')))
fig.update_layout()
st.plotly_chart(fig)
將前面讀取到的df資料輸入到tab2中,並加入資料下載按鈕
with tab2:
st.dataframe(df, height=500)
@st.cache_data
def convert_df(df):
# IMPORTANT: Cache the conversion to prevent computation on every rerun
return df.to_csv().encode("utf-8")
csv = convert_df(df)
st.download_button(
label="點此下載資料範例",
data=csv,
file_name=f"{stock_code}股價資料.csv",
mime="text/csv",)
這裡初始我們定義了幾個參數
● principal:本金
● position:股票部位持有張數
● cash:現金部位持有量
● order_unit:交易單位數這邊將本金設定為500000,並將每次的買次交易張數設定為1000股,並將每次的交易時間、交易成本、交易單位、持倉狀況、現金價值變成新的資料集trade_book,並利用裡面的參數去計算投資的報表
trade_book的交易表欄位名稱:
● BuyTime: 買入時間點
● SellTime: 賣出時間點
● CashFlow: 現金流出入量
● TradeUnit: 買進賣出張數
● HoldingPosition: 持有部位
● CashValue: 剩餘現金量
with tab3:
st.title('交易模擬')
# 初始化本金和交易紀錄表
principal = 500000
cash = principal
position = 0
order_unit = 1000 # 每次交易的股數
trade_book = pd.DataFrame(columns=['Coid', 'BuyOrSell', 'BuyTime', 'SellTime', 'CashFlow', 'TradeUnit', 'HoldingPosition', 'CashValue'])
st.write("本金:", principal)
# 進行網格交易並更新交易紀錄表
for i in range(1, len(df)):
cu_time = df['資料日'][i]
cu_close = df['收盤價'][i]
n_time = df['資料日'][i - 1]
n_open = df['開盤價'][i]
if position == 0 and df['Buy_Signal'][i]:
# 判斷是否進行買進
position += 1
order_time = n_time
order_price = n_open
friction_cost = (20 if order_price * order_unit * 0.001425 < 20 else order_price * order_unit * 0.001425)
total_cost = -1 * order_price * order_unit - friction_cost
cash += total_cost
trade_book = trade_book.append({'Coid': stock_id, 'BuyOrSell': 'Buy', 'BuyTime': order_time, 'SellTime': None, 'CashFlow': total_cost, 'TradeUnit': order_unit, 'HoldingPosition': position, 'CashValue': cash}, ignore_index=True)
elif position > 0 and df['Sell_Signal'][i]:
# 判斷是否進行賣出
order_price = n_open
cover_time = n_time
friction_cost = (20 if order_price * order_unit * 0.001425 < 20 else order_price * order_unit * 0.001425) + order_price * order_unit * 0.003
total_cost = order_price * order_unit - friction_cost
cash += total_cost
trade_book = trade_book.append({'Coid': stock_id, 'BuyOrSell': 'Sell', 'BuyTime': None, 'SellTime': cover_time, 'CashFlow': total_cost, 'TradeUnit': -1 * order_unit, 'HoldingPosition': position, 'CashValue': cash}, ignore_index=True)
position = 0
st.write("交易紀錄表:")
st.dataframe(trade_book)
# 計算交易相關數據
overallreturn = ((cash - principal) / principal) * 100
num_trade = len(trade_book)
num_buy = len(trade_book[trade_book['BuyOrSell'] == 'Buy'])
num_sell = len(trade_book[trade_book['BuyOrSell'] == 'Sell'])
# 計算平均交易報酬
avg_return_ = (trade_book['CashFlow'] / (trade_book['TradeUnit'] * trade_book['CashValue'])).mean() * 100
# 計算平均持有期間
trade_book['BuyTime'] = pd.to_datetime(trade_book['BuyTime']) # 將BuyTime轉換為datetime格式
trade_book['SellTime'] = pd.to_datetime(trade_book['SellTime']) # 將SellTime轉換為datetime格式
# 計算持有期間並排除掉NaN和負值
trade_book['HoldPeriod'] = (trade_book['SellTime'] - trade_book['BuyTime']).dt.days
trade_book['HoldPeriod'] = trade_book['HoldPeriod'].apply(lambda x: max(x, 0)) # 將持有期間中的負值改為0
avg_hold_period_ = trade_book['HoldPeriod'].mean()
# 計算勝率
winning_rate = (len(trade_book[trade_book['CashFlow'] > 0]) / num_trade) * 100
# 計算最大獲利交易報酬和最大損失交易報酬
trade_book['ReturnPercentage'] = (trade_book['CashFlow'] / (trade_book['TradeUnit'] * trade_book['CashValue'])) * 100
max_win = trade_book['ReturnPercentage'].max()
max_loss = trade_book['ReturnPercentage'].min()
# 計算最低現金持有量
min_cash = trade_book['CashValue'].min()
# 呈現交易相關數據
st.write('總績效:', overallreturn, '%')
st.write('交易次數:', num_trade, '次')
st.write('買入次數:', num_buy, '次')
st.write('賣出次數:', num_sell, '次')
st.write('平均交易報酬:', avg_return_, '%')
st.write('勝率:', winning_rate, '%')
st.write('最大獲利交易報酬:', max_win, '%')
st.write('最大損失交易報酬:', max_loss, '%')
st.write('最低現金持有量:', min_cash)
設定完成後,我們可以藉由終端機來啟動 STREAMLIT 專案,透過在終端機輸入 streamlit run app.py 即可執行。
streamlit run app.py
本次實作使用 STREAMLIT 套件,讓 TEJAPI 的資料庫能夠以互動式 APP 的方式呈現數據之外,還可以加上網格交易的交易策略。讓讀者在設計APP時也可以去思考自己的交易策略還可以怎麼在APP上執行,並加入自己的程式中,讓整個分析出來的資料不只是一張報表。此外 ,TEJ API 提供廣泛的臺灣企業和金融市場相關資料,包括財務、股票、基金、宏觀經濟、產業等領域,並提供專業報告和研究,幫助公司研究、投資決策和市場預測。
溫馨提醒,本次介紹與標的僅供參考,不代表任何商品或投資上的建議。之後也會介紹使用 TEJ 資料庫來建構各式選擇權模型,所以歡迎對選擇權交易有興趣的讀者,選購 TEJ E-Shop 的相關方案,用高品質的資料庫,建構出適合自己的訂價模型。
電子報訂閱