IB証券(インタラクティブ・ブローカーズ証券)にてCFDのダウ30と日経225をMACDでデイトレードするシストレを紹介します。Pythonで構築したシステムは、コピペだけでほとんどそのままシストレを開始できます。状況次第ですが年利10%は固いです。
本ページのシストレについて、IB証券でこのCFDは取引できないことが判明しました。
先物であれば取引ができますので、先物で再検討します。少々お待ち下さい。
前提
投資の考え方
今回の投資手法の指針は、以下のように考えました。
「勝つためには負けないこと」
馬鹿じゃねえか。当たり前だろう。という皆様、私もそう思いました。
しかし、ある時、あるハイレバでガンガン儲けているYouTubeの動画で言っていた以下のことを思い出しました。
「FXのUSD/JPYは、極端な価格のポジションでは無く、且つレバレッジが大きく無ければいつか勝てる」
そうだ!これだ!
ということは、トレンドが並行か、右肩上がりの買い、もしくは右肩下がりの売りを仕掛ければ、いつかは利食いできる。
でも、これに近いことというか、これをやっていたのですが、それでも負けました。というか大負けです。
VIXという恐怖指数の投資ですが、いつかは世の中は安定するから下がるという投資です。
上記の条件に当てはめると、13ドルくらいの売り(極端というほどでもないかと。。)、レバレッジというより70ドルに暴騰してもいいようにたっぷりの証拠金を積んでいました。が、なんと歴代1位になる高騰を見せ、あっさりと玉砕しました。。。
これらを踏まえ、今回の投資の方向性の考え方です。
- DOW30、日経225を買いで仕掛ける。
- 現在の価格は最高値の8割くらい。あがる余地は十分ある。
- 下がるリスクも最大2割と考える。(3月に最高値の6割くらいになったから。
- 買いのみ。売りはやらない。
- 今よりは世の中良くなると考えるから。
- インジゲーターMacdの1分足と5分足が買いクロスになったら買い。
- 根拠は弱いのですが、私が手動でトレードしているときに、概ねその動きに合わせると利益が出そうな気がしたからです。
- 上昇トレンドの時だけトレードする。
- 下げ相場で逆張りはうまくいかないです。
- 上げ相場の下げを狙うスタンスです。
目標
同期間のそれぞれの株価上昇率より10%以上、上回るパフォーマンスを出せること。
(特に意味はないのですが、同じパフォーマンスだったらシストレでなく普通に買えばいいだけになります。ではどれくらい以上のパフォーマンスがいいのかというと、これは個人の感覚かと思います。ということで10%くらいはなんとかしたいと。
シストレの投資手法
金融商品 | CFD(ダウ30、日経225) | ||
金融機関 | IB証券(インタラクティブ・ブローカーズ証券) | ||
資金 | DOW30:5万米ドル、日経225:500万円(これらはバックテスト用という意味です。 | ||
売買 | 買いのみ | ||
売買数量 | DOW30:5枚、日経225:500枚の固定(複利効果を使わず | ||
投資判断 | 買い | 発注 | インジゲーターMacdの1分足と5分足、日足が買いクロス(バックテストの結果、日足を含めない方がパフォーマンスが良かった。 |
利確 | インジゲーターMacdの1分足か5分足のどちらかが買いクロスを外れた場合、且つ利益が100pips以上 | ||
損切 | しない | ||
売り | 発注 | ー | |
利確 | ー | ||
損切 | ー |
投資対象の商品情報
IB証券のCDFはイギリスのIB証券の商品となっており、欧州証券市場監督局(ESMA)によるルールが適用されるようです。
ダウ30 | 日経225 | |
シンボル | IBUS30 | IBJP225 |
通貨 | USD | JPY |
取引数量(枚 | 1〜20 | 1〜850 |
手数料*片道 (最低手数料) | 0.01% ($1.00) | 0.01% (¥40) |
取引時間 (BST、英国時間 | 通常取引時間 08:00~21:00 取引可能時間 03:00~16:00 | 通常取引時間 01:00~07:10 取引可能時間 20:00~02:10 |
*手数料は約定レートに対してのパーセントです。片道となっています。決済時の約定レートで手数料が発生します。
オーバーナイトの借入金利はベンチマークの± 1.5%となっています。
とあるので注意しましょう。現時点では買いの場合は金利をもらえます。
その他、詳しくは以下を参照してください。
証拠金は以下を参照してください。
システム要件
開発環境
- OS:macOS Carolina 10.15.4
- 言語:Python 3.7.2
- 開発ツール
- バックテスト用システム:Jupyter Notebook 1.0.0
- 本番用システム:Visual Studio Code 1.45.1
本番環境
- OS:Windows Server 2012 R2(AWS
- 言語:Python 3.7.4
あれ。Pythonのバージョンがあっていませんね。。。気にしない気にしない。。。
バックテスト
システム
以下はGithubにアップしていますので、ダウンロードして利用してください。
(左下のファイル名をクリックするとGithubのサイトに遷移します。「Download ZIP」をクリックするとダウンロードできます。)
過去データはIB証券からダウンロードしたものを利用しているのですが、それは再配布禁止とIB証券の規約になっていたと思いますので、手間ですが、ご自身でダウンロードしてください。
過去データは、1分足、5分足、日足を使っています。
ファイル名は投資対象のシンボル名+足(1分足:M1、5分足:M5、日足:D1)になっています。
例)IBJP225_D1.csv
ソースにゴミがあったりしますが、すいません。
注意
- バックテストに手数料は含まれていません。
- スプレッドは固定でテストしていますが、実際には変動します。
結果
当初の投資手法の通り
シンボル | 投資回数 | 期間日数 | 回数/日数 | 損益PIPS | 期間PIPS | 損益/期間PIPS | 資金残 | 上昇率 | 利益回数 | 損失回数 | 勝率 |
IBUS30 | 7 | 22 | 31.80% | 1120.85 | 627 | 178.80% | 55,604 | 111.20% | 7 | 0 | 100% |
IBJP225 | 13 | 18 | 72.20% | 1085.03 | 1112 | 97.60% | 5,542,515 | 110.90% | 12 | 0 | 100% |
投資判断から日足のmacdの判断を除く
シンボル | 投資回数 | 期間日数 | 回数/日数 | 損益PIPS | 期間PIPS | 損益/期間PIPS | 資金残 | 上昇率 | 利益回数 | 損失回数 | 勝率 |
IBUS30 | 12 | 22 | 54.50% | 1637.27 | 627 | 261.10% | 58,186 | 116.40% | 11 | 0 | 100% |
IBJP225 | 20 | 18 | 111.10% | 1476.42 | 1112 | 132.80% | 5,738,210 | 114.80% | 19 | 0 | 100% |
日足のmacdの判断を除いたものに、投資判断に1分足のmacdが0以上の場合に実行するを追加
シンボル | 投資回数 | 期間日数 | 回数/日数 | 損益PIPS | 期間PIPS | 損益/期間PIPS | 資金残 | 上昇率 | 利益回数 | 損失回数 | 勝率 |
IBUS30 | 12 | 22 | 54.50% | 1586.05 | 627 | 253.00% | 57,930 | 115.90% | 11 | 0 | 100% |
IBJP225 | 20 | 18 | 111.10% | 1457.4 | 1112 | 131.10% | 5,728,700 | 114.60% | 19 | 0 | 100% |
まとめ
- 投資方針に下降トレンド時に対策として日足のmacdの判断を付けたがパフォーマンスが落ちた。
(上記の投資手法など訂正済み。ソースには残骸があります。
以下は、投資判断から日足のmacdの判断を除いた場合です。
- 約1ヶ月間のテストとなったがパフォーマンスは月利15%ほどだった。
- 期間の株価(CFD)の上昇率より、ダウ30は約2.6倍、日経は約1.3倍となった。
- 日経225よりダウ30の方がパフォーマンスが良かった。多分、ボラティリティが大きいということだと思います。
- 作りながら1分足のMACDが1以上という追加の条件を考えたが、パフォーマンスは少し落ちた。
(上記のソースに残骸があります。
課題
- 長期間などのテストを再実施する。
- 上記ともつながるが、今回のようなショックで天井で掴むリスクがある。この回避方法はないか。
- 期間中の最大の含み損はいくらになるのか。またロスカットされないのか。
今後の方針
基本的に良い結果を得られたので、課題事項を確認した上で、本番に入りたいと思います。
課題の対応
以下の2つをバックテストしました。
- 長期間などのテストを再実施する。
- 期間中の最大の含み損はいくらになるのか。またロスカットされないのか。
2020年の年初から
シンボル | 投資回数 | 期間日数 | 回数/日数 | 損益PIPS | 期間PIPS | 損益/期間PIPS | 資金残 | 上昇率 | 利益回数 | 損失回数 | 勝率 | 含み損PIPS | 左記の価格 |
IBUS30 | 12 | 101 | 11.90% | 1262.84 | -4008 | -31.50% | 56,314 | 112.60% | 11 | 0 | 100% | -11208 | 29460 |
IBJP225 | 3 | 78 | 3.80% | 139.02 | -3680 | -3.80% | 5,069,510 | 101.40% | 2 | 0 | 100% | -7849 | 24090 |
まとめ
- 今回のコロナショックがあったので想定通りだったが、天井を掴んでそのままトレードがストップ。
- それでも途中までは、DOW30は112.6%の利益を出しています。
最大含み損の時のロスカットの検証
IBUS30 | IBJP225 | |||
建玉時 | 貸付資産を含む資産価値 | 現金 | 0 | 0 |
貸付 | -97,300 | -7,045,000 | ||
金融資産 | 147,300 | 12,045,000 | ||
ELV | 50,000 | 5,000,000 | ||
維持証拠金 | 金融資産 | 147,300 | 12,045,000 | |
維持証拠金率 | 25% | 25% | ||
MM | 36,825 | 3,011,250 | ||
証拠金余力 | ELV – MM | 13,175 | 1,988,750 | |
最大含み損の時 | 貸付資産を含む資産価値 | 現金 | 0 | 0 |
貸付 | -97,300 | -7,045,000 | ||
金融資産 | 91,260 | 8,120,500 | ||
ELV | -6,040 | 1,075,500 | ||
維持証拠金 | 金融資産 | 91,260 | 8,120,500 | |
維持証拠金率 | 25% | 25% | ||
MM | 22,815 | 2,030,125 | ||
証拠金余力 | ELV – MM | -28,855 | -954,625 |
ぶっちぎりのロスカットですね。資金を増やすか、数量を減らさないとダメですね。
2019年のデータ
シンボル | 投資回数 | 期間日数 | 回数/日数 | 損益PIPS | 期間PIPS | 損益/期間PIPS | 資金残 | 上昇率 | 利益回数 | 損失回数 | 勝率 | 含み損 | 左記の価格 |
IBUS30 | 51 | 252 | 20.20% | 5793.6 | 5451 | 106.30% | 78,968 | 157.90% | 50 | 0 | 100% | -2073.01 | 27314.01 |
IBJP225 | 42 | 241 | 17.40% | 4744.14 | 4230 | 112.20% | 7,372,070 | 147.40% | 41 | 0 | 100% | -2226.34 | 22289.8 |
まとめ
- 2019年は上昇トレンドでしたが、株価の上昇よりも本シストレの方が少しに高かったです。10%前後ですね。まぁ目標は達成していると判断しましょう。
- 年利はきたーって感じです。約1.5倍に資産が増えています。
- ダウ30:年利157.9%
- 日経225:年利147.4%
- 回数/日数が20%弱くらいなので週に1回くらいの投資になります。
最大含み損の時のロスカットの検証
IBUS30 | IBJP225 | |||
建玉時 | 貸付資産を含む資産価値 | 現金 | 0 | 0 |
貸付 | -86,570 | -6,144,500 | ||
金融資産 | 136,570 | 11,144,500 | ||
ELV | 50,000 | 5,000,000 | ||
維持証拠金 | 金融資産 | 136,570 | 11,144,500 | |
維持証拠金率 | 25% | 25% | ||
MM | 34,142 | 2,786,125 | ||
証拠金余力 | ELV – MM | 15,858 | 2,213,875 | |
最大含み損の時 | 貸付資産を含む資産価値 | 現金 | 0 | 0 |
貸付 | -86,570 | -6,144,500 | ||
金融資産 | 126,205 | 10,031,500 | ||
ELV | 39,635 | 3,887,000 | ||
維持証拠金 | 金融資産 | 126,205 | 10,031,500 | |
維持証拠金率 | 25% | 25% | ||
MM | 31,551 | 2,507,875 | ||
証拠金余力 | ELV – MM | 8,084 | 1,379,125 |
ロスカットされなかったようです。
しかし、もう少し資金を増やすか、数量を減らさないと危険ですね。
2018年のデータ
シンボル | 投資回数 | 期間日数 | 回数/日数 | 損益PIPS | 期間PIPS | 損益/期間PIPS | 資金残 | 上昇率 | 利益回数 | 損失回数 | 勝率 | 含み損 | 左記の価格 |
IBUS30 | 16 | 251 | 6.40% | 1894.41 | -1620 | -116.90% | 59,472 | 118.90% | 15 | 0 | 100% | -5245 | 26935 |
IBJP225 | 10 | 245 | 4.10% | 831.86 | -3270 | -25.40% | 5,415,930 | 108.30% | 9 | 0 | 100% | -5477 | 24427 |
まとめ
- 株価は、年初からは少し下げていますが、ほぼ横ばいのトレンドだったようです。
- そんな中のシストレですが、どうやら天井を掴んだようで、投資回数が著しく少ないです。
- それまでの利益は良かったようです。
- DOW30:年利18.9%
- 日経225:年利8.3%
最大含み損の時のロスカットの検証
IBUS30 | IBJP225 | |||
建玉時 | 貸付資産を含む資産価値 | 現金 | 0 | 0 |
貸付 | -84,675 | -7,213,500 | ||
金融資産 | 134,675 | 12,213,500 | ||
ELV | 50,000 | 5,000,000 | ||
維持証拠金 | 金融資産 | 134,675 | 12,213,500 | |
維持証拠金率 | 25% | 25% | ||
MM | 33,669 | 3,053,375 | ||
証拠金余力 | ELV – MM | 16,331 | 1,946,625 | |
最大含み損の時 | 貸付資産を含む資産価値 | 現金 | 0 | 0 |
貸付 | -84,675 | -7,213,500 | ||
金融資産 | 108,450 | 9,475,000 | ||
ELV | 23,775 | 2,261,500 | ||
維持証拠金 | 金融資産 | 108,450 | 9,475,000 | |
維持証拠金率 | 25% | 25% | ||
MM | 27,112 | 2,368,750 | ||
証拠金余力 | ELV – MM | -3,338 | -107,250 |
ほんの少しですが、資金を増やすか、数量を減らさないとロスカットされます。
もしかするとそれまでの利益でギリギリですが、ロスカットされなかったかも知れません。
日経でいうと、上記では10万円強足りずにロスカットですが、それまでに40万円ほどの利益を出していますので、助かっていたようです。DOW30は同じですね。
しかし、タイミングの問題で、この頂点の時にこのシストレを初めていたら利益はないのでロスカットされます。アウトです。
課題のまとめ
- 資金の余裕がなかったり、ロスカットされてしまいます。
2020年の初期からのデータで、以下の設定で、軽くバックテストしました。
- DOW30:数量を3枚
29,460米ドル × 3枚 = 88,380米ドル – 現金50,000米ドル = 38,380米ドル × 1.33 = 51,045米ドル
29,460米ドル -11,208pips = 18,252米ドル × 3枚 = 54,756米ドル - 日経225:数量を300枚
24,090円 × 300枚 = 7,227,000円 – 現金5,000,000円 = 2,227,000円 × 1.33 = 2,961,910円
24,090円 -7,849pips = 16,241円 × 300枚 = 4,872,300円
- DOW30:数量を3枚
上記のように減らせば、今回のショックでも耐えうることがわかりました。
- 天井掴みは仕方ないですね。ただ、基本的には右肩上がりだと信じるしかないかも知れません。
あとはいつ始めるかのタイミング次第ですね。
本番
作成しました。こんな感じかなと思います。
本来であれば、バックテストと本番のシステムは同じものがいいと思うのですが、いろいろ面倒なので別に作りました。
その他、所々、ちょっとと思うところはありますが。。。
インポートしている独自のライブラリが2つあります。
import cls_db
import cls_line
:self.access_token
のところは設定してください。
シストレの本体
CFDs
のところで、購入する数量を変更してください。現状はバックテストに合わせています。# System
のコメント以下のところのIPやポート、クライアントIDは自身の仕様に変更してください。# 通知
のコメント以下のところは不要でしたら削除してください。- 通常取引時間のみ取引しています。時間外取引は停止します。
#!/usr/bin/env python3 | |
# -*- coding: utf-8 -*- | |
""" | |
@author: novonovo | |
""" | |
# -------------------------------------------------------------------------- | |
# ライブラリの読み込み | |
import os | |
import sys | |
import traceback | |
import pandas as pd | |
from datetime import datetime, date, timedelta | |
from time import sleep | |
from pytz import timezone | |
import sqlite3 | |
from queue import Queue | |
import threading | |
from ib_insync import * | |
import cls_db | |
import cls_line | |
# インジゲーター | |
from pyti.moving_average_convergence_divergence import moving_average_convergence_divergence as macd | |
from pyti.simple_moving_average import simple_moving_average as sma | |
# -------------------------------------------------------------------------- | |
# タイムゾーン 英国時間 Europe/London | |
# 日経 通常取引時間 01:00~07:10*** BST 取引可能時間 20:00~02:10 BST(英国時間 | |
# ダウ 通常取引時間 08:00~21:00*** BST 取引可能時間 03:00~16:00 BST(英国時間 | |
data_timezone = 'America/New_York' | |
trade_timezone = 'Europe/London' | |
# -------------------------------------------------------------------------- | |
# 設定 | |
# Trading | |
CFDs = { | |
'IBJP225' : { | |
'EXCHANGE' : 'SMART', | |
'CURRENCY' : 'JPY', | |
'UNITS' : 500 | |
}, | |
'IBUS30' : { | |
'EXCHANGE' : 'SMART', | |
'CURRENCY' : 'USD', | |
'UNITS' : 5 | |
} | |
} | |
PROFIT_PIPS = 100 # 利確PIPS | |
# Indicator | |
MACD_SLOW = 26 | |
MACD_FAST = 12 | |
SMA_PERIOD = 9 | |
# System | |
tws_localIP = '127.0.0.1' | |
tws_port = 4002 | |
tws_clientId = 90001 | |
dbpath = 'trade.sqlite' | |
error_count = 0 | |
shut_down_flag = False | |
LONG = 'BUY' | |
SHORT = 'SELL' | |
fmt = "%Y-%m-%d %H:%M:00%z" | |
# -------------------------------------------------------------------------- | |
# インスタンス設定 | |
db_ins = cls_db.db(dbpath) | |
line_ins = cls_line.line() | |
# # -------------------------------------------------------------------------- | |
# キューの設定 | |
line_message = Queue() | |
# -------------------------------------------------------------------------- | |
# DBの確認 | |
if os.path.isfile(dbpath) == False: | |
db_ins.create_db() | |
# -------------------------------------------------------------------------- | |
class trading: | |
# Default settings | |
def __init__(self): | |
self.ib = IB() | |
self.ib.errorEvent += self.ib_onErrorEvent | |
self.flag_notice = False | |
def ib_connect(self): | |
self.ib.connect(tws_localIP, tws_port, clientId=tws_clientId) | |
def ib_onErrorEvent(self, reqId, error_code, error_string, contract): | |
print('error_code!!',error_code, error_string) | |
if error_code == 1100: | |
self.ib.sleep(10) | |
elif error_code == 201: # Canceled order | |
self.ib.sleep(60) | |
def get_bars_df(self, Symbol, BarSizes): | |
# 日足を取得 | |
contract = CFD(Symbol, CFDs[Symbol]['EXCHANGE']) # 銘柄 取引所 | |
bars = self.ib.reqHistoricalData( | |
contract, | |
endDateTime = '', | |
durationStr = '2 D', | |
barSizeSetting = BarSizes, | |
whatToShow = 'MIDPOINT', | |
useRTH = True, | |
formatDate = 1 | |
) | |
df = util.df(bars) | |
return df | |
def get_tick(self, Symbol): | |
contracts = CFD(Symbol, exchange=CFDs[Symbol]['EXCHANGE']) | |
self.ib.qualifyContracts(contracts) | |
self.ib.reqMktData(contracts, '', False, False) | |
ticker = self.ib.ticker(contracts) | |
self.ib.sleep(1) | |
return ticker.bid | |
def get_jpy_rates(self, final_pl): | |
contract = Forex('USDJPY', exchange='IDEALPRO') | |
bars = self.ib.reqHistoricalData( | |
contract, | |
endDateTime='', | |
durationStr='1 D', | |
barSizeSetting='1 day', | |
whatToShow='MIDPOINT', | |
useRTH=True, | |
formatDate=1) | |
jpy_rates = bars[0].close * final_pl | |
return jpy_rates | |
def get_positions(self, trade_symbol): | |
positions = {} | |
for details in self.ib.positions(): | |
if details.contract.symbol == trade_symbol: | |
positions['units'] = int(details.position) | |
positions['rates'] = details.avgCost | |
return positions | |
def get_portfolio(self): | |
# Get portfolio from IB | |
self.ib.sleep(0.5) | |
for details in self.ib.accountSummary(): | |
if details.account in ['DU1580586','U3203720'] and details.tag == 'TotalCashValue': | |
portfolio = int(float(details.value)) | |
if details.account in ['DU1580586','U3203720'] and details.tag == 'MaintMarginReq': | |
used_portfolio = int(float(details.value)) | |
return portfolio, used_portfolio | |
def get_log(self, log, units): | |
# Create a tikets from the trade logs. | |
final_units = 0 | |
final_rates = 0 | |
final_pl = 0 | |
log_status = '' | |
print('Get log start') | |
while True: | |
if hasattr(log, 'fills') == True: | |
# print(log) | |
if type(log.fills) is list: | |
if len(log.fills) != 0: | |
for details in log.fills: | |
final_units += details.execution.shares | |
final_pl += details.commissionReport.realizedPNL | |
if log.orderStatus.status == 'Filled': | |
final_rates = details.execution.avgPrice | |
log_status = 'Filled' | |
break | |
elif log.orderStatus.status == 'Cancelled': | |
log_status = 'Cancelled' | |
break | |
self.ib.sleep(1) | |
return log_status, final_units, final_rates, final_pl | |
def indicator(self, close_list): | |
macd_list = macd(close_list, MACD_FAST, MACD_SLOW) | |
signal_list = sma(macd_list, SMA_PERIOD) | |
return macd_list[-1], signal_list[-1] | |
# -------------------------------------------------------------------------- | |
# トレード | |
def main(self, trade_symbol, data_time): | |
# -------------------------------------------------------------------------- | |
# 初期値 | |
positions = {} | |
trade_new = '' | |
trade_close = '' | |
UNITS = CFDs[trade_symbol]['UNITS'] | |
# -------------------------------------------------------------------------- | |
# 1分足を取得 | |
while True: | |
df_M1 = self.get_bars_df(trade_symbol, '1 min') | |
last_time_M1 = df_M1.iloc[-1]['date'].strftime('%H:%M') | |
# print(df_M1.tail()) | |
# print(data_time, last_time_M1) | |
if data_time == last_time_M1: | |
break | |
else: | |
trading_ins.ib.sleep(1) | |
# 5分足のデータも取得 | |
df_M5 = self.get_bars_df(trade_symbol, '5 mins') | |
last_time_M5 = df_M5.iloc[-1]['date'].strftime('%H:%M') | |
# 最後の行を削除 | |
df_M1 = df_M1[:-1] | |
# インジゲーターを取得する | |
macd_M1, signal_M1 = self.indicator(df_M1['close'].values.tolist()) | |
macd_M5, signal_M5 = self.indicator(df_M5['close'].values.tolist()) | |
# -------------------------------------------------------------------------- | |
# 通知 | |
print(macd_M5 , signal_M5) | |
print(macd_M1 , signal_M1) | |
if( | |
self.flag_notice == False and | |
macd_M5 >= signal_M5 and | |
macd_M1 >= signal_M1 | |
): | |
line_message.put("It's a good time to trade {}.".format(trade_symbol)) | |
self.flag_notice = True | |
elif( | |
self.flag_notice == True and | |
( | |
macd_M5 < signal_M5 or | |
macd_M1 < signal_M1 | |
) | |
): | |
self.flag_notice = False | |
# -------------------------------------------------------------------------- | |
# 決済 | |
# ポジションを取得 | |
positions = self.get_positions(trade_symbol) | |
if len(positions) != 0: # ポジションがあるか? | |
print(positions) | |
# 利益の確認 | |
now_rate = self.get_tick(trade_symbol) | |
diff_rate = now_rate - positions['rates'] | |
# 決済判断 | |
print("Close Jugement!", trade_symbol, diff_rate) | |
if ( | |
positions['units'] > 0 and # LONGなら | |
# diff_rate == diff_rate | |
diff_rate > PROFIT_PIPS and | |
( | |
macd_M5 < signal_M5 or | |
macd_M1 < signal_M1 | |
) | |
): | |
print("Close Trading!") | |
# 決済 | |
contract = CFD(trade_symbol, CFDs[trade_symbol]['EXCHANGE'], CFDs[trade_symbol]['CURRENCY']) # 銘柄 取引所 通貨 | |
order = MarketOrder(SHORT, positions['units']) | |
trade_close = self.ib.placeOrder(contract, order) | |
# IBよりログを取得 | |
log_status, final_units, final_rates, final_pl = self.get_log(trade_close, positions) | |
# 日本円に換算 | |
if CFDs[trade_symbol]['CURRENCY'] == 'USD': | |
final_pl_jpy = self.get_jpy_rates(final_pl) | |
else: | |
final_pl_jpy = final_pl | |
# ポートフォリオを取得 | |
portfolio, used_portfolio = self.get_portfolio() | |
# DBからデータを取得 | |
db_id = db_ins.get_positions_id() | |
if db_id is None: | |
line_message.put(trade_symbol + ' was profit for {}.\nBut there was no data in DB.'.format(final_pl_jpy)) | |
else: | |
print(db_id) | |
diff_per = diff_rate / positions['rates'] * 100 | |
# ログの保存 | |
trade_log = {} | |
trade_log['close_rates'] = final_rates | |
trade_log['act'] = 'profit' | |
trade_log['diff_per'] = diff_per | |
trade_log['realizedPNL'] = final_pl | |
trade_log['realizedPNLjpy'] = final_pl_jpy | |
trade_log['portfolio'] = portfolio | |
db_ins.insertLog(str(trade_close)) | |
db_ins.updateTrade(trade_log, db_id) | |
# ポジションを初期化 | |
positions = {} | |
# -------------------------------------------------------------------------- | |
# 新規 | |
# if len(positions) == 0: | |
if len(positions) == 0 and last_time_M1 == last_time_M5: # ポジションはないか? 5分刻みか? | |
# print("New Trading Jugement!") | |
# if( | |
# macd_M1 == macd_M1 | |
# ): | |
if( | |
macd_M5 >= signal_M5 and | |
macd_M1 >= signal_M1 | |
): | |
print("New Trading!") | |
# -------------------------------------------------------------------------- | |
# 新規オーダー | |
contract = CFD(trade_symbol, CFDs[trade_symbol]['EXCHANGE'], CFDs[trade_symbol]['CURRENCY']) # 銘柄 取引所 通貨 | |
order = MarketOrder(LONG, UNITS) | |
trade_new = self.ib.placeOrder(contract, order) | |
# ログをIBより取得 | |
log_status, final_units, final_rates, final_pl = self.get_log(trade_new, UNITS) | |
print('final_rates',final_rates) | |
# -------------------------------------------------------------------------- | |
# ログの保存 | |
trade_log = {} | |
trade_log['symbol'] = trade_symbol | |
trade_log['kind'] = LONG | |
trade_log['units'] = UNITS | |
trade_log['new_rates'] = final_rates | |
db_ins.insertLog(str(trade_new)) | |
db_ins.insertTrade(trade_log) | |
# -------------------------------------------------------------------------- | |
# lineへの通知 | |
messages = trade_symbol + " trade is still working today!" | |
if trade_close != '': | |
messages += "\nProfit confirmed." | |
if trade_new != '': | |
messages += "\nAnd made {} units of new trade.".format(UNITS) | |
line_message.put(messages) | |
# ------------------------------------------------------------------------- | |
if __name__ == "__main__": | |
trading_ins = trading() | |
thread = threading.Thread(target=line_ins.queue_send_messages, args=(line_message,)) | |
thread.start() | |
next_time = datetime.now(timezone(trade_timezone)) | |
next_time = next_time.strftime('%H:%M') | |
while True: | |
try: | |
trading_ins.ib_connect() | |
while True: | |
# 時間の判定 | |
now_date = datetime.now(timezone(trade_timezone)) | |
now_time = now_date.strftime('%H:%M') | |
# print(now_time, next_time) | |
# 次の1分か? | |
if now_time > next_time: | |
next_time = now_time | |
# トレード種別判断 | |
if now_time >= '01:00' and now_time < '07:10': | |
trade_symbol = 'IBJP225' | |
elif now_time >= '08:00' and now_time < '21:00': | |
trade_symbol = 'IBUS30' | |
elif now_time >= '21:00': | |
# 金曜日の確認 | |
if now_date.strftime('%a') == 'Fri': | |
print('shut_down_time', now_date, now_date.strftime('%a')) | |
shut_down_flag = True | |
break | |
else: | |
trade_symbol = '' | |
# トレード | |
# print(now_time, trade_symbol) | |
if trade_symbol != '': | |
trading_ins.main(trade_symbol, datetime.now(timezone(data_timezone)).strftime('%H:%M')) | |
# スリープ | |
trading_ins.ib.sleep(1) | |
except Exception as e: | |
print(traceback.format_exc()) | |
line_message.put("CFD from IB\n" + str(e)) | |
error_count = db_ins.error_except(e, "CFD form IB", error_count) | |
except KeyboardInterrupt: | |
trading_ins.ib.disconnect() | |
sys.exit() | |
if shut_down_flag == True: | |
print('End of trade') | |
break |
cls_db
#!/usr/bin/env python3 | |
# -*- coding: utf-8 -*- | |
""" | |
@author: novonovo | |
""" | |
import sys | |
import sqlite3 | |
import pandas as pd | |
import datetime | |
from time import sleep | |
from pytz import timezone | |
# import cls_line | |
ERROR_EXIT_COUNT = 3 | |
# line_ins = cls_line.line() | |
class db: | |
def __init__(self, dbpath): | |
self.dbpath = dbpath | |
def open(self): | |
self.connection = sqlite3.connect(self.dbpath) | |
self.cursor = self.connection.cursor() | |
def close(self): | |
self.connection.close() # 接続を閉じる | |
def insertLog(self, messages): | |
print(messages) | |
now = datetime.datetime.now(timezone('UTC')) | |
try: | |
self.open() | |
sql = "INSERT INTO Log (dates,messages) VALUES (?, ?)" | |
self.cursor.execute(sql, (now, messages)) | |
self.connection.commit() # 保存を実行(忘れると保存されないので注意) | |
self.connection.close() # 接続を閉じる | |
except sqlite3.Error as e: | |
print('sqlite3.Error occurred:', e.args[0]) | |
return | |
def updateTrade(self, trade_log, id): | |
try: | |
now = datetime.datetime.now(timezone('UTC')) | |
self.open() | |
sql = (""" | |
UPDATE trade SET | |
close_date = ?, | |
close_rates = ?, | |
act = ?, | |
diff_per = ?, | |
realizedPNL = ?, | |
realizedPNLjpy = ?, | |
portfolio = ? | |
where id = {} | |
""").format(id) | |
self.cursor.execute(sql, ( | |
now, trade_log['close_rates'], | |
trade_log['act'], | |
trade_log['diff_per'], trade_log['realizedPNL'], trade_log['realizedPNLjpy'], | |
trade_log['portfolio'] | |
)) | |
self.connection.commit() # 保存を実行(忘れると保存されないので注意) | |
self.connection.close() # 接続を閉じる | |
except sqlite3.Error as e: | |
print('sqlite3.Error occurred:', e.args[0]) | |
def insertTrade(self, trade_log): | |
try: | |
now = datetime.datetime.now(timezone('UTC')) | |
self.open() | |
sql = """ | |
INSERT INTO trade ( | |
symbol, kind, units, | |
new_date, new_rates | |
) VALUES ( | |
?, ?, ?, | |
?, ? | |
) | |
""" | |
self.cursor.execute(sql, ( | |
trade_log['symbol'], trade_log['kind'], trade_log['units'], | |
now, trade_log['new_rates'] | |
)) | |
self.connection.commit() # 保存を実行(忘れると保存されないので注意) | |
self.connection.close() # 接続を閉じる | |
except sqlite3.Error as e: | |
print('sqlite3.Error occurred:', e.args[0]) | |
# def get_won_pips(self, rowid): | |
# won_pips = 0 | |
# try: | |
# self.open() | |
# sql = 'SELECT won_pips FROM trade WHERE rowid=?' | |
# self.cursor.execute(sql,(rowid,)) | |
# data = self.cursor.fetchone() | |
# self.connection.close() # 接続を閉じる | |
# if data is None: | |
# won_pips = 0 | |
# else: | |
# won_pips = float(data[0]) | |
# except sqlite3.Error as e: | |
# print('sqlite3.Error occurred:', e.args[0]) | |
# return won_pips | |
def get_positions_id(self): | |
id = None | |
try: | |
self.open() | |
sql = 'SELECT id FROM trade WHERE close_rates IS NULL' | |
self.cursor.execute(sql) | |
data = self.cursor.fetchone() | |
self.connection.close() # 接続を閉じる | |
# print('data', data) | |
if data is None: | |
id = None | |
else: | |
id = int(data[0]) | |
except sqlite3.Error as e: | |
print('sqlite3.Error occurred:', e.args[0]) | |
# print('id', id) | |
return id | |
# def get_positions(self): | |
# df = '' | |
# try: | |
# self.open() | |
# sql = 'SELECT id, symbol, kind, units, new_date, new_rates FROM trade WHERE close_date = ""' | |
# df = pd.read_sql_query(sql, self.connection) | |
# self.connection.close() # 接続を閉じる | |
# except sqlite3.Error as e: | |
# print('sqlite3.Error occurred:', e.args[0]) | |
# return df | |
def create_db(self): | |
self.open() | |
# ログ | |
self.cursor.execute("DROP TABLE IF EXISTS Log") | |
sql = """ | |
CREATE TABLE IF NOT EXISTS Log ( | |
id INTEGER PRIMARY KEY AUTOINCREMENT, | |
dates TIMESTAMP, | |
messages TEXT | |
) | |
""" | |
self.cursor.execute(sql) | |
# ログ | |
self.cursor.execute("DROP TABLE IF EXISTS trade") | |
sql = """ | |
CREATE TABLE IF NOT EXISTS trade ( | |
id INTEGER PRIMARY KEY AUTOINCREMENT, | |
symbol TEXT, | |
kind TEXT, | |
units TEXT, | |
new_date TIMESTAMP, | |
new_rates REAL, | |
close_date TIMESTAMP, | |
close_rates REAL, | |
act TEXT, | |
diff_per REAL, | |
realizedPNL REAL, | |
realizedPNLjpy REAL, | |
portfolio REAL | |
) | |
""" | |
self.cursor.execute(sql) | |
def error_except(self, e, title, count): | |
messages = '=== ERROR({}) {}==='.format(count, title) + '\n' | |
messages += 'type:' + str(type(e)) + '\n' | |
messages += 'args:' + str(type(e.args)) + '\n' | |
messages += str(e) | |
self.insertLog(messages) | |
count += 1 | |
sleep(1) | |
return count | |
# Not use ----------------------------------------------------------------- | |
def readLogs_df(self): | |
df = pd.read_sql_query("select * from Logs", self.connection) | |
return df | |
def insertLogs(self, currency, kind, details, close_tickets): | |
try: | |
self.open() | |
sql = """ | |
INSERT INTO Logs ( | |
order_dates, close_dates, currency, turm_min, kind, units, order_rates, close_rates, diff_pips, won_pips | |
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?) | |
""" | |
self.cursor.execute(sql, [ | |
details['order_dates'], | |
close_tickets['close_dates'], | |
currency, | |
close_tickets['turm_min'], | |
kind, | |
details['units'], | |
details['order_rates'], | |
close_tickets['close_rates'], | |
close_tickets['diff_pips'], | |
close_tickets['won_pips'] | |
]) | |
self.connection.commit() # 保存を実行(忘れると保存されないので注意) | |
self.connection.close() # 接続を閉じる | |
except sqlite3.Error as e: | |
print('sqlite3.Error occurred:', e.args[0]) | |
# self.insertLog('insertLogs') | |
return | |
cls_line
#!/usr/bin/env python3 | |
# -*- coding: utf-8 -*- | |
""" | |
@author: novonovo | |
""" | |
# -------------------------------------------------------------------------- | |
# ライブラリの読み込み | |
from time import sleep | |
import requests | |
class line: | |
def __init__(self): | |
self.url = "https://notify-api.line.me/api/notify" | |
self.access_token = '' | |
self.headers = {'Authorization': 'Bearer ' + self.access_token} | |
def queue_send_messages(self, line_message): | |
while True: | |
# if not line_message.empty(): | |
message = line_message.get() | |
try: | |
payload = {'message': message} | |
res = requests.post(self.url, headers=self.headers, params=payload,) | |
if res.text == '{"status":200,"message":"ok"}': | |
break | |
except Exception as e: | |
print ('type:' + str(type(e))) | |
sleep(1) | |
# def send_messages(self, message): | |
# while True: | |
# try: | |
# payload = {'message': message} | |
# res = requests.post(self.url, headers=self.headers, params=payload,) | |
# if res.text == '{"status":200,"message":"ok"}': | |
# break | |
# except Exception as e: | |
# print ('type:' + str(type(e))) | |
# return |
コメント