Revision | ba14272cef6659e472eb6a9ca8f03296d14a1e3d (tree) |
---|---|
Time | 2023-02-13 06:33:39 |
Author | kazuhiro_kondow <simauma.circus@gmai...> |
Commiter | kazuhiro_kondow |
macdの使い方を見直し
判定はGoldenCross,DeahCrossのみに変更
交差の角度要素を追加
macdパラメータを過去90日のデータからmacdの4要素で
最適化して設定
最適化はbacktesting_skoptを使用
経済指標イベントのフラグを追加
現状は機能しない
@@ -1,18 +1,34 @@ | ||
1 | 1 | altgraph==0.17.3 |
2 | 2 | autopep8==2.0.0 |
3 | +Backtesting==0.3.3 | |
4 | +bayesian-optimization==1.4.2 | |
3 | 5 | beautifulsoup4==4.11.1 |
6 | +bokeh==3.0.3 | |
4 | 7 | certifi==2022.12.7 |
5 | 8 | charset-normalizer==3.0.1 |
9 | +colorama==0.4.6 | |
10 | +contourpy==1.0.7 | |
6 | 11 | flake8==6.0.0 |
7 | 12 | future==0.18.2 |
13 | +html5lib==1.1 | |
8 | 14 | idna==3.4 |
15 | +investpy==1.0.8 | |
16 | +Jinja2==3.1.2 | |
17 | +joblib==1.2.0 | |
18 | +lxml==4.9.2 | |
19 | +MarkupSafe==2.1.2 | |
9 | 20 | mccabe==0.7.0 |
10 | 21 | MetaTrader5==5.0.43 |
11 | 22 | mypy==0.991 |
12 | 23 | mypy-extensions==0.4.3 |
13 | 24 | numpy==1.23.5 |
25 | +oandapyV20==0.7.2 | |
26 | +packaging==23.0 | |
14 | 27 | pandas==1.5.2 |
28 | +pandas-datareader==0.10.0 | |
15 | 29 | pefile==2022.5.30 |
30 | +Pillow==9.4.0 | |
31 | +pyaml==21.10.1 | |
16 | 32 | pycodestyle==2.10.0 |
17 | 33 | pyflakes==3.0.1 |
18 | 34 | pyinstaller==5.7.0 |
@@ -20,12 +36,22 @@ pyinstaller-hooks-contrib==2022.14 | ||
20 | 36 | python-dateutil==2.8.2 |
21 | 37 | pytz==2022.6 |
22 | 38 | pywin32-ctypes==0.2.0 |
39 | +PyYAML==6.0 | |
23 | 40 | requests==2.28.2 |
24 | 41 | schedule==1.1.0 |
42 | +scikit-learn==1.2.1 | |
43 | +scikit-optimize==0.9.0 | |
44 | +scipy==1.10.0 | |
25 | 45 | six==1.16.0 |
26 | 46 | soupsieve==2.3.2.post1 |
27 | 47 | style==1.1.0 |
48 | +TA-Lib==0.4.25 | |
49 | +threadpoolctl==3.1.0 | |
28 | 50 | tomli==2.0.1 |
51 | +tornado==6.2 | |
29 | 52 | typing_extensions==4.4.0 |
53 | +Unidecode==1.3.6 | |
30 | 54 | update==0.0.1 |
31 | 55 | urllib3==1.26.14 |
56 | +webencodings==0.5.1 | |
57 | +xyzservices==2022.9.0 |
@@ -0,0 +1,60 @@ | ||
1 | +# 過去90日のrateを取得してcsvファイルを作成する | |
2 | + | |
3 | +import pandas as pd | |
4 | +import datetime as dt | |
5 | +import pytz | |
6 | +import MetaTrader5 as mt5 | |
7 | + | |
8 | +# 本日日付 | |
9 | +now = dt.datetime.now() | |
10 | +# 日本のtimezone | |
11 | +tz_local = pytz.timezone('Etc/GMT-9') | |
12 | +# OANDAサーバのtimezone | |
13 | +tz_oanda = pytz.timezone('Etc/GMT-2') | |
14 | + | |
15 | +local_to = dt.datetime( | |
16 | + now.year, | |
17 | + now.month, | |
18 | + now.day, | |
19 | + 0, | |
20 | + 0, | |
21 | + 0, | |
22 | + tzinfo=tz_local | |
23 | +) | |
24 | +local_from = local_to - dt.timedelta(days=90) | |
25 | + | |
26 | +rate_to = local_to.astimezone(tz_oanda) | |
27 | +rate_from = local_from.astimezone(tz_oanda) | |
28 | + | |
29 | +# レートを取得 | |
30 | +cp = 'USDJPY' | |
31 | +mt5.initialize() | |
32 | +rates = mt5.copy_rates_range(cp, mt5.TIMEFRAME_M1, rate_from, rate_to) | |
33 | + | |
34 | +df = pd.DataFrame(rates) | |
35 | + | |
36 | +# timeカラムをdatetime型に変換 | |
37 | +df.time = pd.to_datetime(df['time'], unit='s') | |
38 | +# timeカラムをindexに時系列インデックスとして割り当て | |
39 | +df.index = pd.DatetimeIndex(df.time, name='Date') | |
40 | +# インデックスのTimeZoneをUTCに設定 | |
41 | +df.index = df.index.tz_localize(dt.timezone.utc) | |
42 | +# インデックスのTimeZoneをLovalに変換 | |
43 | +df.index = df.index.tz_convert(tz_local) | |
44 | + | |
45 | +# DataFrameのカラム名をbacktesting向けに修正 | |
46 | +df = df.rename( | |
47 | + columns={ | |
48 | + "time": "Date", | |
49 | + "open": "Open", | |
50 | + "high": "High", | |
51 | + "low": "Low", | |
52 | + "close": "Close", | |
53 | + "tick_volume": "Volume", | |
54 | + "spread": "Spread", | |
55 | + "real_volume": "not_use", | |
56 | + } | |
57 | +) | |
58 | + | |
59 | +# csvファイル保存 | |
60 | +df.to_csv('USDJPY_1M.csv') |
@@ -0,0 +1,28 @@ | ||
1 | +import oandapyV20 | |
2 | +import oandapyV20.endpoints.forexlabs as labs | |
3 | + | |
4 | + | |
5 | +client = oandapyV20.API(access_token=...) | |
6 | +params = { | |
7 | + "instrument": "USD_JPY", | |
8 | + "period": 604800 | |
9 | +} | |
10 | + | |
11 | +# instrument | |
12 | +# 必須 カレンダー取得対象の銘柄名。 全ての取引可能な銘柄が指定可能です。 | |
13 | +# period | |
14 | +# 必須 カレンダーデータを取得する期間(秒単位)。 以下の有効な値のいずれかに合致しない値を設定した場合は、一番近い有効な値に自動的に修正されます。 | |
15 | +# 有効な値: | |
16 | + | |
17 | +# 3600 - 1時間 | |
18 | +# 43200 - 12時間 | |
19 | +# 86400 - 1日 | |
20 | +# 604800 - 1週間 | |
21 | +# 2592000 - 1ヶ月 | |
22 | +# 7776000 - 3ヶ月 | |
23 | +# 15552000 - 6ヶ月 | |
24 | +# 31536000 - 1年 | |
25 | + | |
26 | +r = labs.Calendar(params=params) | |
27 | +client.request(r) | |
28 | +print(r.response) |
@@ -43,8 +43,10 @@ LossCut = 0.2 | ||
43 | 43 | # テクニカル分析 |
44 | 44 | [Technical] |
45 | 45 | # 短期EMAの期間 |
46 | -FastEMAperiod = 28 | |
46 | +FastEMAperiod = 39 | |
47 | 47 | # 長期EMAの期間 |
48 | -SlowEMAperiod = 53 | |
48 | +SlowEMAperiod = 56 | |
49 | 49 | # シグナルの期間 |
50 | -Signalperiod = 15 | |
50 | +Signalperiod = 21 | |
51 | +# 交差角度閾値 | |
52 | +CrossingAngleThreshold = 0.684 |
@@ -6,6 +6,7 @@ import logging | ||
6 | 6 | logger = logging.getLogger() |
7 | 7 | |
8 | 8 | |
9 | +# 週次イベントフラグを設置、月曜日に発生する「窓開け」リスク対策 | |
9 | 10 | class Setting(object): |
10 | 11 | """アプリ設定 |
11 | 12 | モジュール間の状態保持機能 |
@@ -35,8 +36,10 @@ class Setting(object): | ||
35 | 36 | self.__market_availability: bool = False |
36 | 37 | # 口座の取引可否フラグ |
37 | 38 | self.__account_availability: bool = False |
38 | - # イベントの有無フラグ | |
39 | + # 週次イベントの有無フラグ | |
39 | 40 | self.__event_availability: bool = False |
41 | + # 経済指標イベントの有無フラグ | |
42 | + self.__economic_indicator_event_availability: bool = False | |
40 | 43 | # 最後に確認した為替レート |
41 | 44 | self.__last_checked_exchange_rate: float = 0.0 |
42 | 45 | # 注文可能lot数 |
@@ -69,24 +72,36 @@ class Setting(object): | ||
69 | 72 | self.__account_availability = value |
70 | 73 | |
71 | 74 | @property |
72 | - def upcoming_event(self) -> bool: | |
73 | - """イベントの有無 | |
75 | + def weekday_event(self) -> bool: | |
76 | + """週次イベントフラグ | |
74 | 77 | """ |
75 | 78 | return self.__event_availability |
76 | 79 | |
77 | - @upcoming_event.setter | |
78 | - def upcoming_event(self, value: bool = False): | |
80 | + @weekday_event.setter | |
81 | + def weekday_event(self, value: bool = False): | |
79 | 82 | self.__event_availability = value |
80 | 83 | |
81 | 84 | @property |
85 | + def economic_event(self) -> bool: | |
86 | + """経済指標イベントフラグ | |
87 | + """ | |
88 | + return self.__economic_indicator_event_availability | |
89 | + | |
90 | + @economic_event.setter | |
91 | + def economic_event(self, value: bool = False): | |
92 | + self.__economic_indicator_event_availability = value | |
93 | + | |
94 | + @property | |
82 | 95 | def ready_to_order(self) -> bool: |
83 | - # TODO:現状はイベントを避ける | |
96 | + # 経済指標イベント以外がTrueの場合、注文可能 | |
84 | 97 | ret = self.__event_availability and \ |
85 | 98 | self.__account_availability and \ |
99 | + not self.__economic_indicator_event_availability and \ | |
86 | 100 | self.__market_availability |
87 | 101 | logger.debug( |
88 | 102 | f'Market:{self.__market_availability} - ' |
89 | 103 | f'Event:{self.__event_availability} - ' |
104 | + f'Economic:{self.__economic_indicator_event_availability} - ' | |
90 | 105 | f'Account:{self.__account_availability} => ' |
91 | 106 | f'Ready to order?:{ret}' |
92 | 107 | ) |
@@ -62,7 +62,7 @@ class EventMng(): | ||
62 | 62 | weekday = local_date.weekday() |
63 | 63 | if weekday == 6: |
64 | 64 | logger.info('It Sunday is closed market.') |
65 | - Setting().upcoming_event = False | |
65 | + Setting().weekday_event = False | |
66 | 66 | elif weekday == 5: |
67 | 67 | # サマータイムの期間 クローズ時間: 土曜日の午前6時 |
68 | 68 | if is_dst and hour_minut > 555: |
@@ -70,32 +70,32 @@ class EventMng(): | ||
70 | 70 | 'During daylight saving time. ' |
71 | 71 | 'The market is closed after 6am on Saturdays.' |
72 | 72 | ) |
73 | - Setting().upcoming_event = False | |
73 | + Setting().weekday_event = False | |
74 | 74 | # サマータイムの期間 クローズ時間: 土曜日の午前6時前 |
75 | 75 | elif is_dst and hour_minut <= 555: |
76 | 76 | logger.info( |
77 | 77 | 'During daylight saving time. ' |
78 | 78 | 'Saturday market not closed yet.' |
79 | 79 | ) |
80 | - Setting().upcoming_event = True | |
80 | + Setting().weekday_event = True | |
81 | 81 | # サマータイム以外の期間 クローズ時間: 土曜日の午前7時 |
82 | 82 | elif not is_dst and hour_minut > 655: |
83 | 83 | logger.info( |
84 | 84 | 'During non-summertime periods. ' |
85 | 85 | 'The market is closed after 7am on Saturdays.' |
86 | 86 | ) |
87 | - Setting().upcoming_event = False | |
87 | + Setting().weekday_event = False | |
88 | 88 | elif not is_dst and hour_minut <= 655: |
89 | 89 | logger.info( |
90 | 90 | 'During daylight saving time. ' |
91 | 91 | 'Saturday market not closed yet.' |
92 | 92 | ) |
93 | - Setting().upcoming_event = True | |
93 | + Setting().weekday_event = True | |
94 | 94 | else: |
95 | 95 | pass |
96 | 96 | elif weekday in [4, 3, 2, 1]: |
97 | 97 | logger.info('It weekday.') |
98 | - Setting().upcoming_event = True | |
98 | + Setting().weekday_event = True | |
99 | 99 | elif weekday == 0: |
100 | 100 | # サマータイムの期間 オープン時間: 月曜日の午前6時 |
101 | 101 | if is_dst and hour_minut < 610: |
@@ -103,17 +103,17 @@ class EventMng(): | ||
103 | 103 | 'During daylight saving time. ' |
104 | 104 | 'The market is closed before 6am on Mondays.' |
105 | 105 | ) |
106 | - Setting().upcoming_event = False | |
106 | + Setting().weekday_event = False | |
107 | 107 | # サマータイム以外の期間 オープン時間: 月曜日の午前7時 |
108 | 108 | elif not is_dst and hour_minut < 710: |
109 | 109 | logger.info( |
110 | 110 | 'During non-summertime periods. ' |
111 | 111 | 'The market is closed before 7am on Mondays.' |
112 | 112 | ) |
113 | - Setting().upcoming_event = False | |
113 | + Setting().weekday_event = False | |
114 | 114 | else: |
115 | 115 | logger.info('It weekday.') |
116 | - Setting().upcoming_event = True | |
116 | + Setting().weekday_event = True | |
117 | 117 | |
118 | 118 | def __check_economic_index_event(self): |
119 | 119 | """経済指標イベント確認 |
@@ -4,6 +4,7 @@ from datetime import datetime | ||
4 | 4 | import MetaTrader5 as mt5 |
5 | 5 | import talib as ta |
6 | 6 | import numpy as np |
7 | +from numpy import linalg as LA | |
7 | 8 | import pytz |
8 | 9 | import logging |
9 | 10 |
@@ -35,6 +36,8 @@ class ExchangeRateInfo(): | ||
35 | 36 | self.__n2 = int(s['Technical']['SlowEMAperiod']) |
36 | 37 | # シグナル(MACDのSMA)の期間 |
37 | 38 | self.__ns = int(s['Technical']['Signalperiod']) |
39 | + # 交差角度閾値 | |
40 | + self.__angle = float(s['Technical']['CrossingAngleThreshold']) | |
38 | 41 | except Exception as e: |
39 | 42 | logger.error(e) |
40 | 43 | raise |
@@ -75,6 +78,14 @@ class ExchangeRateInfo(): | ||
75 | 78 | raise ValueError('Nan is included in the value.') |
76 | 79 | return series1[-2] < series2[-2] and series1[-1] > series2[-1] |
77 | 80 | |
81 | + def __vector_angle(self, u: np.ndarray, v: np.ndarray): | |
82 | + if np.isnan(u) or np.isnan(v): | |
83 | + raise ValueError('Nan is included in the value.') | |
84 | + i = np.inner(u, v) | |
85 | + n = LA.norm(u) * LA.norm(v) | |
86 | + c = i / n | |
87 | + return np.rad2deg(np.arccos(np.clip(c, -1.0, 1.0))) | |
88 | + | |
78 | 89 | def __get_latest_bid(self) -> float: |
79 | 90 | """最新bid取得 |
80 | 91 | """ |
@@ -176,39 +187,26 @@ class ExchangeRateInfo(): | ||
176 | 187 | macdsignal = np.around(macdsignal, 6) |
177 | 188 | |
178 | 189 | # for debug |
179 | - for i in range(-4, 0, 1): | |
190 | + for i in range(-2, 0, 1): | |
180 | 191 | msg = ( |
181 | 192 | f'num[{i}]: MACD: {macd[i]}, MACDSignal: {macdsignal[i]}' |
182 | 193 | ) |
183 | 194 | logger.debug(msg) |
184 | 195 | |
185 | 196 | # 指標判定 |
186 | - if self.__crossover(macd, macdsignal) and macd[-1] < 0.0: | |
197 | + v_angle = self.__vector_angle(macd[-2:], macdsignal[-2:]) | |
198 | + msg = f'macd vector angle:{v_angle}' | |
199 | + if self.__crossover(macd, macdsignal) and \ | |
200 | + v_angle > self.__angle: | |
187 | 201 | # GoldenCross 上昇転換サイン |
188 | 202 | s.technical_indicator = DEF.TECHNICAL_GOLDEN_CROSS |
189 | 203 | msg = 'Technical Indicator: GoldenCross.' |
190 | - elif self.__crossover(macdsignal, macd) and macd[-1] > 0.0: | |
204 | + elif self.__crossover(macdsignal, macd) and \ | |
205 | + self.__vector_angle(macd[-2:], macdsignal[-2:]) > \ | |
206 | + self.__angle: | |
191 | 207 | # DeadCross 降下転換サイン |
192 | 208 | s.technical_indicator = DEF.TECHNICAL_DEAD_CROSS |
193 | 209 | msg = 'Technical Indicator: DeadCross.' |
194 | - elif macd[-1] > macdsignal[-1]: | |
195 | - if macd[-1] < 0.0: | |
196 | - # 弱い上昇トレンド | |
197 | - s.technical_indicator = DEF.TECHNICAL_UPTREND | |
198 | - msg = 'Technical Indicator: Weak Uptrend.' | |
199 | - else: | |
200 | - # 強い上昇トレンド | |
201 | - s.technical_indicator = DEF.TECHNICAL_UPTREND | |
202 | - msg = 'Technical Indicator: Strong Uptrend.' | |
203 | - elif macd[-1] < macdsignal[-1]: | |
204 | - if macd[-1] > 0.0: | |
205 | - # 弱い降下トレンド | |
206 | - s.technical_indicator = DEF.TECHNICAL_DOWNTREND | |
207 | - msg = 'Technical Indicator: Weak Downtrand.' | |
208 | - else: | |
209 | - # 強い降下トレンド | |
210 | - s.technical_indicator = DEF.TECHNICAL_DOWNTREND | |
211 | - msg = 'Technical Indicator: Strong Downtrand.' | |
212 | 210 | else: |
213 | 211 | # いずれにも該当しない |
214 | 212 | s.technical_indicator = DEF.TECHNICAL_UNCHANGED |
@@ -48,11 +48,9 @@ class PositionMng(): | ||
48 | 48 | indicator = s.technical_indicator |
49 | 49 | # 解消する対象のポジション mt5.ENUM_POSITION_TYPE |
50 | 50 | position_to_be_canceled: int = -1 # -1:Undefine |
51 | - if indicator == DEF.TECHNICAL_GOLDEN_CROSS or \ | |
52 | - indicator == DEF.TECHNICAL_UPTREND: | |
51 | + if indicator == DEF.TECHNICAL_GOLDEN_CROSS: | |
53 | 52 | position_to_be_canceled = mt5.POSITION_TYPE_SELL |
54 | - elif indicator == DEF.TECHNICAL_DEAD_CROSS or \ | |
55 | - indicator == DEF.TECHNICAL_DOWNTREND: | |
53 | + elif indicator == DEF.TECHNICAL_DEAD_CROSS: | |
56 | 54 | position_to_be_canceled = mt5.POSITION_TYPE_BUY |
57 | 55 | else: |
58 | 56 | pass |