投資の成功のコツは
「マイルール」を愚直に守ること
誰が言い始めたが調べても分からないが、一般常識になっている格言だ。
そんな、私自身には、次の格言も送っておく。
株のマイルールは作って満足、守って完成
はい。今まで、作って満足してました・・・。
でも、システムトレードはルールを作り、ルールを守るための施策だったはず。
【目的】 投資で安定的に長期的な利益を得ること
【問題】 ルールがなく心理的に弱く投資で勝てない。継続的にコツコツが苦手
【課題】 再現性があり、精神的に負担のない投資手法確立
【施策】 完全自動な環境を構築し、バックテストによる手法の妥当性確認後に実践を行う
8月には実践投入という計画だったので、一ヶ月程度遅延しているが準備をしていく。
因みに、過去2回、自動化に挑戦しています・・・。
でも、そもそも「ストラテジー」無いのに自動化をしたため、何も発展がなかった。
今回は今まで作ったストラテジーを動作させる事を目標に、少しずつ自動化の環境を整えよう。
まずは、ゴールデンウィークに作っていたライブラリを今更公開しておく。
まず、どのライブラリを使うべきか?
近々、証券会社がREST APIを公開される可能性があるが、独自で作るなら有名なライブラリには次のものがある。
ライブラリ名 | 実行速度 | コード量 |
---|---|---|
requests | 早い | 多い |
Mechanize | 普通 | 少ない |
Selenium | やや遅い | 少ない |
SBI証券では、聞くところによると1.5秒以内の高速処理が出来ない制約がついているらしい。
であればコード量との費用対効果より「Mechanize」を使用する。
Seleniumは、一銘柄の購入まで22秒かかったと書かれている記事も存在したし、有名すぎて今更感
SBIcommライブラリを使ってみる&修正方法
このライブラリはSBI証券を経由して、株の売買を行う。
GitHub上に存在するのを発見した。
必要なライブラリは次の通り。
1 2 3 |
$ pip install python-dateutil $ pip install mechanize $ pip install lxml |
コードをPython3化して動作するようにする
Python3対応と、幾つかのライブラリの挙動が違うため、その部分の修正を行う。
修正したコードはGitHubに置いた。
差分は次のとおり。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 |
--- SBIcomm/SBIcomm.py.bak.py 2020-07-04 07:43:34.460822800 +0900 +++ SBIcomm/SBIcomm.py 2020-07-04 08:40:48.968412000 +0900 @@ -7,7 +7,7 @@ from dateutil.relativedelta import * import mechanize import urllib -import cookielib +import http.cookiejar from lxml import html _NUM_PAT = re.compile(r'\d+\.*') @@ -217,7 +217,7 @@ return "//table/tr/td/table" + path def _add_url_param(self, url, params): - return url + '?' + urllib.urlencode(params) + return url + '?' + urllib.parse.urlencode(params) def __init__(self, username, password, proxy=None, proxy_user=None, proxy_password=None): @@ -248,7 +248,7 @@ br.set_proxies(self._proxy) br.add_proxy_password(self._proxy_user, self._proxy_password) - cj = cookielib.LWPCookieJar() + cj = http.cookiejar.LWPCookieJar() br.set_cookiejar(cj) br.addheaders = [('User-agent', 'Chrome')] #br.set_debug_http(True) @@ -305,8 +305,8 @@ return date, (start_price, end_price, max_price, min_price, volume, gain_loss, gain_loss / (end_price - gain_loss)) except: - print traceback.format_exc() - print "Cannot Get Value! %s" % code + print(traceback.format_exc()) + print("Cannot Get Value! %s" % code) return datetime.date.today(), None def get_market_index(self, index_name='nk225'): @@ -355,8 +355,8 @@ return (start_price, end_price, max_price, min_price, gain_loss, gain_loss / (end_price - gain_loss)) except: - print traceback.format_exc() - print "Cannot Get Value! %s" % index_name + print(traceback.format_exc()) + print("Cannot Get Value! %s" % index_name) return (None, None, None, None, None, None) def get_nikkei_avg(self): @@ -429,8 +429,8 @@ records["diff_ratio"] = _extract_plus_minus_num(l[9].text) records["balance_ratio"] = eval(_extract_num(l[10].text)) except: - print traceback.format_exc() - print "Cannot Get Value! %s" % code + print(traceback.format_exc()) + print("Cannot Get Value! %s" % code) return records def buy_order(self, code, quantity=None, price=None, @@ -495,7 +495,7 @@ tds = path_list[0].xpath("descendant::td") return {'code': code, 'number': int(_extract_num(tds[3].text)), 'state': tds[1].text} except: - raise ValueError, "Cannot get info %s!" % order_num + raise ValueError("Cannot get info %s!" % order_num) def get_purchase_margin(self, wday_step=0): """ @@ -554,7 +554,7 @@ day = calc_workday(today, limit) br["limit"] = [day.strftime("%Y/%m/%d")] else: - raise ValueError, "Cannot setting 6 later working day!" + raise ValueError("Cannot setting 6 later working day!") br["sasinari_kbn"] = [order] def _confirm(self, br): @@ -572,9 +572,9 @@ br.select_form(nr=0) try: req = br.click(type="submit", nr=0) - print "Submitting Order..." + print("Submitting Order...") except: - raise RuntimeError, "Cannot Order!" + raise RuntimeError("Cannot Order!") for _ in range(5): try: @@ -585,7 +585,7 @@ return path_list[0].attrib['value'] except: error_msg = u"処理がタイムアウトしました。" - raise ValueError, error_msg + raise ValueError(error_msg) def _init_open(self, page): """ @@ -609,5 +609,5 @@ username = raw_input("username: ") password = getpass.getpass("password: ") sbi = SBIcomm(username, password) - print sbi.get_value("6758") + print(sbi.get_value("6758")) sbi.buy_order("6752", 100, 614, inv=True, trigger_price=612) # 買い注文 |
そんなに難しいことはしていない。
ライブラリに対する修正は最低限としておく。
売買部分のソースコード
ここから本題。
SBIcommの修正は最低限にして、SBIcommを継承することで追加のライブラリ機能を実現することにする。
今回は、ライブラリ拡張方法を理解するため、
- ログインして最新の現在値を返す
という部分を実装する。
最終的には Protra等で「翌日買うべき銘柄」を抽出した後に購入判定を行うため、銘柄リストの読み込みも用意した。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 |
# -*- coding: utf-8 -*- import re import os import sys import time import array import subprocess from datetime import datetime, timedelta ## SBI証券での売買 from SBIcomm import SBIcomm USERNAME = "<ユーザ名>" PASSWORD = "<パスワード>" FILE = "<ファイル名>" class SBIext(SBIcomm.SBIcomm): # バックアップサイトは4/3秒つまり、1.333秒/回 を超えるリクエストは弾く # どれだけ頑張っても1.5秒以上の発注はできない def buy_order_by_value( self, code, quantity=None, price=None, limit=0, order=SBIcomm.ORDER.LIM_UNC, category=SBIcomm.CATEGORY.SPC, inv=False, comp=SBIcomm.COMP.MORE, trigger_price=None): """ 現在の株価と購入希望額を確認して低ければ購入 :param str code: 企業コード """ br, res = self._init_open(self._add_url_param(self.pages['buy'], {"ipm_product_code": code, "market": "TKY", "cayen.isStopOrder": str(inv).lower()})) # 取得したhtmlを解析して日付と価格を求める doc = SBIcomm.html.fromstring(res.read().decode(self.ENC)) try: path_list = doc.xpath("//font[@class=\"ltext\"]") date = doc.xpath("//form/text()") for item in date: if ('(' in item): item = re.findall('([0-9]+/[0-9]+)\xa0([0-9]+:[0-9]+)\xa0', item) break current_price = float(path_list[0].text.replace(",","")) current_price = int(current_price) # 現在の株価と比較 print(item, (current_price), flush=True) if (int(current_price) < int(price)): # 株価の売買 br.select_form(nr=0) self._set_order_propaty(br, quantity, price, limit, order) print("[BUY] code="+code+" quantity="+quantity, flush=True) if inv == True: br["trigger_zone"] = [comp] br["trigger_price"] = str(trigger_price) br["hitokutei_trade_kbn"] = [category] br["password"] = self._password return item, (current_price) else: return item, (current_price) except ValueError: print("Cannot float! %s" % code) return SBIcomm.datetime.date.today(), None except: print("Cannot Get Value! %s" % code) return SBIcomm.datetime.date.today(), None def _init_open(self, page): """ ユーザのパスワードを送信してpageをオープンする """ br = self.submit_user_and_pass() res = br.open(page) return br, res # 購入銘柄毎に購入処理 def buySellStock(): buylist = list() selllist = list() # ファイルの呼び出し stock_names = [] with open(FILE, 'r',encoding="utf-8") as f: rows = f.readlines() for row_str in rows: row = row_str.split(",") stock_names.append(row) start = time.time() # SBI証券ログイン sbi = SBIext(USERNAME, PASSWORD) for stock_id in stock_names: code = stock_id[0] if (code.isdecimal()): # 整数判定 price = stock_id[1] quantity = stock_id[2] # 現在の株価チェック date, value = sbi.buy_order_by_value(code, quantity, price) elapsed_time = time.time() - start print ("elapsed_time:{0}".format(elapsed_time) + "[sec]") return True #以下、メインルーチン if __name__ == "__main__": # 銘柄売買 if (not buySellStock()): print("銘柄売買失敗。中断") sys.exit() |
ファイルのフォーマットは次のとおりです。
(銘柄番号),(購入価格),(購入数)
例)
1 2 3 4 |
2158,698,100 2315,36,200 4736,710,300 6635,484,300 |
実行結果
結果表示は至ってシンプルだ。
日付と時間、そして株価が返ってくる。
1 2 3 |
# python sbi_operation.py [('08/24', '15:00')] 825 elapsed_time:0.9035325050354004[sec] |
リアルタイムな株価が取得できていれば成功だ。
まとめ
ログインして発注まで行うことができた。
SBI証券では、日取りの信用取引による売買の方が手数料が安いので、少しずつ拡張していくことにする。
少し飽きてるので、飽きないように継続実装しなきゃなぁ・・・・。
Selenium編も執筆した。