この記事には広告を含む場合があります。
記事内で紹介する商品を購入することで、当サイトに売り上げの一部が還元されることがあります。
株式投資における投資手法には、ファンダメンタルズ分析とテクニカル分析があります。
ファンダメンタルズ分析は、企業の本質的な価値や経済状況などを分析して投資する手法です。
一方、テクニカル分析は、過去の値動きをチャートで表してトレンドやパターンを把握し、今後の株価を予想する手法です。
プログラミングによる自動売買は、後者のテクニカル分析と相性がいいです。過去のチャートをより優位性のある戦略を見つけて、自動売買プログラムに実装しましょう。
なお注意点として、空売りはせず、現物取り引きのプログラムを作成します。
TreadingViewで売買戦略のバックテストをする方法
今回は、TradingViewのバックテストの機能を使って戦略を決めたいと思います。
TradingViewとは、アメリカのTradingView Inc.が開発した、世界中で5,500万人以上のトレーダーに利用されている人気のチャートツールです。豊富な機能と使いやすさから、初心者から上級者まで幅広いトレーダーに支持されています。
TradingViewではバックテストする機能・ツールを「ストラテジーテスター」と呼びます。無料版で利用可能なヒストリカルバーは5,000本となっています。有料版では最大20,000本まで検証できます。
ストラテジーテスターの使い方
バックテストをする銘柄ですが、最低購入金額が低く、取引量の多いNTT(日本電信電話)で行います。
いくつかのストラテジーを検証しましたが、RSIストラテジーのパフォーマンスが良かったので、こちらで解説を進めます。
今回は30分です。
検証したタイミングでは、RSIが30以上で買い、70以下で売り、期間の設定が15の時が最もパフォーマンスが良かったです。
併せてプロパティの注文サイズを1単元の100に変更しておいてもいいです。
バックテストの結果の見方
パフォーマンスサマリーをクリックするとバックテストの結果を確認できます。
まずは信用取引をせず、現物取引でプログラムを作成するのでロングの純利益を見ると、3,550円のプラスと言う結果になりました。プロフィットファクターも1.679となっているのでまずまずかと思います。
TreadingViewのストラテジーテスターでは色々なストラテジーで検証できるので、他のストラテジーも試してみてください。
また、バックテストの結果について次の2点について注意してください。
- スリッページが発生する可能性がある
シミュレーション通りの価格で約定しないかもしれません。 - カーブフィッティングをしない
過去のデータにパラメーターを最適化しすぎても、未来のデータでバックテストの結果通りに行かないケースがあります。
プログラムのフローチャート
フローチャートとは、プログラムの処理の流れをイメージ化したもので、プログラマーを始めとしたプログラムの設計者にとっては必要不可欠なツールです。
RSI戦略のフローチャートは次のようにします。
- ポジションの確認をする。
- 価格データを取得する。
- RSIの計算をして、30以下から30以上になった場合は買い、70以上から70以下になった場合は売りのシグナルを出す。
- シグナルが発生していなければ②へ戻る。発生していれば次へ。
- 成行き注文を出す。
- ポジションを確認する。
- 30分待つ。
サンプルコード
上記のロジックでプログラムを作成しますが、次の点についてご了承ください。
kabuステーション®APIでは、諸事情で時間足の提供はしておらず、今後も対応が難しいようです。今回はyfinanceで30分足を取得しますが、最新のデータは20分遅れで配信されます。リアルタイムで時間足を取得する方法を探しましたが、有料のサービスを利用するか、約定履歴をデータベースに保存して時間足を作製するしかなさそうです。後者については、別の機会に解説できればと思います。
import time
import urllib.request
import urllib.error
import pprint
import json
import yfinance as yf
import talib
# 設定項目
api_password = 'YOUR_PASSWORD'
login_password = 'YOUR_PASSWORD'
symbol = '9432'
qty = 100
signal = None
# トークンを発行する関数
def generate_token():
obj = {'APIPassword': api_password}
json_data = json.dumps(obj).encode('utf8')
url = 'http://localhost:18080/kabusapi/token'
req = urllib.request.Request(url, json_data, method='POST')
req.add_header('Content-Type', 'application/json')
try:
with urllib.request.urlopen(req) as res:
content = json.loads(res.read())
token_value = content.get('Token')
except urllib.error.HTTPError as e:
print(e)
return token_value
# 残高照会する関数
def check_positions(token):
url = 'http://localhost:18080/kabusapi/positions'
params = {'product': 1} # product - 0:すべて、1:現物、2:信用、3:先物、4:OP
params['symbol'] = symbol # symbol='xxxx'
params['side'] = '2' # 1:売、2:買
params['addinfo'] = 'false' # true:追加情報を出力する、false:追加情報を出力しない ※追加情報は、「現在値」、「評価金額」、「評価損益額」、「評価損益率」を意味します
req = urllib.request.Request('{}?{}'.format(url, urllib.parse.urlencode(params)), method='GET')
req.add_header('Content-Type', 'application/json')
req.add_header('X-API-KEY', token)
try:
with urllib.request.urlopen(req) as res:
print(res.status, res.reason)
for header in res.getheaders():
print(header)
print()
content = json.loads(res.read())
pprint.pprint(content)
except urllib.error.HTTPError as e:
print(e)
content = json.loads(e.read())
pprint.pprint(content)
except Exception as e:
print(e)
if not content or content[0]['LeavesQty'] == 0.0:
check_position = False
check_positions_side = None
else:
check_position = True
if content[0]['Side'] == '2':
check_positions_side = 'BUY'
return check_position, check_positions_side
# ローソク足を取得する関数
def get_price_data():
symbol_data = yf.download(symbol + '.T', period='5d', interval='30m')
return symbol_data
# シグナルを判定する関数
def entry_signal(df):
df['RSI'] = talib.RSI(df['Close'], timeperiod=14)
if df.iat[-2, 6] <= 30 < df.iat[-1, 6]:
signal = 'BUY'
print('買いのシグナルが発生しました')
elif df.iat[-2, 6] >= 70 > df.iat[-1, 6]:
signal = 'SELL'
print('売りのシグナルが発生しました')
else:
signal = None
return signal
# 買い注文を出す関数
def kabusapi_sendorder_cash(token):
obj = {'Password': login_password,
'Symbol': symbol,
'Exchange': 1,
'SecurityType': 1,
'Side': '2',
'CashMargin': 1,
'DelivType': 2,
'FundType': 'AA',
'AccountType': 2,
'Qty': qty,
'FrontOrderType': 10,
'Price': 0,
'ExpireDay': 0
}
json_data = json.dumps(obj).encode('utf-8')
url = 'http://localhost:18080/kabusapi/sendorder'
req = urllib.request.Request(url, json_data, method='POST')
req.add_header('Content-Type', 'application/json')
req.add_header('X-API-KEY', token)
try:
with urllib.request.urlopen(req) as res:
print(res.status, res.reason)
for header in res.getheaders():
print(header)
print()
content = json.loads(res.read())
pprint.pprint(content)
except urllib.error.HTTPError as e:
print(e)
content = json.loads(e.read())
pprint.pprint(content)
except Exception as e:
print(e)
# 売り注文を出す関数
def kabusapi_sendorder_cash_close(token):
obj = {'Password': login_password,
'Symbol': symbol,
'Exchange': 1,
'SecurityType': 1,
'Side': '1',
'CashMargin': 1,
'DelivType': 0,
'FundType': ' ',
'AccountType': 2,
'Qty': qty,
'FrontOrderType': 10,
'Price': 0,
'ExpireDay': 0
}
json_data = json.dumps(obj).encode('utf-8')
url = 'http://localhost:18080/kabusapi/sendorder'
req = urllib.request.Request(url, json_data, method='POST')
req.add_header('Content-Type', 'application/json')
req.add_header('X-API-KEY', token)
try:
with urllib.request.urlopen(req) as res:
print(res.status, res.reason)
for header in res.getheaders():
print(header)
print()
content = json.loads(res.read())
pprint.pprint(content)
except urllib.error.HTTPError as e:
print(e)
content = json.loads(e.read())
pprint.pprint(content)
except Exception as e:
print(e)
# メイン処理
token_value = generate_token()
position, side = check_positions(token_value)
while True:
# ポジションがある場合
if position is True and side == 'BUY':
price = get_price_data()
signal = entry_signal(price)
# シグナルが発生した時
if signal == 'SELL':
kabusapi_sendorder_cash_close(token_value)
signal = None
time.sleep(10)
position, side = check_positions(token_value)
time.sleep(1800)
else:
time.sleep(60)
# ポジションがない場合
else:
price = get_price_data()
signal = entry_signal(price)
# シグナルが発生した時
if signal == 'BUY':
kabusapi_sendorder_cash(token_value)
signal = None
time.sleep(10)
position, side = check_positions(token_value)
time.sleep(1800)
else:
time.sleep(60)
ポイントの解説
それではポイントとなるところの解説をしていきます。
必要なライブラリのインポート
import time
import urllib.request
import urllib.error
import pprint
import json
import yfinance as yf
import talib
まずは必要なライブラリをインポートします。
yfinanceとTA-Libのインストールや使い方については、次の記事を参考にしてください。
設置項目
# 設定項目
api_password = 'YOUR_PASSWORD'
login_password = 'YOUR_PASSWORD'
symbol = '9432'
qty = 100
signal = None
api_password
は、kabuステーション®で設定したパスワードを入力してください。
login_password
は、auカブコム証券のログインするパスワードを入力してください。
api_password
とlogin_password
は、別物になるので入力する際は注意してください。
symbol
は、銘柄の証券コードです。別の株の取り引きをしたい場合は変更してください。
qty
は、購入する株数です。こちらも200株や300株などで購入したい場合は変更してください。
残高照会する関数
# 残高照会する関数
def check_positions(token):
url = 'http://localhost:18080/kabusapi/positions'
params = {'product': 1} # product - 0:すべて、1:現物、2:信用、3:先物、4:OP
params['symbol'] = symbol # symbol='xxxx'
params['side'] = '2' # 1:売、2:買
params['addinfo'] = 'false' # true:追加情報を出力する、false:追加情報を出力しない ※追加情報は、「現在値」、「評価金額」、「評価損益額」、「評価損益率」を意味します
req = urllib.request.Request('{}?{}'.format(url, urllib.parse.urlencode(params)), method='GET')
req.add_header('Content-Type', 'application/json')
req.add_header('X-API-KEY', token)
try:
with urllib.request.urlopen(req) as res:
print(res.status, res.reason)
for header in res.getheaders():
print(header)
print()
content = json.loads(res.read())
pprint.pprint(content)
except urllib.error.HTTPError as e:
print(e)
content = json.loads(e.read())
pprint.pprint(content)
except Exception as e:
print(e)
if not content or content[0]['LeavesQty'] == 0.0:
check_position = False
check_positions_side = None
else:
check_position = True
if content[0]['Side'] == '2':
check_positions_side = 'BUY'
return check_position, check_positions_side
公式のサンプルコードを引用して、保有銘柄の確認をします。レファレンスのResponse samplesを確認すると、LeavesQty
が保有数量、Side
が売買区分となっています。
ローソク足を取得する関数
# ローソク足を取得する関数
def get_price_data():
symbol_data = yf.download(symbol + '.T', period='5d', interval='30m')
return symbol_data
yfinance
で30分のローソク足を5日分取得します。取得できるデータは、20分遅れになるということは注意してください。
シグナルを判定する関数
# シグナルを判定する関数
def entry_signal(df):
df['RSI'] = talib.RSI(df['Close'], timeperiod=15)
if df.iat[-2, 6] <= 30 < df.iat[-1, 6]:
signal = 'BUY'
print('買いのシグナルが発生しました')
elif df.iat[-2, 6] >= 70 > df.iat[-1, 6]:
signal = 'SELL'
print('売りのシグナルが発生しました')
else:
signal = None
return signal
TA-Lib
でRSIを計算しています。
そして、RSIの値が30以下から30以上になった場合は買い、逆に70以上から70以下ないなった場合は売りのシグナルを出します。
買い注文を出す関数と売り注文を出す関数
# 買い注文を出す関数
def kabusapi_sendorder_cash(token):
obj = {'Password': login_password,
'Symbol': symbol,
'Exchange': 1,
'SecurityType': 1,
'Side': '2',
'CashMargin': 1,
'DelivType': 2,
'FundType': 'AA',
'AccountType': 2,
'Qty': qty,
'FrontOrderType': 10,
'Price': 0,
'ExpireDay': 0
}
json_data = json.dumps(obj).encode('utf-8')
url = 'http://localhost:18080/kabusapi/sendorder'
req = urllib.request.Request(url, json_data, method='POST')
req.add_header('Content-Type', 'application/json')
req.add_header('X-API-KEY', token)
try:
with urllib.request.urlopen(req) as res:
print(res.status, res.reason)
for header in res.getheaders():
print(header)
print()
content = json.loads(res.read())
pprint.pprint(content)
except urllib.error.HTTPError as e:
print(e)
content = json.loads(e.read())
pprint.pprint(content)
except Exception as e:
print(e)
# 売り注文を出す関数
def kabusapi_sendorder_cash_close(token):
obj = {'Password': login_password,
'Symbol': symbol,
'Exchange': 1,
'SecurityType': 1,
'Side': '1',
'CashMargin': 1,
'DelivType': 0,
'FundType': ' ',
'AccountType': 2,
'Qty': qty,
'FrontOrderType': 10,
'Price': 0,
'ExpireDay': 0
}
json_data = json.dumps(obj).encode('utf-8')
url = 'http://localhost:18080/kabusapi/sendorder'
req = urllib.request.Request(url, json_data, method='POST')
req.add_header('Content-Type', 'application/json')
req.add_header('X-API-KEY', token)
try:
with urllib.request.urlopen(req) as res:
print(res.status, res.reason)
for header in res.getheaders():
print(header)
print()
content = json.loads(res.read())
pprint.pprint(content)
except urllib.error.HTTPError as e:
print(e)
content = json.loads(e.read())
pprint.pprint(content)
except Exception as e:
print(e)
公式のサンプルコードを参考にパラメーターを設定しています。今回は成行ですが、指値や逆指値の注文をした場合は下記の表を参考にしてください。パラメーターは正しく設定しないとエラーになるので注意してください。
定義値 | 説明 | ”Price”の指定 |
---|---|---|
10 | 成行 | 0 |
13 | 寄成(前場) | 0 |
14 | 寄成(後場) | 0 |
15 | 引成(前場) | 0 |
16 | 引成(後場) | 0 |
17 | IOC成行 | 0 |
20 | 指値 | 発注したい金額 |
21 | 寄指(前場) | 発注したい金額 |
22 | 寄指(後場) | 発注したい金額 |
23 | 引指(前場) | 発注したい金額 |
24 | 引指(後場) | 発注したい金額 |
25 | 不成(前場) | 発注したい金額 |
26 | 不成(後場) | 発注したい金額 |
27 | IOC指値 | 発注したい金額 |
30 | 逆指値 | 指定なし ※AfterHitPriceで指定ください |
メイン処理
# メイン処理
token_value = generate_token()
position, side = check_positions(token_value)
while True:
# ポジションがある場合
if position is True and side == 'BUY':
price = get_price_data()
signal = entry_signal(price)
# シグナルが発生した時
if signal == 'SELL':
kabusapi_sendorder_cash_close(token_value)
signal = None
time.sleep(10)
position, side = check_positions(token_value)
time.sleep(1800)
else:
time.sleep(60)
# ポジションがない場合
else:
price = get_price_data()
signal = entry_signal(price)
# シグナルが発生した時
if signal == 'BUY':
kabusapi_sendorder_cash(token_value)
signal = None
time.sleep(10)
position, side = check_positions(token_value)
time.sleep(1800)
else:
time.sleep(60)
メイン処理では、まず初めにトークン発行します。次に保有銘柄の有無を確認しています。
ポジションがない場合は、1分おきにRSIを計算して、買いのシグナルが発生すれば買い注文を出して、ポジションの確認をして30分待ちます。売りの場合も同様です。
おわりに
TradingViewのバックテスト機能の使い方と株の自動売買のプログラムについて解説しました。
TradingViewのストラテジーテスターでは、色々なストラテジーでバックテストをすることができます。パラメーターや時間足の組み合わせは無数にあるので、勝てる戦略を探してみてください。
そして、勝てる戦略をプログラムで再現できるように挑戦してみてください。
信用格付は主要ネット証券No.1「AA」MUFGグループならではの信頼感
- APIによる株の全自動取り引きに対応
- 「kabuステーション®」や「カブナビ®」など、用途に合わせた各種の自社開発ツールを利用可能
- 1日100万円まで手数料無料
\無料登録はこちらから!/