Day29 - [Shioaji] 超入门!永丰证券程序交易API快速上手 (2)

今天来看一下如何使用Shioaji问回历史交易资料,不过在此先提醒一下,上一篇有讲到的永丰讲师的Youtube课程,其中讲师特别提醒大家,这个用法仅仅是方便永丰API用户测试用途使用,因为资源有限,并不建议大家过度使用这个方式进行历史资料的大量捞取,若在非正常情况下的使用,永丰是有权将使用者的帐号锁定。所以大家要使用的前提下,若仅进行资料测试时,尽量设定资料存取范围,以及爱惜资源。

询问历史Ticks资料:以股票为例

以下仅列出程序片段要点,前面该做login的步骤,请还是要按照方式使用,可参考前一篇的介绍。
就拿护国神山2330台积电为例。

import pandas as pd

TSE2330 = api.Contracts.Stocks.TSE.TSE2330
ticks = api.ticks(
    contract=TSE2330,
    date="2021-10-13",
    query_type=sj.constant.TicksQueryType.RangeTime,
    time_start="13:24:00",
    time_end="13:24:30"
)

pd.set_option('display.max_columns', None)

df = pd.DataFrame({**ticks})
df.ts = pd.to_datetime(df.ts)

print(df.tail(10))

这里使用了api.ticks()取回我们指定的Contract的历史资料,可指定某天的当日tick资料,为了缩小资料量,可透过query_type设定为TicksQueryType.RangeTime的方式,我们可设定取回的起迄时间。
例如我这里设定了取回收盘前逐笔搓合时间段的某30秒的资料。

并且使用pandas的DataFrame进行资料整理,记得把ts的时间作转换。我们印出最末端10笔资料。

Output如下:

                           ts  ask_price  close  bid_volume  bid_price  \
9  2021-10-13 13:24:09.456604      572.0  571.0         121      571.0   
10 2021-10-13 13:24:11.003873      572.0  572.0         121      571.0   
11 2021-10-13 13:24:14.429999      572.0  571.0         122      571.0   
12 2021-10-13 13:24:18.495718      572.0  572.0         124      571.0   
13 2021-10-13 13:24:22.597406      572.0  572.0         125      571.0   
14 2021-10-13 13:24:23.154184      572.0  572.0         125      571.0   
15 2021-10-13 13:24:23.206579      573.0  572.0         126      571.0   
16 2021-10-13 13:24:27.416942      573.0  572.0         129      571.0   
17 2021-10-13 13:24:27.782836      573.0  572.0         130      571.0   
18 2021-10-13 13:24:28.055190      573.0  573.0         130      571.0   

    ask_volume  volume  
9           71      41  
10          69       1  
11          67       3  
12          65       2  
13          65       1  
14          64       1  
15         116      64  
16         120       1  
17         120       1  
18         119       2  

询问K线(KBar)资料:以股票为例

我们接着可以使用api.kbars()来问回K线资料,一样指定contract与起迄日期,并会以每分钟的K线资料逐笔回传。而K线资料会有每个区间的Open、High、Low、Close四个价格数值,K线图也是以此作为最重要的依据。



kbars = api.kbars(
    contract=TSE2330,
    start="2021-10-12",
    end="2021-10-12"
)
df = pd.DataFrame({**kbars})
df.ts = pd.to_datetime(df.ts)

我们印出部份的Kbars值如下:

                     Volume  Close   Open    Low   High        Amount
ts                                                                   
2021-10-12 09:01:00    4493  570.0  570.0  570.0  571.0  2.561513e+09
2021-10-12 09:02:00     415  570.0  570.0  569.0  571.0  2.364880e+08
2021-10-12 09:03:00     184  570.0  570.0  569.0  571.0  1.048730e+08
2021-10-12 09:04:00     636  569.0  570.0  568.0  570.0  3.616940e+08
2021-10-12 09:05:00     276  568.0  568.0  568.0  569.0  1.567810e+08
...                     ...    ...    ...    ...    ...           ...
2021-10-12 13:22:00      84  573.0  573.0  573.0  574.0  4.815400e+07
2021-10-12 13:23:00      48  573.0  573.0  573.0  574.0  2.753100e+07
2021-10-12 13:24:00     128  573.0  574.0  573.0  574.0  7.336300e+07
2021-10-12 13:25:00      88  573.0  573.0  572.0  574.0  5.037700e+07
2021-10-12 13:30:00    3792  575.0  575.0  575.0  575.0  2.180400e+09

接下来,介绍一个画金融资料的好用图表工具:mplfinance

可透过conda的安装路径进行安装,可参考conda-forge package网址

conda install -c conda-forge mplfinance

接着就可以使用简单的方法,来绘制K线图。

用图表来显示更直觉

import mplfinance as mpf

df.set_index('ts', inplace=True)
df.index.name = "ts"
df.index = pd.DatetimeIndex(df.index)

color = mpf.make_marketcolors(up='r', down='g', inherit=True)
style = mpf.make_mpf_style(base_mpf_style='charles', marketcolors=color)

mpf.plot(df, **dict(type='candle', volume=True, style=style))

上面需设定DatetimeIndex,请参考上述方式,才能正确透过malfinance画图。

接着是定义其线图的颜色与型态,需要将上涨与下跌的重新指定台湾股市特有的红涨绿跌。(国外是相反过来的)
style有几种可以指定,可透过mplfinance.available_styles()印出:
['blueskies', 'brasil', 'charles', 'checkers', 'classic', 'default', 'mike', 'nightclouds', 'sas','starsandstripes', 'yahoo']

再来就是指定plot的参数,我们需要画的是candle图,以及把上述的设定放入。
如此一来,我们的1分钟期的K线图就产生了。

https://ithelp.ithome.com.tw/upload/images/20211013/20130354pYqCY4Gjvt.png

Snapshot快照

官方文件的Snapshot定义如下:

Snapshot is a present stock, future, option info. It contain open, high, low, close, change price, average price, volume, total volume, buy price, buy volume, sell price, sell volume and yesterday volume.

也就是股票、期货、选择权的当下资讯的摘要整理资讯。

直接使用范例程序看结果比较有感觉,Snapshot可一次放入多笔Contracts。
我们将2330台积电与2409友达光电放入作快照:

contracts = [api.Contracts.Stocks['2330'], api.Contracts.Stocks['2409']]
snapshots = api.snapshots(contracts)
df = pd.DataFrame(snapshots)
df.ts = pd.to_datetime(df.ts)

print(df)

Output结果如下:

     amount  average_price  buy_price  buy_volume  change_price  change_rate  \
0  25695000         571.26      570.0      2289.0         -4.00        -0.70   
1   6014400          16.95       16.8      3349.0         -0.25        -1.47   

       change_type  close  code exchange   high    low    open  sell_price  \
0  ChangeType.Down  571.0  2330      TSE  575.0  570.0  572.00      571.00   
1  ChangeType.Down   16.8  2409      TSE   17.2   16.8   17.15       16.85   

   sell_volume      tick_type  total_amount  total_volume                  ts  \
0           19   TickType.Buy   10804728928         18914 2021-10-13 14:30:00   
1          218  TickType.Sell     997845168         58854 2021-10-13 14:30:00   

   volume  volume_ratio  yesterday_volume  
0      45          0.71           26522.0  
1     358          0.83           71159.0  

Order与挂单

证券的买卖交易是投资人最关心的行为,当然程序交易必定终究还是要出手的。
当然交易不是买,就是卖,但其中有不少的交易特性是需要了解的,其Order的属性如下:

price (float or int): the price of order
quantity (int): the quantity of order
action (str): order action to buy or sell
    {Buy, Sell}
price_type (str): pricing type of order
    {LMT, MKT, MKP} (限价、市价、范围市价)
order_type (str): the type of order
    {ROD, IOC, FOK}
order_cond (str): order condition stock only
    {Cash, MarginTrading, ShortSelling} (现股、融资、融券)
order_lot (str): the type of order
    {Common, Fixing, Odd, IntradayOdd} (整股、定盘、盘後零股、盘中零股)
first_sell {str}: the type of first sell
    {true, false}
account (:obj:Account): which account to place this order
ca (binary): the ca of this order

价格与数量这不用多说,但当然你是买一般整股还是零股交易,数量的单位数是不同的需要特别注意。
再来就是会影响成交行为的挂单类型:ROD, IOC, FOK

  • ROD (Rest of Day):当日有效,这是一般股市下单的预设值。也就是你下的张数或股数,在今日收盘入都有效。所以如果要买入5张股票,先成交了3张,另外2张会继续自动挂单。
  • IOC (Immediate or Cancel):可同意部份成交,以刚的例子先成交了3张後,另2张就取消。
  • FOK (Fill or Kill):也就是只能全部数量成交才算,否则就直接全部取消。

而以上的Order物件,是纯粹就「不含Contract的交易资讯」进行设定,设定好了後,才和你要的标的物Contract一起送到api.place_order()中。

我们就进行针对友达光电2409进行小买5张的挂单实例:

TSE2409 = api.Contracts.Stocks.TSE.TSE2409
order = api.Order(
    price=17.0,
    quantity=5,
    action=sj.constant.Action.Buy,
    price_type=sj.constant.StockPriceType.LMT,
    order_type=sj.constant.TFTOrderType.ROD,
    account=api.stock_account
)
trade_2409 = api.place_order(TSE2409, order)
print(trade_2409)

上述先准备好目标Contract,然後是要挂单的Order设定。
我们以17.0元,下5张委买,使用限价模式以及ROD挂单。接着就可以把呼叫place_order()挂单。

以下是挂单的资讯:

contract=Stock(exchange=<Exchange.TSE: 'TSE'>, code='2409', symbol='TSE2409', name='友达', category='26', unit=1000, limit_up=18.75, limit_down=15.35, reference=17.05, update_date='2021/10/13', margin_trading_balance=99098, short_selling_balance=1344, day_trade=<DayTrade.Yes: 'Yes'>) order=Order(action=<Action.Buy: 'Buy'>, price=17.0, quantity=5, id='e40a1458', seqno='100538', ordno='00000', account=Account(account_type=<AccountType.Stock: 'S'>, person_id='PAPIUSER02', broker_id='9A95', account_id='0504486', signed=True), price_type=<StockPriceType.LMT: 'LMT'>, order_type=<FuturesOrderType.ROD: 'ROD'>) status=OrderStatus(id='e40a1458', status=<Status.PendingSubmit: 'PendingSubmit'>, status_code='0', order_datetime=datetime.datetime(2021, 10, 13, 22, 45, 23), deals=[])

上面的资讯,分为contractorder以及status三部份,而status中我们可看到目前状态为PendingSubmit (传送中)。

共有以下的状态:

  • PendingSubmit: 传送中
  • PreSubmitted: 预约单
  • Submitted: 传送成功
  • Failed: 失败
  • Cancelled: 已删除
  • Filled: 完全成交
  • Filling: 部分成交

更新挂单状态

可使用api.update_status()对帐户进行状态更新,里面传入的参数是证券帐户。
例如:

api.update_status(api.stock_account)

而我们可以把刚刚上面使用变数接值下来的交易trade,进行其他的操作,例如取消单修改单量或价格等。

我们以下面的取消单实例,看一下status的变化:

trade_2409 = api.place_order(TSE2409, order)
print(trade_2409)

api.update_status(api.stock_account)
print(trade_2409)

api.cancel_order(trade_2409)
api.update_status(api.stock_account)
print(trade_2409)

我们只列出上面三次print(trade_2409)的status的部份:

status=OrderStatus(id='e0d6ad73', status=<Status.PendingSubmit: 'PendingSubmit'>, status_code='0', order_datetime=datetime.datetime(2021, 10, 13, 22, 57, 2), deals=[])

status=OrderStatus(id='e0d6ad73', status=<Status.PreSubmitted: 'PreSubmitted'>, status_code='R', order_datetime=datetime.datetime(2021, 10, 13, 22, 57, 2), deals=[])

status=OrderStatus(id='e0d6ad73', status=<Status.Cancelled: 'Cancelled'>, status_code='X', order_datetime=datetime.datetime(2021, 10, 13, 22, 57, 2), cancel_quantity=5, deals=[])

可看到上述的status历经了:PendingSubmit(传送中) → PreSubmitted(预约单) → Cancelled(已删除)

看一下期货与选择权的Order

都看了股票了,也把期货与选择权的Order属性也一并确认一下:

price (float or int): the price of order
quantity (int): the quantity of order
action (str): order action to buy or sell
    {Buy, Sell}
price_type (str): pricing type of order
    {LMT, MKT, MKP} (限价、市价、范围市价)
order_type (str): the type of order
    {ROD, IOC, FOK}
octype (str): the type or order to open new position or close position future only
    {Auto, NewPosition, Cover, DayTrade} (自动、新仓、平仓、当冲)
account (:obj:Account): which account to place this order
ca (binary): the ca of this order

上述在octype和股票不太相同。

模拟期货挂单

我们针对大台指TXF 2021年10月份期货进行新仓挂买进2口。

TXF202110 = api.Contracts.Futures.TXF.TXF202110
order = api.Order(action="Buy",
                  price=16355,
                  quantity=2,
                  price_type=sj.constant.StockPriceType.LMT,
                  order_type=sj.constant.FuturesOrderType.ROD,
                  octype=sj.constant.FuturesOCType.Auto,
                  account=api.futopt_account)
trade_txf202110 = api.place_order(TXF202110, order)
print(trade_txf202110)

取得的trade内容如下:

contract=Future(code='TXFJ1', symbol='TXF202110', name='台股期货10', category='TXF', delivery_month='202110', delivery_date='2021/10/20', underlying_kind='I', unit=1, limit_up=18060.0, limit_down=14778.0, reference=16419.0, update_date='2021/10/13') order=Order(action=<Action.Buy: 'Buy'>, price=16355, quantity=2, id='91242c54', seqno='980237', account=Account(account_type=<AccountType.Future: 'F'>, person_id='PAPIUSER02', broker_id='F002000', account_id='9100295', signed=True), price_type=<StockPriceType.LMT: 'LMT'>, order_type=<FuturesOrderType.ROD: 'ROD'>) status=OrderStatus(id='91242c54', status=<Status.PendingSubmit: 'PendingSubmit'>, status_code='    ', order_datetime=datetime.datetime(2021, 10, 13, 23, 5, 38), deals=[])

盘中零股交易

除了以前要买卖零股只能於盘後进行交易,现在在盘中也可以方便投资人进行零股交易。在这边我们就需要在Order中设定order_lot=sj.constant.TFTStockOrderLot.IntradayOdd,不特别指定的话是预设的Common(一般整股交易)。

TSE2409 = api.Contracts.Stocks.TSE.TSE2409
order = api.Order(
    price=17.0,
    quantity=300,
    action=sj.constant.Action.Buy,
    price_type=sj.constant.StockPriceType.LMT,
    order_type=sj.constant.TFTOrderType.ROD,
    order_lot=sj.constant.TFTStockOrderLot.IntradayOdd,
    account=api.stock_account
)

而交易资讯Output如下:

contract=Stock(exchange=<Exchange.TSE: 'TSE'>, code='2409', symbol='TSE2409', name='友达', category='26', unit=1000, limit_up=18.75, limit_down=15.35, reference=17.05, update_date='2021/10/13', margin_trading_balance=99098, short_selling_balance=1344, day_trade=<DayTrade.Yes: 'Yes'>) order=Order(action=<Action.Buy: 'Buy'>, price=17.0, quantity=300, id='204dbcd3', seqno='100540', ordno='00000', account=Account(account_type=<AccountType.Stock: 'S'>, person_id='PAPIUSER02', broker_id='9A95', account_id='0504486', signed=True), price_type=<StockPriceType.LMT: 'LMT'>, order_type=<FuturesOrderType.ROD: 'ROD'>, order_lot=<TFTStockOrderLot.IntradayOdd: 'IntradayOdd'>) status=OrderStatus(id='204dbcd3', status=<Status.PendingSubmit: 'PendingSubmit'>, status_code='0', order_datetime=datetime.datetime(2021, 10, 13, 23, 14, 58), deals=[])

取消单我们先前说过了就不再写一次,但我们可以针对盘中零售进行改量的委托设定修改。(盘中零股仅可改量,不可改价)

api.update_order(trade=trade_2409, qty=250)
api.update_status(api.stock_account)
print(trade_2409)

Order与Deal的Callback回呼函式设定

除了昨天文章提到的报价(Quote) Callback,在Order相关的状态变更时,也会有对应的callback可设定。

def place_cb(stat, msg):
    print('my_place_callback')
    print(stat, msg)

api.set_order_callback(place_cb)

而相关的内容可以直接参考Shioaji官网API文件内容

後记

我们的Shioaji超入门系列,就到这边告一段落了!希望能快速帮助到相入门的人可以用最短的时间理解。
果然还是有达到自己的承诺(虽然搞的很累),把两个主题都带到了!

明天就是我的铁人赛最後一天了,终於!! 最後一篇,敬请期待罗。(当然,并没有人在期待…)


<<:  [Android Studio 30天自我挑战] Timer计时器练习

>>:  Day29 实作信件发送功能(2)

冒险村21 - draper

21 - Draper 上篇 Design Pattern(1) - Decorator 简单的介绍...

Day14 金银满堂-北方名菜合菜戴帽

合菜玳瑁是有名的北方菜,刚好看到读书会书友外带了好吃的合菜戴帽,把合菜戴帽比喻成蛋皮界的星海罗盘让我...

Day 14. Tutorial: Create a scene flow - 10. Challenge Answer

如果你也有跟着教程做的话,第10节有个练习,可以来跟我交流一下答案,我也不知道我的写法是不是好的,但...

Leetcode 挑战 Day 16 [231. Power of Two]

231. Power of Two 今天我们一起挑战leetcode第231题Power of Tw...

Day 23. Zabbix 通知设定 - Custom alertscripts - Line

在 SMTP Mail 之後,今天要跟大家介绍第二种通知方式 Custom alertscripts...