openfund-maker 2.3.1__py3-none-any.whl → 2.3.3__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- maker/BestTopDownStrategyMaker.py +149 -67
- maker/StrategyMaker.py +67 -18
- {openfund_maker-2.3.1.dist-info → openfund_maker-2.3.3.dist-info}/METADATA +1 -1
- {openfund_maker-2.3.1.dist-info → openfund_maker-2.3.3.dist-info}/RECORD +6 -6
- {openfund_maker-2.3.1.dist-info → openfund_maker-2.3.3.dist-info}/WHEEL +0 -0
- {openfund_maker-2.3.1.dist-info → openfund_maker-2.3.3.dist-info}/entry_points.txt +0 -0
@@ -38,8 +38,8 @@ class BestTopDownStrategyMaker(StrategyMaker):
|
|
38
38
|
|
39
39
|
"""
|
40
40
|
try:
|
41
|
-
#
|
42
|
-
if self.
|
41
|
+
# 是否有持仓,有持仓不进行下单
|
42
|
+
if self.fetch_position(symbol=symbol) :
|
43
43
|
self.reset_all_cache(symbol)
|
44
44
|
self.logger.info(f"{symbol} : 有持仓合约,不进行下单。")
|
45
45
|
return
|
@@ -67,9 +67,8 @@ class BestTopDownStrategyMaker(StrategyMaker):
|
|
67
67
|
htf_df = self.get_historical_klines_df_by_cache(symbol=symbol, tf=htf)
|
68
68
|
htf_struct =self.build_struct(symbol=symbol, data=htf_df)
|
69
69
|
|
70
|
-
htf_latest_struct = self.
|
71
|
-
|
72
|
-
htf_trend = self.BULLISH_TREND if htf_latest_struct[self.STRUCT_DIRECTION_COL] == 1 else self.BEARISH_TREND
|
70
|
+
htf_latest_struct = self.get_latest_struct(symbol=symbol, data=htf_struct)
|
71
|
+
htf_trend = htf_latest_struct[self.STRUCT_DIRECTION_COL]
|
73
72
|
htf_side = self.BUY_SIDE if htf_trend == self.BULLISH_TREND else self.SELL_SIDE
|
74
73
|
# 1.1. Price's Current Trend 市场趋势(HTF)
|
75
74
|
step = "1.1"
|
@@ -86,15 +85,15 @@ class BestTopDownStrategyMaker(StrategyMaker):
|
|
86
85
|
self.logger.debug(f"{symbol} : {step}. HTF {htf} 未找到OB。")
|
87
86
|
return
|
88
87
|
else:
|
89
|
-
self.logger.debug(f"{symbol} : {step}. HTF {htf} 找到OB。")
|
88
|
+
# self.logger.debug(f"{symbol} : {step}. HTF {htf} 找到OB。")
|
90
89
|
|
91
|
-
htf_support_OB = self.
|
90
|
+
htf_support_OB = self.get_latest_OB(symbol=symbol,data=htf_OBs_df,trend=self.BULLISH_TREND)
|
92
91
|
if htf_support_OB :
|
93
92
|
htf_support_price = htf_support_OB.get(self.OB_MID_COL)
|
94
93
|
else:
|
95
94
|
htf_support_price = htf_struct.at[htf_struct.index[-1], self.STRUCT_LOW_COL]
|
96
95
|
|
97
|
-
htf_resistance_OB = self.
|
96
|
+
htf_resistance_OB = self.get_latest_OB(symbol=symbol,data=htf_OBs_df,trend=self.BEARISH_TREND)
|
98
97
|
if htf_resistance_OB :
|
99
98
|
htf_resistance_price = htf_resistance_OB.get(self.OB_MID_COL)
|
100
99
|
else:
|
@@ -104,7 +103,7 @@ class BestTopDownStrategyMaker(StrategyMaker):
|
|
104
103
|
step = "1.4"
|
105
104
|
# 计算支撑位和阻力位之间的利润空间百分比
|
106
105
|
htf_profit_percent = abs((htf_resistance_price - htf_support_price) / htf_support_price * 100)
|
107
|
-
min_profit_percent =
|
106
|
+
min_profit_percent = top_down_strategy.get('min_profit_percent', 4) # 默认最小利润空间为0.5%
|
108
107
|
|
109
108
|
if htf_profit_percent < min_profit_percent:
|
110
109
|
self.logger.info(f"{symbol} : {step}. HTF {htf} 支撑位={htf_support_price:.{precision}f} 与阻力位={htf_resistance_price:.{precision}f} 之间利润空间{htf_profit_percent:.2f}% < {min_profit_percent}%,等待...")
|
@@ -114,17 +113,37 @@ class BestTopDownStrategyMaker(StrategyMaker):
|
|
114
113
|
|
115
114
|
# 1.5. 检查当前价格是否在关键支撑位和阻力位,支撑位可以做多,阻力位可以做空。
|
116
115
|
step = "1.5"
|
117
|
-
|
118
|
-
|
119
|
-
|
120
|
-
|
121
|
-
|
122
|
-
|
123
|
-
|
124
|
-
|
116
|
+
htf_support_OB_top = None
|
117
|
+
if htf_support_OB :
|
118
|
+
htf_support_OB_top = htf_support_OB.get(self.OB_HIGH_COL)
|
119
|
+
htf_resistance_OB_bottom = None
|
120
|
+
if htf_resistance_OB :
|
121
|
+
htf_resistance_OB_bottom = htf_resistance_OB.get(self.OB_LOW_COL)
|
122
|
+
|
123
|
+
# 检查支撑位做多条件
|
124
|
+
if htf_support_OB_top is not None:
|
125
|
+
if market_price <= htf_support_OB_top:
|
126
|
+
# 价格进入支撑OB,可以开始做多
|
127
|
+
if htf_side != self.BUY_SIDE:
|
128
|
+
htf_side = self.BUY_SIDE
|
129
|
+
self.logger.info(f"{symbol} : {step}. HTF {htf} 当前价格{market_price:.{precision}f} <= HTF_OB_TOP{htf_support_OB_top:.{precision}f}, 开始做多{htf_side}。")
|
130
|
+
else:
|
131
|
+
self.logger.info(f"{symbol} : {step}. HTF {htf} 当前价格{market_price:.{precision}f} > HTF_OB_TOP{htf_support_OB_top:.{precision}f}, 无需做多{htf_side}。")
|
125
132
|
else:
|
126
|
-
self.logger.info(f"{symbol} : {step}. HTF {htf}
|
127
|
-
|
133
|
+
self.logger.info(f"{symbol} : {step}. HTF {htf} 未找到HTF_OB_TOP。")
|
134
|
+
|
135
|
+
# 检查阻力位做空条件
|
136
|
+
if htf_resistance_OB_bottom is not None:
|
137
|
+
if market_price >= htf_resistance_OB_bottom:
|
138
|
+
# 价格进入阻力OB,可以开始做空
|
139
|
+
if htf_side != self.SELL_SIDE:
|
140
|
+
htf_side = self.SELL_SIDE
|
141
|
+
self.logger.info(f"{symbol} : {step}. HTF {htf} 当前价格{market_price:.{precision}f} >= HTF_OB_BOTTOM{htf_resistance_OB_bottom:.{precision}f}, 开始做空{htf_side}。")
|
142
|
+
else:
|
143
|
+
self.logger.info(f"{symbol} : {step}. HTF {htf} 当前价格{market_price:.{precision}f} < HTF_OB_BOTTOM{htf_resistance_OB_bottom:.{precision}f}, 无需做空{htf_side}。")
|
144
|
+
else:
|
145
|
+
self.logger.info(f"{symbol} : {step}. HTF {htf} 未找到HTF_OB_BOTTOM。")
|
146
|
+
|
128
147
|
"""
|
129
148
|
step 2 : Analysis Time Frames
|
130
149
|
"""
|
@@ -134,7 +153,11 @@ class BestTopDownStrategyMaker(StrategyMaker):
|
|
134
153
|
atf_side, atf_struct, atf_trend = None, None, None
|
135
154
|
atf_df = self.get_historical_klines_df(symbol=symbol, tf=atf)
|
136
155
|
atf_struct =self.build_struct(symbol=symbol, data=atf_df)
|
137
|
-
|
156
|
+
# 获取最新的市场结构,如果为空则返回None
|
157
|
+
atf_latest_struct = self.get_latest_struct(symbol=symbol, data=atf_struct)
|
158
|
+
if atf_latest_struct is None:
|
159
|
+
self.logger.info(f"{symbol} : {step}. ATF {atf} 未形成结构,等待... ")
|
160
|
+
return
|
138
161
|
atf_trend = atf_latest_struct[self.STRUCT_DIRECTION_COL]
|
139
162
|
atf_side = self.BUY_SIDE if atf_trend == self.BULLISH_TREND else self.SELL_SIDE
|
140
163
|
# 2.1. Price's Current Trend 市场趋势(HTF )
|
@@ -143,22 +166,81 @@ class BestTopDownStrategyMaker(StrategyMaker):
|
|
143
166
|
# 2.2. Who's In Control 供需控制,Bullish 或者 Bearish | Choch 或者 BOS
|
144
167
|
step = "2.2"
|
145
168
|
self.logger.info(f"{symbol} : {step}. ATF {atf} struct is {atf_latest_struct[self.STRUCT_COL]}。")
|
146
|
-
#
|
169
|
+
# 2.3. 检查关键支撑位和阻力位之间是否有利润空间。
|
170
|
+
step = "2.3"
|
171
|
+
atf_OBs_df = self.find_OBs(symbol=symbol,struct=atf_struct)
|
172
|
+
atf_support_OB = self.get_latest_OB(symbol=symbol,data=atf_OBs_df,trend=self.BULLISH_TREND)
|
173
|
+
if atf_support_OB :
|
174
|
+
atf_support_price = atf_support_OB.get(self.OB_MID_COL)
|
175
|
+
else:
|
176
|
+
atf_support_price = atf_struct.at[atf_struct.index[-1], self.STRUCT_LOW_COL]
|
177
|
+
|
178
|
+
atf_resistance_OB = self.get_latest_OB(symbol=symbol,data=atf_OBs_df,trend=self.BEARISH_TREND)
|
179
|
+
if atf_resistance_OB :
|
180
|
+
atf_resistance_price = atf_resistance_OB.get(self.OB_MID_COL)
|
181
|
+
else:
|
182
|
+
atf_resistance_price = atf_struct.at[atf_struct.index[-1], self.STRUCT_HIGH_COL]
|
183
|
+
|
184
|
+
self.logger.info(f"{symbol} : {step}.1 ATF {atf}, Key Support={atf_support_price:.{precision}f} "
|
185
|
+
f"& Key Resistance={atf_resistance_price:.{precision}f} ")
|
186
|
+
# 计算支撑位和阻力位之间的利润空间百分比
|
187
|
+
atf_profit_percent = abs((atf_resistance_price - atf_support_price) / atf_support_price * 100)
|
188
|
+
|
189
|
+
if atf_profit_percent < min_profit_percent:
|
190
|
+
self.logger.info(f"{symbol} : {step}.2 ATF {atf} 支撑位={atf_support_price:.{precision}f} 与阻力位={atf_resistance_price:.{precision}f} "
|
191
|
+
f"之间利润空间{atf_profit_percent:.2f}% < {min_profit_percent}%,等待...")
|
192
|
+
return
|
193
|
+
else:
|
194
|
+
self.logger.info(f"{symbol} : {step}.2 ATF {atf} 支撑位={atf_support_price:.{precision}f} 与阻力位={atf_resistance_price:.{precision}f} "
|
195
|
+
f"之间利润空间{atf_profit_percent:.2f}% >= {min_profit_percent}%,允许下单...")
|
196
|
+
|
197
|
+
|
147
198
|
|
148
199
|
# 2.4. ATF 方向要和 HTF方向一致
|
149
200
|
step = "2.4"
|
150
201
|
|
151
|
-
if
|
152
|
-
self.logger.info(f"{symbol} : {step}. ATF {atf} is {
|
202
|
+
if htf_side != atf_side:
|
203
|
+
self.logger.info(f"{symbol} : {step}. ATF {atf} is {atf_side} 与 HTF {htf} is {htf_side} 不一致,等待...")
|
153
204
|
return
|
154
205
|
else:
|
155
|
-
self.logger.info(f"{symbol} : {step}. ATF {atf} is {
|
156
|
-
|
157
|
-
|
158
|
-
|
206
|
+
self.logger.info(f"{symbol} : {step}. ATF {atf} is {atf_side} 与 HTF {htf} is {htf_side} 一致。")
|
207
|
+
|
208
|
+
#2.5. 反转结构CHOCH, check Liquidity Areas ,检查当前结构是否是流动性摄取。
|
209
|
+
step = "2.5"
|
210
|
+
# if "CHOCH" in atf_struct[self.STRUCT_COL] or "BOS" in atf_struct[self.STRUCT_COL]:
|
211
|
+
# 2.5.1. Equal Lows & Equal Highs
|
212
|
+
if top_down_strategy.get('open_check_liquidity_areas', True):
|
213
|
+
end_idx = atf_latest_struct[self.STRUCT_HIGH_INDEX_COL] if atf_side == self.BUY_SIDE else atf_latest_struct[self.STRUCT_LOW_INDEX_COL]
|
214
|
+
last_EQ = self.find_EQH_EQL(symbol=symbol, data=atf_df, trend=atf_trend, end_idx=end_idx, pair_config=pair_config)
|
215
|
+
if last_EQ and last_EQ[self.HAS_EQ_KEY]:
|
216
|
+
price_eq = last_EQ[self.EQUAL_HIGH_COL] if atf_side == self.BUY_SIDE else last_EQ[self.EQUAL_LOW_COL]
|
217
|
+
self.logger.info(f"{symbol} : {step}.1 ATF {atf} {atf_side} find EQ {price_eq}")
|
218
|
+
# 检查是否Liquidity Sweeps
|
219
|
+
if (atf_side == self.BUY_SIDE and atf_latest_struct[self.STRUCT_HIGH_COL] > price_eq) \
|
220
|
+
or (atf_side == self.SELL_SIDE and atf_latest_struct[self.STRUCT_LOW_COL] < price_eq):
|
221
|
+
|
222
|
+
atf_side = self.SELL_SIDE if atf_side == self.BUY_SIDE else self.BUY_SIDE
|
223
|
+
self.logger.info(f"{symbol} : {step}.1 ATF {atf} Liquidity Sweeps , Reverse the ATF {atf} {atf_side} side。")
|
224
|
+
else:
|
225
|
+
self.logger.info(f"{symbol} : {step}.1 ATF {atf} is not found Liquidity Sweeps .")
|
226
|
+
else:
|
227
|
+
self.logger.info(f"{symbol} : {step}.1 ATF {atf} is not found EQ .")
|
228
|
+
|
229
|
+
# FIXME 2.5.2. Dynamic Trendlines and Channels
|
230
|
+
if top_down_strategy.get('open_check_dynamic_trendlines_and_channels', True):
|
231
|
+
atf_pre_struct = atf_struct[atf_struct[self.STRUCT_DIRECTION_COL].notna()].iloc[-2] # 看前一个结构是否为动态趋势
|
232
|
+
atf_start_index = min(atf_pre_struct[self.STRUCT_LOW_INDEX_COL] ,atf_pre_struct[self.STRUCT_HIGH_INDEX_COL])
|
233
|
+
atf_end_index = max(atf_latest_struct[self.STRUCT_LOW_INDEX_COL] ,atf_latest_struct[self.STRUCT_HIGH_INDEX_COL])
|
234
|
+
|
235
|
+
is_dynamic_trendlines = self.identify_dynamic_trendlines(symbol=symbol, data=atf_struct, trend=atf_trend, start_idx=atf_start_index, end_idx=atf_end_index)
|
236
|
+
if is_dynamic_trendlines :
|
237
|
+
self.logger.info(f"{symbol} : {step}.2 ATF {atf} {atf_trend} find Dynamic Trendlines .")
|
238
|
+
else:
|
239
|
+
self.logger.info(f"{symbol} : {step}.2 ATF {atf} {atf_trend} not find Dynamic Trendlines .")
|
240
|
+
|
159
241
|
|
160
242
|
# 2.6. 在HTF供需区范围,找ATF的PDArray,FVG和OB,供需区,计算监测下单区域范围。
|
161
|
-
|
243
|
+
step = "2.6"
|
162
244
|
atf_pdArrays_df = self.find_PDArrays(symbol=symbol,struct=atf_struct,side=atf_side)
|
163
245
|
|
164
246
|
# 不同的结构,不同位置,如果是Choch则等待价格进入PDArray,如果是BOS则等待价格进入折价区
|
@@ -170,150 +252,150 @@ class BestTopDownStrategyMaker(StrategyMaker):
|
|
170
252
|
if "CHOCH" in atf_struct[self.STRUCT_COL]:
|
171
253
|
# 找PDArray,Bullish 则PDArray的mid要小于 atf_struct_mid,Bearish 则PDArray的mid要大于 atf_struct_mid
|
172
254
|
# atf_discount_mid = (atf_struct_mid + atf_struct_high) / 2 if atf_trend == self.BEARISH_TREND else (atf_struct_mid + atf_struct_low) / 2
|
173
|
-
mask = atf_pdArrays_df[self.PD_MID_COL] >= atf_struct_mid if
|
255
|
+
mask = atf_pdArrays_df[self.PD_MID_COL] >= atf_struct_mid if atf_side == self.BUY_SIDE else atf_pdArrays_df[self.PD_MID_COL] <= atf_struct_mid
|
174
256
|
atf_pdArrays_df = atf_pdArrays_df[mask]
|
175
257
|
if len(atf_pdArrays_df) == 0:
|
176
|
-
self.logger.info(f"{symbol} : {
|
258
|
+
self.logger.info(f"{symbol} : {step}.1. ATF {atf} 未找到PDArray,不下单")
|
177
259
|
return
|
178
260
|
else:
|
179
261
|
# 找到最新的PDArray
|
180
262
|
atf_vaild_pdArray = atf_pdArrays_df.iloc[-1]
|
181
|
-
self.logger.info(f"{symbol} : {
|
263
|
+
self.logger.info(f"{symbol} : {step}.1. ATF {atf} 找到PDArray\n"
|
182
264
|
f"{atf_vaild_pdArray[[self.TIMESTAMP_COL,self.PD_TYPE_COL,self.PD_HIGH_COL,self.PD_LOW_COL,self.PD_MID_COL]]}。")
|
183
265
|
|
184
266
|
|
185
267
|
#SMS
|
186
268
|
elif "SMS" in atf_struct[self.STRUCT_COL]:
|
187
|
-
mask = atf_pdArrays_df[self.PD_MID_COL] >= atf_struct_mid if
|
269
|
+
mask = atf_pdArrays_df[self.PD_MID_COL] >= atf_struct_mid if atf_side == self.BUY_SIDE else atf_pdArrays_df[self.PD_MID_COL] <= atf_struct_mid
|
188
270
|
atf_pdArrays_df = atf_pdArrays_df[mask]
|
189
271
|
if len(atf_pdArrays_df) == 0:
|
190
|
-
self.logger.info(f"{symbol} : {
|
272
|
+
self.logger.info(f"{symbol} : {step}.1. ATF {atf} 在{atf_struct_mid:.{precision}f}未找到PDArray,不下单")
|
191
273
|
return
|
192
274
|
else:
|
193
275
|
# 找到最新的PDArray
|
194
276
|
atf_vaild_pdArray = atf_pdArrays_df.iloc[-1]
|
195
|
-
self.logger.info(f"{symbol} : {
|
277
|
+
self.logger.info(f"{symbol} : {step}.1. ATF {atf} 找到PDArray\n"
|
196
278
|
f"{atf_vaild_pdArray[[self.TIMESTAMP_COL,self.PD_TYPE_COL,self.PD_HIGH_COL,self.PD_LOW_COL,self.PD_MID_COL]]}。")
|
197
279
|
|
198
280
|
|
199
281
|
#BMS
|
200
282
|
else:
|
201
|
-
atf_premium_mid = (atf_struct_mid + atf_struct_low) / 2 if
|
202
|
-
mask = atf_pdArrays_df[self.PD_HIGH_COL] >= atf_premium_mid if
|
283
|
+
atf_premium_mid = (atf_struct_mid + atf_struct_low) / 2 if atf_side == self.BUY_SIDE else (atf_struct_mid + atf_struct_high) / 2
|
284
|
+
mask = atf_pdArrays_df[self.PD_HIGH_COL] >= atf_premium_mid if atf_side == self.BUY_SIDE else atf_pdArrays_df[self.PD_LOW_COL] <= atf_premium_mid
|
203
285
|
atf_pdArrays_df = atf_pdArrays_df[mask]
|
204
286
|
if len(atf_pdArrays_df) == 0:
|
205
|
-
self.logger.info(f"{symbol} : {
|
287
|
+
self.logger.info(f"{symbol} : {step}.1. ATF {atf} ,在{atf_premium_mid:.{precision}f}未找到PDArray,不下单")
|
206
288
|
return
|
207
289
|
else:
|
208
290
|
# 找到最新的PDArray
|
209
291
|
atf_vaild_pdArray = atf_pdArrays_df.iloc[-1]
|
210
|
-
self.logger.info(f"{symbol} : {
|
292
|
+
self.logger.info(f"{symbol} : {step}.1. ATF {atf} 找到PDArray\n"
|
211
293
|
f"{atf_vaild_pdArray[[self.TIMESTAMP_COL,self.PD_TYPE_COL,self.PD_HIGH_COL,self.PD_LOW_COL,self.PD_MID_COL]]}")
|
212
294
|
|
213
295
|
|
214
296
|
|
215
|
-
|
297
|
+
step = "2.7"
|
216
298
|
|
217
299
|
# 2.7. 等待价格进入 PDArray
|
218
300
|
|
219
301
|
if not (market_price <= atf_vaild_pdArray[self.PD_HIGH_COL] and market_price >= atf_vaild_pdArray[self.PD_LOW_COL]):
|
220
|
-
self.logger.info(f"{symbol} : {
|
302
|
+
self.logger.info(f"{symbol} : {step}. ATF {atf} market_price={market_price:.{precision}f} 未达到PDArray范围。"
|
221
303
|
f"PD_HIGH={atf_vaild_pdArray[self.PD_LOW_COL]:.{precision}f} "
|
222
304
|
f"PD_LOW={atf_vaild_pdArray[self.PD_HIGH_COL]:.{precision}f} ")
|
223
305
|
|
224
306
|
return
|
225
307
|
else:
|
226
|
-
self.logger.info(f"{symbol} : {
|
308
|
+
self.logger.info(f"{symbol} : {step}. ATF {atf} market_price={market_price:.{precision}f} 已到达PDArray范围。"
|
227
309
|
f"PD_HIGH={atf_vaild_pdArray[self.PD_LOW_COL]:.{precision}f} "
|
228
310
|
f"PD_LOW={atf_vaild_pdArray[self.PD_HIGH_COL]:.{precision}f} ")
|
229
311
|
|
230
312
|
|
231
313
|
# 3. ETF Step
|
232
|
-
|
314
|
+
step = "3.1"
|
233
315
|
etf_side, etf_struct, etf_trend = None, None, None
|
234
316
|
etf_df = self.get_historical_klines_df(symbol=symbol, tf=etf)
|
235
317
|
etf_struct =self.build_struct(symbol=symbol, data=etf_df)
|
236
|
-
etf_latest_struct = self.
|
318
|
+
etf_latest_struct = self.get_latest_struct(symbol=symbol, data=etf_struct)
|
237
319
|
|
238
|
-
# 初始化
|
239
|
-
|
240
|
-
|
241
|
-
|
242
|
-
etf_trend =
|
243
|
-
|
320
|
+
# 初始化ETF趋势相关变量
|
321
|
+
if etf_latest_struct is None:
|
322
|
+
self.logger.info(f"{symbol} : {step}. ETF {etf} 未形成结构,等待... ")
|
323
|
+
return
|
324
|
+
etf_trend = etf_latest_struct[self.STRUCT_DIRECTION_COL]
|
244
325
|
etf_side = self.BUY_SIDE if etf_trend == self.BULLISH_TREND else self.SELL_SIDE
|
245
326
|
|
246
327
|
# 3.1. Price's Current Trend 市场趋势(ETF )
|
247
|
-
|
248
|
-
self.logger.info(f"{symbol} : {
|
328
|
+
step = "3.1"
|
329
|
+
self.logger.info(f"{symbol} : {step}. ETF {etf} Price's Current Trend is {etf_trend}。")
|
249
330
|
# 3.2. Who's In Control 供需控制,Bullish 或者 Bearish | Choch 或者 BOS
|
250
|
-
|
251
|
-
self.logger.info(f"{symbol} : {
|
331
|
+
step = "3.2"
|
332
|
+
self.logger.info(f"{symbol} : {step}. ETF {etf} struct is {etf_latest_struct[self.STRUCT_COL]}。")
|
252
333
|
|
253
334
|
|
254
335
|
# 3.3 Reversal Signs 反转信号
|
255
|
-
|
336
|
+
step = "3.3"
|
256
337
|
|
257
|
-
if
|
338
|
+
if atf_side != etf_side:
|
258
339
|
|
259
|
-
self.logger.info(f"{symbol} : {
|
340
|
+
self.logger.info(f"{symbol} : {step}. ETF {etf} 市场结构{etf_latest_struct[self.STRUCT_COL]}未反转,等待...")
|
260
341
|
return
|
261
342
|
else:
|
262
|
-
self.logger.info(f"{symbol} : {
|
343
|
+
self.logger.info(f"{symbol} : {step}. ETF {etf} 市场结构{etf_latest_struct[self.STRUCT_COL]}已反转。")
|
263
344
|
|
264
345
|
# TODO "CHOCH"|"BOS" 的PDArray 入场位置不一样
|
265
346
|
|
266
347
|
# 3.4 找 PD Arrays 价格区间(ETF 看上下的供需区位置)
|
267
|
-
|
348
|
+
step = "3.4"
|
268
349
|
etf_pdArrays_df = self.find_PDArrays(symbol=symbol,struct=etf_struct,side=etf_side)
|
269
350
|
# 划分 折价(discount)区和溢价(premium)区
|
270
351
|
etf_struct_high = etf_latest_struct[self.STRUCT_HIGH_COL]
|
271
352
|
etf_struct_low = etf_latest_struct[self.STRUCT_LOW_COL]
|
272
353
|
etf_struct_mid = etf_latest_struct[self.STRUCT_MID_COL]
|
273
|
-
mask = etf_pdArrays_df[self.PD_MID_COL] >= etf_struct_mid if
|
354
|
+
mask = etf_pdArrays_df[self.PD_MID_COL] >= etf_struct_mid if etf_side == self.SELL_SIDE else etf_pdArrays_df[self.PD_MID_COL] <= etf_struct_mid
|
274
355
|
etf_pdArrays_df = etf_pdArrays_df[mask]
|
275
356
|
if len(etf_pdArrays_df) == 0:
|
276
|
-
self.logger.info(f"{symbol} : {
|
357
|
+
self.logger.info(f"{symbol} : {step}.1. ETF {etf} 未找到PDArray,不下单")
|
277
358
|
return
|
278
359
|
else:
|
279
360
|
# 找到最新的PDArray
|
280
361
|
etf_vaild_pdArray = etf_pdArrays_df.iloc[-1]
|
281
|
-
self.logger.info(f"{symbol} : {
|
362
|
+
self.logger.info(f"{symbol} : {step}.1. ETF {etf} 找到PDArray.\n"
|
282
363
|
f"{etf_vaild_pdArray[[self.TIMESTAMP_COL,self.PD_TYPE_COL,self.PD_HIGH_COL,self.PD_LOW_COL,self.PD_MID_COL]]}。")
|
283
364
|
|
284
365
|
|
285
366
|
if not (market_price <= etf_vaild_pdArray[self.PD_HIGH_COL] and market_price >= etf_vaild_pdArray[self.PD_LOW_COL]):
|
286
|
-
self.logger.info(f"{symbol} : {
|
367
|
+
self.logger.info(f"{symbol} : {step}.2. ETF {etf} market_price={market_price:.{precision}f} 未达到PDArray范围。"
|
287
368
|
f"PD_HIGH={etf_vaild_pdArray[self.PD_HIGH_COL]:.{precision}f} "
|
288
369
|
f"PD_LOW={etf_vaild_pdArray[self.PD_LOW_COL]:.{precision}f}")
|
289
370
|
|
290
371
|
return
|
291
372
|
else:
|
292
|
-
self.logger.info(f"{symbol} : {
|
373
|
+
self.logger.info(f"{symbol} : {step}.2. ETF {etf} market_price={market_price:.{precision}f} 已到达PDArray范围。"
|
293
374
|
f"PD_HIGH={etf_vaild_pdArray[self.PD_HIGH_COL]:.{precision}f} "
|
294
375
|
f"PD_LOW={etf_vaild_pdArray[self.PD_LOW_COL]:.{precision}f}")
|
295
376
|
|
296
377
|
# 3.5 Place Order 下单
|
297
|
-
|
378
|
+
step = "3.5"
|
298
379
|
# order_price = self.toDecimal(etf_vaild_pdArray[self.PD_HIGH_COL] if etf_trend == self.BULLISH_TREND else etf_vaild_pdArray[self.PD_LOW_COL] )
|
299
380
|
order_price = self.toDecimal(etf_vaild_pdArray[self.PD_MID_COL])
|
300
381
|
|
301
382
|
latest_order_price = self.toDecimal(self.place_order_prices.get(symbol,0))
|
302
383
|
if order_price == latest_order_price:
|
303
|
-
self.logger.info(f"{symbol} : {
|
384
|
+
self.logger.info(f"{symbol} : {step}. ETF {etf}, 下单价格 {order_price:.{precision}} 未变化,不进行下单。")
|
304
385
|
return
|
305
386
|
|
306
387
|
self.cancel_all_orders(symbol=symbol)
|
307
388
|
self.place_order(symbol=symbol, price=order_price, side=etf_side, pair_config=pair_config)
|
308
389
|
self.place_order_prices[symbol] = order_price # 记录下单价格,过滤重复下单
|
309
|
-
self.logger.info(f"{symbol} : {
|
390
|
+
self.logger.info(f"{symbol} : {step}. ETF {etf}, {etf_side} 价格={order_price:.{precision}}")
|
310
391
|
|
311
392
|
|
312
393
|
except KeyboardInterrupt:
|
313
394
|
self.logger.info("程序收到中断信号,开始退出...")
|
314
395
|
except Exception as e:
|
315
396
|
error_message = f"程序异常退出: {str(e)}"
|
316
|
-
|
397
|
+
# 记录错误信息和堆栈跟踪
|
398
|
+
self.logger.error(f"{error_message}\n{traceback.format_exc()}")
|
317
399
|
traceback.print_exc()
|
318
400
|
self.send_feishu_notification(symbol, error_message)
|
319
401
|
finally:
|
maker/StrategyMaker.py
CHANGED
@@ -1,6 +1,5 @@
|
|
1
1
|
# -*- coding: utf-8 -*-
|
2
2
|
from functools import lru_cache
|
3
|
-
import traceback
|
4
3
|
import pandas as pd
|
5
4
|
from datetime import datetime, timedelta
|
6
5
|
from decimal import Decimal
|
@@ -14,7 +13,8 @@ from core.smc import (
|
|
14
13
|
SMCPDArray,
|
15
14
|
SMCStruct,
|
16
15
|
SMCOrderBlock,
|
17
|
-
SMCFVG
|
16
|
+
SMCFVG,
|
17
|
+
SMCLiquidity
|
18
18
|
)
|
19
19
|
|
20
20
|
class StrategyMaker():
|
@@ -53,6 +53,14 @@ class StrategyMaker():
|
|
53
53
|
PD_LOW_COL = SMCPDArray.SMCPDArray.PD_LOW_COL
|
54
54
|
PD_MID_COL = SMCPDArray.SMCPDArray.PD_MID_COL
|
55
55
|
PD_TYPE_COL = SMCPDArray.SMCPDArray.PD_TYPE_COL
|
56
|
+
|
57
|
+
LIQU_HIGH_COL = SMCLiquidity.SMCLiquidity.LIQU_HIGH_COL
|
58
|
+
LIQU_LOW_COL = SMCLiquidity.SMCLiquidity.LIQU_LOW_COL
|
59
|
+
EQUAL_HIGH_COL = SMCLiquidity.SMCLiquidity.EQUAL_HIGH_COL
|
60
|
+
EQUAL_LOW_COL = SMCLiquidity.SMCLiquidity.EQUAL_LOW_COL
|
61
|
+
EQH_INDEX_KEY = SMCLiquidity.SMCLiquidity.EQUAL_HIGH_INDEX_KEY
|
62
|
+
EQL_INDEX_KEY = SMCLiquidity.SMCLiquidity.EQUAL_LOW_INDEX_KEY
|
63
|
+
HAS_EQ_KEY = SMCLiquidity.SMCLiquidity.HAS_EQ_KEY
|
56
64
|
|
57
65
|
def __init__(self, config, platform_config, common_config, feishu_webhook=None, logger=None ,exchangeKey='okx'):
|
58
66
|
"""_summary_
|
@@ -97,6 +105,7 @@ class StrategyMaker():
|
|
97
105
|
self.smcStruct = SMCStruct.SMCStruct()
|
98
106
|
self.smcOB = SMCOrderBlock.SMCOrderBlock()
|
99
107
|
self.smcFVG = SMCFVG.SMCFVG()
|
108
|
+
self.smcLiqu = SMCLiquidity.SMCLiquidity()
|
100
109
|
|
101
110
|
self.interval_map = {
|
102
111
|
'1d': 24 * 60 * 60 , # 1天
|
@@ -183,17 +192,22 @@ class StrategyMaker():
|
|
183
192
|
|
184
193
|
# 记录下单日志
|
185
194
|
direction = self.BULLISH_TREND if side == self.BUY_SIDE else self.BEARISH_TREND
|
186
|
-
self.logger.
|
195
|
+
self.logger.info(f"{symbol} : 触发{direction}下单条件. 下单价格: {price}")
|
187
196
|
|
188
197
|
# 执行下单
|
189
|
-
|
190
|
-
|
191
|
-
|
192
|
-
|
193
|
-
|
194
|
-
|
195
|
-
|
196
|
-
|
198
|
+
try :
|
199
|
+
self.exchange.place_order(
|
200
|
+
symbol=symbol,
|
201
|
+
price=price,
|
202
|
+
amount_usdt=order_amount_usdt,
|
203
|
+
side=side,
|
204
|
+
leverage=leverage,
|
205
|
+
order_type=order_type
|
206
|
+
)
|
207
|
+
except Exception as e:
|
208
|
+
error_message = f"{symbol} 下单失败: {e}"
|
209
|
+
self.logger.warning(error_message)
|
210
|
+
self.send_feishu_notification(symbol, error_message)
|
197
211
|
|
198
212
|
def cancel_all_orders(self, symbol):
|
199
213
|
"""_summary_
|
@@ -201,7 +215,13 @@ class StrategyMaker():
|
|
201
215
|
Args:
|
202
216
|
symbol (_type_): _description_
|
203
217
|
"""
|
204
|
-
|
218
|
+
try:
|
219
|
+
self.exchange.cancel_all_orders(symbol=symbol)
|
220
|
+
except Exception as e:
|
221
|
+
error_message = f"{symbol} 取消所有挂单失败: {e}"
|
222
|
+
self.logger.warning(error_message)
|
223
|
+
self.send_feishu_notification(symbol, error_message)
|
224
|
+
|
205
225
|
def get_historical_klines(self, symbol, tf='15m'):
|
206
226
|
"""_summary_
|
207
227
|
获取历史K线数据
|
@@ -327,7 +347,7 @@ class StrategyMaker():
|
|
327
347
|
|
328
348
|
return self.smcOB.find_OBs(struct=struct, side=side, start_index=start_index, is_valid=is_valid)
|
329
349
|
|
330
|
-
def
|
350
|
+
def get_latest_OB(self, symbol, data, trend, start_index=-1) -> dict:
|
331
351
|
"""_summary_
|
332
352
|
获取最新的Order Block
|
333
353
|
Args:
|
@@ -339,7 +359,7 @@ class StrategyMaker():
|
|
339
359
|
_type_: _description_
|
340
360
|
"""
|
341
361
|
|
342
|
-
return self.smcOB.
|
362
|
+
return self.smcOB.get_latest_OB(data=data, trend=trend, start_index=start_index)
|
343
363
|
|
344
364
|
|
345
365
|
def find_FVGs(self, symbol, data, side, check_balanced=True, start_index=-1, pair_config=None) -> pd.DataFrame:
|
@@ -359,6 +379,35 @@ class StrategyMaker():
|
|
359
379
|
|
360
380
|
return self.smcFVG.find_FVGs(data, side, check_balanced, start_index)
|
361
381
|
|
382
|
+
def find_EQH_EQL(self, symbol, data, trend, end_idx=-1, atr_offset=0.1, pair_config=None) -> dict:
|
383
|
+
"""_summary_
|
384
|
+
寻找等值高点和等值低点
|
385
|
+
Args:
|
386
|
+
symbol (_type_): _description_
|
387
|
+
data (_type_): _description_
|
388
|
+
trend (_type_): _description_
|
389
|
+
end_idx (int, optional): _description_. Defaults to -1.
|
390
|
+
atr_offset (float, optional): _description_. Defaults to 0.1.
|
391
|
+
Returns:
|
392
|
+
_type_: _description_
|
393
|
+
"""
|
394
|
+
return self.smcLiqu.find_EQH_EQL(data, trend, end_idx=end_idx, atr_offset=atr_offset)
|
395
|
+
|
396
|
+
def identify_dynamic_trendlines(self, symbol, data, trend, start_idx=-1, end_idx=-1, ratio=0.8) -> bool:
|
397
|
+
"""_summary_
|
398
|
+
识别动态趋势线
|
399
|
+
Args:
|
400
|
+
symbol (_type_): _description_
|
401
|
+
data (_type_): _description_
|
402
|
+
trend (_type_): _description_
|
403
|
+
start_idx (int, optional): _description_. Defaults to -1.
|
404
|
+
end_idx (int, optional): _description_. Defaults to -1.
|
405
|
+
ratio (float, optional): _description_. Defaults to 0.5.
|
406
|
+
Returns:
|
407
|
+
_type_: _description_
|
408
|
+
"""
|
409
|
+
return self.smcLiqu.identify_dynamic_trendlines(data, trend, start_idx, end_idx, ratio)
|
410
|
+
|
362
411
|
def build_struct(self, symbol, data) -> pd.DataFrame:
|
363
412
|
|
364
413
|
"""_summary_
|
@@ -373,7 +422,7 @@ class StrategyMaker():
|
|
373
422
|
|
374
423
|
return self.smcStruct.build_struct(data)
|
375
424
|
|
376
|
-
def
|
425
|
+
def get_latest_struct(self, symbol, data) -> dict:
|
377
426
|
"""_summary_
|
378
427
|
获取最后一个SMC结构
|
379
428
|
Args:
|
@@ -382,7 +431,7 @@ class StrategyMaker():
|
|
382
431
|
Returns:
|
383
432
|
_type_: _description_
|
384
433
|
"""
|
385
|
-
return self.smcStruct.
|
434
|
+
return self.smcStruct.get_latest_struct(data)
|
386
435
|
|
387
436
|
def reset_all_cache(self, symbol):
|
388
437
|
"""_summary_
|
@@ -392,7 +441,7 @@ class StrategyMaker():
|
|
392
441
|
self.place_order_prices.pop(symbol)
|
393
442
|
self.clear_cache_historical_klines_df(symbol)
|
394
443
|
|
395
|
-
def
|
444
|
+
def fetch_position(self, symbol) -> bool:
|
396
445
|
"""
|
397
446
|
检查指定交易对是否有持仓,失败时最多重试3次
|
398
447
|
|
@@ -409,7 +458,7 @@ class StrategyMaker():
|
|
409
458
|
error_message = f"{symbol} 检查持仓失败: {e}"
|
410
459
|
self.logger.error(error_message)
|
411
460
|
self.send_feishu_notification(symbol,error_message)
|
412
|
-
return
|
461
|
+
return True
|
413
462
|
|
414
463
|
@abstractmethod
|
415
464
|
def process_pair(self, symbol, pair_config):
|
@@ -1,8 +1,8 @@
|
|
1
1
|
maker/BestFVGStrategyMaker.py,sha256=a9UfClrfzkgX6jXL2FODzANtawrmGeZ_PVeO1-tweDc,12532
|
2
|
-
maker/BestTopDownStrategyMaker.py,sha256=
|
2
|
+
maker/BestTopDownStrategyMaker.py,sha256=HGG4883WTDBc3PPHNtWAAR4cjtg6r7a7QNv-JLBVOU0,23981
|
3
3
|
maker/MACDStrategyMaker.py,sha256=WX8wqpF9h5W4WclN1NjZ_Bur7KFi_aMTvacfLyHzEcI,12681
|
4
4
|
maker/SMCStrategyMaker.py,sha256=hkDqymWnuyYDo1gTYY_uyO4H4yOwCw8NBTM9RcfLRPc,28780
|
5
|
-
maker/StrategyMaker.py,sha256=
|
5
|
+
maker/StrategyMaker.py,sha256=iJa-9MxuUwPDOZot2YJmT-sdYHnPZbgPsdCaKvw3Sis,18821
|
6
6
|
maker/ThreeLineStrategyMaker.py,sha256=K4NZB1rH8IZMVrCFEnzwXctZQbyI9ZdyTMrYObpl-vM,32095
|
7
7
|
maker/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
8
8
|
maker/history_code/WickReversalStrategyMaker.py,sha256=7DqPDVJot4EM0_lSAcFAHrR9rNvkIds9KLMoDOiAHEc,17486
|
@@ -11,7 +11,7 @@ maker/history_code/okxapi.py,sha256=_9G0U_o0ZC8NxaT6PqpiLgxBm9gPobC9PsFHZE1c5w0,
|
|
11
11
|
maker/history_code/zhen.py.bak,sha256=HNkrQbJts8G9umE9chEFsc0cLQApcM9KOVNMYPpkBXM,10918
|
12
12
|
maker/history_code/zhen_2.py,sha256=4IaHVtTCMSlrLGSTZrWpW2q-f7HZsUNRkW_-5QgWv24,10509
|
13
13
|
maker/main.py,sha256=PRCP2qCUiUFPQyi1YbvnmW9KqeCZcc0zGjy9OBvMWbM,3723
|
14
|
-
openfund_maker-2.3.
|
15
|
-
openfund_maker-2.3.
|
16
|
-
openfund_maker-2.3.
|
17
|
-
openfund_maker-2.3.
|
14
|
+
openfund_maker-2.3.3.dist-info/METADATA,sha256=0ZvMTnyUA47auoEj2OmQ4gDGOWDk1PbQEQJ2Wp17XNs,2001
|
15
|
+
openfund_maker-2.3.3.dist-info/WHEEL,sha256=IYZQI976HJqqOpQU6PHkJ8fb3tMNBFjg-Cn-pwAbaFM,88
|
16
|
+
openfund_maker-2.3.3.dist-info/entry_points.txt,sha256=gKMytICEKcMRFQDFkHZLnIpID7UQFoTIM_xcpiiV6Ns,50
|
17
|
+
openfund_maker-2.3.3.dist-info/RECORD,,
|
File without changes
|
File without changes
|