• R/O
  • HTTP
  • SSH
  • HTTPS

Commit

Tags
No Tags

Frequently used words (click to add to your profile)

javac++androidlinuxc#windowsobjective-ccocoa誰得qtpythonphprubygameguibathyscaphec計画中(planning stage)翻訳omegatframeworktwitterdomtestvb.netdirectxゲームエンジンbtronarduinopreviewer

Commit MetaInfo

Revisiond21bada40c06056e353dca42cdcecc742753d8f6 (tree)
Time2023-02-22 21:41:00
Authorkazuhiro_kondow <simauma.circus@gmai...>
Commiterkazuhiro_kondow

Log Message

Backtestingの結果をsetting.iniに反映

main.py
並びを変更

position_mng
指標反転次にフラグをクリアする動作を修正

exchange_rate_info
MACDテクニカル判定を安定させる目的で修正を
加え続けているが正解がまだ見えない
いったん、角度評価についてパラメータが小数の時に
大きい角度を取っている様子がある

Change Summary

Incremental Difference

--- /dev/null
+++ b/backtesting_fx_m1_7_result.txt
@@ -0,0 +1,32 @@
1+Processing time(sec): 4.0
2+Start 2022-11-18 00:00...
3+End 2023-02-16 00:00...
4+Duration 90 days 00:00:00
5+Exposure Time [%] 82.612554
6+Equity Final [$] 9405.308164
7+Equity Peak [$] 10478.629701
8+Return [%] -5.946918
9+Buy & Hold Return [%] -4.777882
10+Return (Ann.) [%] -19.025605
11+Volatility (Ann.) [%] 8.457869
12+Sharpe Ratio 0.0
13+Sortino Ratio 0.0
14+Calmar Ratio 0.0
15+Max. Drawdown [%] -11.023872
16+Avg. Drawdown [%] -0.230081
17+Max. Drawdown Duration 43 days 13:43:00
18+Avg. Drawdown Duration 0 days 16:28:00
19+# Trades 166
20+Win Rate [%] 58.433735
21+Best Trade [%] 1.149513
22+Worst Trade [%] -1.187417
23+Avg. Trade [%] -0.032828
24+Max. Trade Duration 6 days 18:23:00
25+Avg. Trade Duration 0 days 11:14:00
26+Profit Factor 0.821075
27+Expectancy [%] -0.031742
28+SQN -1.010143
29+_strategy MACDCross
30+_equity_curve ...
31+_trades Size Entry...
32+dtype: object
--- /dev/null
+++ b/investigation_study/backtesting_fx_M1_7.py
@@ -0,0 +1,199 @@
1+# 元ネタ
2+# https://qiita.com/Fujinoinvestor/items/780b138e441531860e60
3+
4+# 過去90日のrateを取得してbacktestingを実行する
5+# セオリーからGoldenCross,DeadCross判定に
6+# macdの0.0以上、以下を外した場合の方が
7+# backtesting_fx_M1_5.pyと比較して
8+# Backtestingの結果が良かった
9+# backtesting_bayes_optimize_case1.pyから
10+# 個々のパラメータを最適化してこちらで検証した結果
11+
12+import talib as ta
13+from backtesting.lib import crossover
14+from backtesting import Backtest
15+from backtesting import Strategy
16+import pandas as pd
17+import datetime as dt
18+import pytz
19+import MetaTrader5 as mt5
20+import numpy as np
21+from numpy import linalg as LA
22+
23+# 本日日付
24+now = dt.datetime.now()
25+# 日本のtimezone
26+tz_local = pytz.timezone('Etc/GMT-9')
27+# OANDAサーバのtimezone
28+tz_oanda = pytz.timezone('Etc/GMT-2')
29+
30+local_to = dt.datetime(
31+ now.year,
32+ now.month,
33+ now.day,
34+ 0,
35+ 0,
36+ 0,
37+ tzinfo=tz_local
38+)
39+local_from = local_to - dt.timedelta(days=90)
40+
41+rate_to = local_to.astimezone(tz_oanda)
42+rate_from = local_from.astimezone(tz_oanda)
43+
44+# レートを取得
45+cp = 'USDJPY'
46+mt5.initialize()
47+rates = mt5.copy_rates_range(cp, mt5.TIMEFRAME_M1, rate_from, rate_to)
48+
49+df = pd.DataFrame(rates)
50+
51+# timeカラムをdatetime型に変換
52+df.time = pd.to_datetime(df['time'], unit='s')
53+# timeカラムをindexに時系列インデックスとして割り当て
54+df.index = pd.DatetimeIndex(df.time, name='Date')
55+# インデックスのTimeZoneをUTCに設定
56+df.index = df.index.tz_localize(dt.timezone.utc)
57+# インデックスのTimeZoneをLovalに変換
58+df.index = df.index.tz_convert(tz_local)
59+
60+# DataFrameのカラム名をbacktesting向けに修正
61+df = df.rename(
62+ columns={
63+ "time": "Date",
64+ "open": "Open",
65+ "high": "High",
66+ "low": "Low",
67+ "close": "Close",
68+ "tick_volume": "Volume",
69+ "spread": "Spread",
70+ "real_volume": "not_use",
71+ }
72+)
73+
74+# 必要なカラムのみ取得
75+data = df.loc[:, ['Open', 'High', 'Low', 'Close', 'Volume']]
76+
77+
78+def MACD(close, n1, n2, ns):
79+ macd, macdsignal, macdhist = ta.MACD(
80+ close, fastperiod=n1, slowperiod=n2, signalperiod=ns)
81+ return macd, macdsignal
82+
83+
84+# 下記記事を参考にStrategyを修正
85+# https://mmorley.hatenablog.com/entry/fx_backtesting01
86+
87+class MACDCross(Strategy):
88+ # 短期EMAの期間
89+ n1 = 18
90+ # 長期EMAの期間
91+ n2 = 41
92+ # シグナル(MACDのSMA)の期間
93+ ns = 24
94+ # angleの閾値
95+ crossover_angle = 3.794
96+ # 利益確定(円)
97+ ProfitTaking = 1.428
98+ # 損切(円)
99+ LossCut = 1.8
100+
101+ def init(self):
102+ self.macd, self.macdsignal = self.I(
103+ MACD, self.data.Close, self.n1, self.n2, self.ns)
104+
105+ def vector_angle(self, u: np.ndarray, v: np.ndarray):
106+ i = np.inner(u, v)
107+ n = LA.norm(u) * LA.norm(v)
108+ c = i / n
109+ return np.rad2deg(np.arccos(np.clip(c, -1.0, 1.0)))
110+
111+ # チャートデータの行ごとに呼び出される
112+ # tp(take profit)利確する価格
113+ # sl(stop losst)損切りする価格
114+ def next(self):
115+ # macdがsignalを上回った時
116+ if crossover(self.macd, self.macdsignal) and \
117+ self.vector_angle(
118+ self.macd[-2:],
119+ self.macdsignal[-2:]
120+ ) > self.crossover_angle:
121+ # 買いオーダー
122+ self.buy(
123+ tp=self.data.Close[-1] + self.ProfitTaking,
124+ sl=self.data.Close[-1] - self.LossCut
125+ )
126+
127+ # signalがmacdを上回った時
128+ elif crossover(self.macdsignal, self.macd) and \
129+ self.vector_angle(
130+ self.macd[-2:],
131+ self.macdsignal[-2:]
132+ ) > self.crossover_angle:
133+ # 売りオーダー
134+ self.sell(
135+ tp=self.data.Close[-1] - self.ProfitTaking,
136+ sl=self.data.Close[-1] + self.LossCut
137+ )
138+
139+
140+# バックテストを設定
141+bt = Backtest(
142+ # チャートデータ
143+ data,
144+ # 売買戦略
145+ MACDCross,
146+ # 最初の所持金
147+ cash=10000,
148+ # 取引手数料(為替価格に対する倍率で指定、為替価格100円でcommission=0.0005なら0.05円)
149+ # OANDAの場合0.3〜0.9銭=0.003~0.009円
150+ commission=0.00009,
151+ # レバレッジ倍率の逆数(0.5で2倍レバレッジ)
152+ margin=1.0,
153+ # True:現在の終値で取引,False:次の時間の始値で取引
154+ trade_on_close=True
155+)
156+
157+s_time = dt.datetime.timestamp(dt.datetime.utcnow())
158+# バックテスト実行
159+output = bt.run()
160+e_time = dt.datetime.timestamp(dt.datetime.utcnow())
161+# 経過時間を出力(秒)
162+p_time = round((e_time - s_time), 0)
163+print(f'Processing time(sec): {p_time}')
164+
165+# 実行結果(データ)
166+print(output)
167+
168+# Processing time(sec): 5.0
169+# Start 2022-11-18 00:00...
170+# End 2023-02-16 00:00...
171+# Duration 90 days 00:00:00
172+# Exposure Time [%] 97.433366
173+# Equity Final [$] 12025.707282
174+# Equity Peak [$] 12357.610885
175+# Return [%] 20.257073
176+# Buy & Hold Return [%] -4.777882
177+# Return (Ann.) [%] 85.150565
178+# Volatility (Ann.) [%] 22.785833
179+# Sharpe Ratio 3.736996
180+# Sortino Ratio 13.20781
181+# Calmar Ratio 20.604035
182+# Max. Drawdown [%] -4.132713
183+# Avg. Drawdown [%] -0.136679
184+# Max. Drawdown Duration 12 days 22:28:00
185+# Avg. Drawdown Duration 0 days 04:29:00
186+# # Trades 66
187+# Win Rate [%] 78.787879
188+# Best Trade [%] 1.289563
189+# Worst Trade [%] -1.422667
190+# Avg. Trade [%] 0.492818
191+# Max. Trade Duration 12 days 18:25:00
192+# Avg. Trade Duration 2 days 15:42:00
193+# Profit Factor 2.825621
194+# Expectancy [%] 0.497406
195+# SQN 2.896036
196+# _strategy MACDCross
197+# _equity_curve ...
198+# _trades Size EntryB...
199+# dtype: object
--- a/investigation_study/numpy_vector_angle_1.py
+++ b/investigation_study/numpy_vector_angle_1.py
@@ -45,3 +45,8 @@ u3 = np.array([0.007748, 0.007773])
4545 v3 = np.array([0.006698, 0.007639])
4646 print(vector_angle(u3, v3))
4747 # 3.6628990024599957
48+
49+u2 = np.array([-0.0295, -0.0293])
50+v2 = np.array([-0.0283, -0.0291])
51+print(vector_angle(u2, v2))
52+
--- a/investigation_study/oanda_v20_api_calendar_test.py
+++ b/investigation_study/oanda_v20_api_calendar_test.py
@@ -1,8 +1,9 @@
1+import os
12 import oandapyV20
23 import oandapyV20.endpoints.forexlabs as labs
34
4-
5-client = oandapyV20.API(access_token=...)
5+token = os.getenv('OANDA_TOKEN')
6+client = oandapyV20.API(access_token=token)
67 params = {
78 "instrument": "USD_JPY",
89 "period": 604800
@@ -26,3 +27,18 @@ params = {
2627 r = labs.Calendar(params=params)
2728 client.request(r)
2829 print(r.response)
30+
31+# oandapyV20.exceptions.V20Error: {
32+# "code" : 4,
33+# "message" : "The access token provided does not allow this request to be made",
34+# "moreInfo" : "http:\/\/developer.oanda.com\/docs\/v1\/authentication\/#overview"
35+# }
36+# 現状ではエラーとなってしまう
37+
38+# これは動作した
39+# curl -H "Content-Type: application/json" -H "Authorization: Bearer <token>" "https://api-fxtrade.oanda.com/v3/accounts"
40+# {"accounts":[{"id":"001-009-5378507-001","tags":[]},{"id":"001-009-5378507-002","tags":[]}]}
41+
42+# これはエラーが帰らないけどデータが無い様子
43+# curl -H "Content-Type: application/json" -H "Authorization: Bearer <token>" "https://api-fxtrade.oanda.com/labs/v1/calendar?instrument=USD_JPY&period=2592000"
44+# []
--- a/src/config/setting.ini
+++ b/src/config/setting.ini
@@ -36,17 +36,17 @@ Allowablespread = 0.01
3636 # 1銭は0.01円
3737 OpenMergin = 0.007
3838 # 利益確定(円)
39-ProfitTaking = 1.5
39+ProfitTaking = 0.15
4040 # 損切(円)
41-LossCut = 0.25
41+LossCut = 0.265
4242
4343 # テクニカル分析
4444 [Technical]
4545 # 短期EMAの期間
46-FastEMAperiod = 35
46+FastEMAperiod = 18
4747 # 長期EMAの期間
48-SlowEMAperiod = 53
48+SlowEMAperiod = 56
4949 # シグナルの期間
50-Signalperiod = 18
50+Signalperiod = 10
5151 # 交差角度閾値
52-CrossingAngleThreshold = 1.54
52+CrossingAngleThreshold = 0.576
--- a/src/exchange_rate_info.py
+++ b/src/exchange_rate_info.py
@@ -68,7 +68,14 @@ class ExchangeRateInfo():
6868 0,
6969 count
7070 )
71- return rates['close']
71+
72+ ret = rates['close']
73+
74+ # 最新bidを暫定closeとして追加
75+ tick = mt5.symbol_info_tick(self.__cp)
76+ ret = np.append(ret, tick.bid)
77+
78+ return ret
7279
7380 def __crossover(self, series1: np.ndarray, series2: np.ndarray) -> bool:
7481 """指標値列の交差判定
@@ -171,9 +178,8 @@ class ExchangeRateInfo():
171178 s = Setting()
172179
173180 # MACDテクニカル分析に必要なデータ件数
174- # mt5アプリの表示と数値の乖離があるので件数を増やしてみる
175- # count = (self.__n2 + self.__n1) * 20
176- count = self.__ns * 20
181+ # 暫定でmacd_value_verification.pyから1000と推定
182+ count = 1000
177183
178184 # 終値履歴を取得
179185 closes = self.__get_history_closes(
@@ -189,50 +195,62 @@ class ExchangeRateInfo():
189195 )
190196
191197 # MACDの結果を端数処理(少数点以下6)
198+ # 結果は最後の二つを使う
192199 macd = np.around(macd, 4)
193200 macdsignal = np.around(macdsignal, 4)
194201
195- # 前回結果が未設定なら判定しない
196- if self.__previous_macd is None:
197- self.__previous_macd = macd[-1]
198- self.__previous_signal = macdsignal[-1]
199- s.technical_indicator = DEF.TECHNICAL_UNCHANGED
200- msg = 'Previous MACD result not set.'
201- logger.info(msg)
202- return
203- else:
204- macd[-2] = self.__previous_macd
205- macdsignal[-2] = self.__previous_signal
206-
207202 # for debug
208- for i in range(-3, 0, 1):
203+ logger.debug('MACD実測値')
204+ for i in range(-6, 0, 1):
209205 msg = (
210206 f'num[{i}]: MACD: {macd[i]}, MACDSignal: {macdsignal[i]}'
211207 )
212208 logger.debug(msg)
213209
210+ # 指標判定は最後の2データで行う
211+ # macd = macd[-2:]
212+ # macdsignal = macdsignal[-2:]
213+
214+ # # 前回結果が未設定なら判定しない
215+ # if self.__previous_macd is None:
216+ # self.__previous_macd = macd[-1]
217+ # self.__previous_signal = macdsignal[-1]
218+ # s.technical_indicator = DEF.TECHNICAL_UNCHANGED
219+ # msg = 'Previous MACD result not set.'
220+ # logger.info(msg)
221+ # return
222+ # else:
223+ # macd[-2] = self.__previous_macd
224+ # macdsignal[-2] = self.__previous_signal
225+
226+ # for debug
227+ # logger.debug('MACD判定値')
228+ # for i in range(-2, 0, 1):
229+ # msg = (
230+ # f'num[{i}]: MACD: {macd[i]}, MACDSignal: {macdsignal[i]}'
231+ # )
232+ # logger.debug(msg)
233+
214234 # 指標判定
215235 v_angle = self.__vector_angle(macd[-2:], macdsignal[-2:])
216236 msg = f'vector angle:{v_angle}'
217237 logger.debug(msg)
218- msg = f'macd vector angle:{v_angle}'
219238
220- if self.__crossover(macd, macdsignal) and \
221- v_angle > self.__angle and \
222- macd[-1] < 0.0:
239+ msg = f'befor turning point:{s.turning_point}'
240+ logger.debug(msg)
241+
242+ if self.__crossover(macd[-2:], macdsignal[-2:]):
223243 logger.debug('Cross up.')
224- if s.turning_point is not True:
244+ if s.turning_point is not True and v_angle > self.__angle:
225245 # GoldenCross 上昇転換サイン
226246 s.technical_indicator = DEF.TECHNICAL_GOLDEN_CROSS
227247 s.turning_point = True
228248 msg = 'Technical Indicator: GoldenCross.'
229249 else:
230250 logger.debug('Already judged goldencross.')
231- elif self.__crossover(macdsignal, macd) and \
232- v_angle > self.__angle and \
233- macd[-1] > 0.0:
251+ elif self.__crossover(macdsignal[-2:], macd[-2:]):
234252 logger.debug('Cross down.')
235- if s.turning_point is not False:
253+ if s.turning_point is not False and v_angle > self.__angle:
236254 # DeadCross 降下転換サイン
237255 s.technical_indicator = DEF.TECHNICAL_DEAD_CROSS
238256 s.turning_point = False
@@ -251,11 +269,42 @@ class ExchangeRateInfo():
251269 # いずれにも該当しない
252270 s.technical_indicator = DEF.TECHNICAL_UNCHANGED
253271 msg = 'Technical Indicator: Unchanged.'
272+
273+ # 過去指標の再判定
274+ # 2023-02-22 02:17:04 num[-3]: MACD: 0.0278, MACDSignal: 0.0248 >
275+ # 2023-02-22 02:17:04 num[-2]: MACD: 0.0287, MACDSignal: 0.0255 > !
276+ # 2023-02-22 02:17:04 num[-1]: MACD: 0.0293, MACDSignal: 0.0262 > !
277+ # 2023-02-22 02:18:04 num[-4]: MACD: 0.0278, MACDSignal: 0.0248 >
278+ # 2023-02-22 02:18:04 num[-3]: MACD: 0.0259, MACDSignal: 0.025 >
279+ # 2023-02-22 02:18:04 num[-2]: MACD: 0.0235, MACDSignal: 0.0248 < !
280+ # 2023-02-22 02:18:04 num[-1]: MACD: 0.0213, MACDSignal: 0.0241 < !
281+ # このような事象が発生するので転換サインが出てない場合に限り
282+ # 過去指標(num[-2],num[-3])の再評価を行う
283+ if s.technical_indicator == DEF.TECHNICAL_UPTREND or \
284+ s.technical_indicator == DEF.TECHNICAL_DOWNTREND or \
285+ s.technical_indicator == DEF.TECHNICAL_UNCHANGED:
286+ if self.__crossover(macd[-3:-1], macdsignal[-3:-1]):
287+ logger.debug('Re-evaluation Cross up.')
288+ if s.turning_point is not True and v_angle > self.__angle:
289+ # GoldenCross 上昇転換サイン
290+ s.technical_indicator = DEF.TECHNICAL_GOLDEN_CROSS
291+ s.turning_point = True
292+ msg = 'Re-evaluation Technical Indicator: GoldenCross.'
293+ elif self.__crossover(macdsignal[-3:-1], macd[-3:-1]):
294+ logger.debug('Re-evaluation Cross down.')
295+ if s.turning_point is not False and v_angle > self.__angle:
296+ # DeadCross 降下転換サイン
297+ s.technical_indicator = DEF.TECHNICAL_DEAD_CROSS
298+ s.turning_point = False
299+ msg = 'Re-evaluation Technical Indicator: DeadCross.'
254300 logger.info(msg)
255301
302+ msg = f'after turning point:{s.turning_point}'
303+ logger.debug(msg)
304+
256305 # 今回MACD結果を退避
257- self.__previous_macd = macd[-1]
258- self.__previous_signal = macdsignal[-1]
306+ # self.__previous_macd = macd[-1]
307+ # self.__previous_signal = macdsignal[-1]
259308
260309 except Exception as e:
261310 logger.error(e)
--- a/src/main.py
+++ b/src/main.py
@@ -65,9 +65,9 @@ def main():
6565 # PositionMngは長期ポジションに対応する
6666 logger.debug("set schedule.")
6767 schedule.every().minute.at(":00").do(em.execute)
68- schedule.every().minute.at(":02").do(rt.execute)
69- schedule.every().minute.at(":04").do(pm.execute)
70- schedule.every().minute.at(":06").do(ac.execute)
68+ schedule.every().minute.at(":02").do(ac.execute)
69+ schedule.every().minute.at(":04").do(rt.execute)
70+ schedule.every().minute.at(":06").do(pm.execute)
7171 schedule.every().minute.at(":08").do(tm.execute)
7272
7373 logger.debug("start loop.")
--- a/src/position_mng.py
+++ b/src/position_mng.py
@@ -14,6 +14,8 @@ class PositionMng():
1414 # check_position
1515 # ポジション確認
1616 # テクニカル指標を確認しポジションを解消する
17+ # ポジションが全くない状態でテクニカルフラグがOnなら
18+ # フラグを初期化する
1719
1820 def __init__(self):
1921 """initializer
@@ -42,6 +44,10 @@ class PositionMng():
4244 if positions is None:
4345 # 保有ポジションは無いとする
4446 logger.info('No open position.')
47+ if s.turning_point is not None and \
48+ s.technical_indicator != DEF.TECHNICAL_GOLDEN_CROSS and \
49+ s.technical_indicator != DEF.TECHNICAL_DEAD_CROSS:
50+ s.turning_point = None
4551 return
4652
4753 # テクニカル指標を確認する