openfund-maker 2.2.8__py3-none-any.whl → 2.3.1__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.
@@ -38,9 +38,12 @@ class BestFVGStrategyMaker(SMCStrategyMaker):
38
38
  '''
39
39
  FixMe: bug2.2.5_1 20230502
40
40
  检查价格是否在FVG范围内,bar_index,跳过FVG相关的三根K线,从FVG下一根的K线开始检查
41
+ 并且满足bar_index要从struct_index之后,struct_index和FVG_index 不一定哪个大,取最晚的index。
41
42
  '''
43
+ start_index = max(struct_index,fvg_index+2) #
44
+
42
45
  if fvg_index is not None:
43
- data = data.iloc[min(len(data),fvg_index+2):]
46
+ data = data.iloc[min(len(data),start_index):].copy()
44
47
 
45
48
 
46
49
  # 检查价格是否在FVG范围内,bar_index 从struct_index
@@ -0,0 +1,324 @@
1
+ # -*- coding: utf-8 -*-
2
+ import traceback
3
+ from typing import override
4
+
5
+ from maker.StrategyMaker import StrategyMaker
6
+
7
+ class BestTopDownStrategyMaker(StrategyMaker):
8
+ def __init__(self, config, platform_config, common_config, logger=None, exchangeKey='okx'):
9
+ super().__init__(config, platform_config, common_config, logger, exchangeKey)
10
+ self.htf_last_struct = {} # 缓存HTF的最后一个结构
11
+ self.logger = logger
12
+
13
+ @override
14
+ def reset_all_cache(self, symbol):
15
+ """
16
+ 重置所有缓存
17
+ """
18
+ super().reset_all_cache(symbol)
19
+ self.htf_last_struct.pop(symbol, None)
20
+ self.clear_cache_historical_klines_df(symbol)
21
+
22
+ @override
23
+ def process_pair(self,symbol,pair_config):
24
+ self.logger.info("-" * 60)
25
+ """_summary_
26
+ HTF (Daily & 4H)
27
+ 1.1. Price's Current Trend 市场趋势
28
+ 1.2. Who's In Control 供需控制
29
+ 1.3. Key Support & Resistance Levels 关键位置
30
+ ATF (1H & 30 Min & 15 Min)
31
+ 2.1. Market Condition
32
+ 2.2. PD Arrays
33
+ 2.3. Liquidity Areas
34
+ ETF (5 Min & 1 Min)
35
+ 1. Reversal Signs
36
+ 2. PD Arrays
37
+ 3. Place Order
38
+
39
+ """
40
+ try:
41
+ # 检查是否有持仓,有持仓不进行下单
42
+ if self.check_position(symbol=symbol) :
43
+ self.reset_all_cache(symbol)
44
+ self.logger.info(f"{symbol} : 有持仓合约,不进行下单。")
45
+ return
46
+ precision = self.get_precision_length(symbol)
47
+
48
+ top_down_strategy = pair_config.get('top_down_strategy',{})
49
+
50
+ """
51
+ 获取策略配置
52
+ """
53
+ htf = str(top_down_strategy.get('HTF','4h'))
54
+ atf = str(top_down_strategy.get('ATF','15m'))
55
+ etf = str(top_down_strategy.get('ETF', '1m'))
56
+
57
+ self.logger.info(f"{symbol} : TopDownSMC策略 {htf}|{atf}|{etf} \n")
58
+ market_price = self.get_market_price(symbol=symbol)
59
+
60
+ """
61
+ step 1 : Higher Time Frame Analysis
62
+ """
63
+ step = "1"
64
+ # 初始化HTF趋势相关变量
65
+ htf_side, htf_struct, htf_trend = None, None, None
66
+ # HTF 缓存,减小流量损耗
67
+ htf_df = self.get_historical_klines_df_by_cache(symbol=symbol, tf=htf)
68
+ htf_struct =self.build_struct(symbol=symbol, data=htf_df)
69
+
70
+ htf_latest_struct = self.get_last_struct(symbol=symbol, data=htf_struct)
71
+
72
+ htf_trend = self.BULLISH_TREND if htf_latest_struct[self.STRUCT_DIRECTION_COL] == 1 else self.BEARISH_TREND
73
+ htf_side = self.BUY_SIDE if htf_trend == self.BULLISH_TREND else self.SELL_SIDE
74
+ # 1.1. Price's Current Trend 市场趋势(HTF)
75
+ step = "1.1"
76
+ self.logger.info(f"{symbol} : {step}. HTF {htf} Price's Current Trend is {htf_trend}。")
77
+ # 1.2. Who's In Control 供需控制,Bullish 或者 Bearish | Choch 或者 BOS
78
+ step = "1.2"
79
+ self.logger.info(f"{symbol} : {step}. HTF {htf} struct is {htf_latest_struct[self.STRUCT_COL]}。")
80
+
81
+ # 1.3. HTF Key Support & Resistance Levels 支撑或阻力关键位置(HTF 看上下的供需区位置)
82
+ step = "1.3"
83
+ htf_OBs_df = self.find_OBs(symbol=symbol,struct=htf_struct)
84
+
85
+ if htf_OBs_df is None or len(htf_OBs_df) == 0:
86
+ self.logger.debug(f"{symbol} : {step}. HTF {htf} 未找到OB。")
87
+ return
88
+ else:
89
+ self.logger.debug(f"{symbol} : {step}. HTF {htf} 找到OB。")
90
+
91
+ htf_support_OB = self.get_lastest_OB(symbol=symbol,data=htf_OBs_df,trend=self.BULLISH_TREND)
92
+ if htf_support_OB :
93
+ htf_support_price = htf_support_OB.get(self.OB_MID_COL)
94
+ else:
95
+ htf_support_price = htf_struct.at[htf_struct.index[-1], self.STRUCT_LOW_COL]
96
+
97
+ htf_resistance_OB = self.get_lastest_OB(symbol=symbol,data=htf_OBs_df,trend=self.BEARISH_TREND)
98
+ if htf_resistance_OB :
99
+ htf_resistance_price = htf_resistance_OB.get(self.OB_MID_COL)
100
+ else:
101
+ htf_resistance_price = htf_struct.at[htf_struct.index[-1], self.STRUCT_HIGH_COL]
102
+ self.logger.info(f"{symbol} : {step}. HTF {htf}, Key Support={htf_support_price:.{precision}f} & Key Resistance={htf_resistance_price:.{precision}f} ")
103
+ #1.4. 检查关键支撑位和阻力位之间是否有利润空间。
104
+ step = "1.4"
105
+ # 计算支撑位和阻力位之间的利润空间百分比
106
+ htf_profit_percent = abs((htf_resistance_price - htf_support_price) / htf_support_price * 100)
107
+ min_profit_percent = pair_config.get('min_profit_percent', 4) # 默认最小利润空间为0.5%
108
+
109
+ if htf_profit_percent < min_profit_percent:
110
+ 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}%,等待...")
111
+ return
112
+ else:
113
+ 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
+
115
+ # 1.5. 检查当前价格是否在关键支撑位和阻力位,支撑位可以做多,阻力位可以做空。
116
+ step = "1.5"
117
+ htf_ce_price = (htf_support_price + htf_resistance_price) / 2
118
+ # 折价区
119
+ target_trend = self.BULLISH_TREND if (market_price >= htf_support_price and market_price < htf_ce_price) else self.BEARISH_TREND
120
+ # FIXME target_trend 更新 htf_trend
121
+ htf_trend = target_trend
122
+ if target_trend != htf_trend:
123
+ self.logger.info(f"{symbol} : {step}. HTF {htf} 目标趋势 {target_trend} 与当前趋势 {htf_trend} 不符,折价区 ce={htf_ce_price:.{precision}f} ,等待...")
124
+ return
125
+ else:
126
+ self.logger.info(f"{symbol} : {step}. HTF {htf} 目标趋势 {target_trend} 与当前趋势 {htf_trend} 一致, ce={htf_ce_price:.{precision}f}")
127
+
128
+ """
129
+ step 2 : Analysis Time Frames
130
+ """
131
+ # 2. ATF Step
132
+ # 2.1 Market Condition 市场状况(ATF 看上下的供需区位置)
133
+
134
+ atf_side, atf_struct, atf_trend = None, None, None
135
+ atf_df = self.get_historical_klines_df(symbol=symbol, tf=atf)
136
+ atf_struct =self.build_struct(symbol=symbol, data=atf_df)
137
+ atf_latest_struct = self.get_last_struct(symbol=symbol, data=atf_struct)
138
+ atf_trend = atf_latest_struct[self.STRUCT_DIRECTION_COL]
139
+ atf_side = self.BUY_SIDE if atf_trend == self.BULLISH_TREND else self.SELL_SIDE
140
+ # 2.1. Price's Current Trend 市场趋势(HTF )
141
+ step = "2.1"
142
+ self.logger.info(f"{symbol} : {step}. ATF {atf} Price's Current Trend is {atf_trend}。")
143
+ # 2.2. Who's In Control 供需控制,Bullish 或者 Bearish | Choch 或者 BOS
144
+ step = "2.2"
145
+ self.logger.info(f"{symbol} : {step}. ATF {atf} struct is {atf_latest_struct[self.STRUCT_COL]}。")
146
+ # TODO 2.3. 检查关键支撑位和阻力位之间是否有利润空间。
147
+
148
+ # 2.4. ATF 方向要和 HTF方向一致
149
+ step = "2.4"
150
+
151
+ if htf_trend != atf_trend:
152
+ self.logger.info(f"{symbol} : {step}. ATF {atf} is {atf_trend} 与 HTF {htf} is {htf_trend} 不一致,等待...")
153
+ return
154
+ else:
155
+ self.logger.info(f"{symbol} : {step}. ATF {atf} is {atf_trend} 与 HTF {htf} is {htf_trend} 一致。")
156
+ #TODO 2.5. check Liquidity Areas ,检查当前结构是否是流动性摄取。
157
+
158
+
159
+
160
+ # 2.6. 在HTF供需区范围,找ATF的PDArray,FVG和OB,供需区,计算监测下单区域范围。
161
+ setp = "2.6"
162
+ atf_pdArrays_df = self.find_PDArrays(symbol=symbol,struct=atf_struct,side=atf_side)
163
+
164
+ # 不同的结构,不同位置,如果是Choch则等待价格进入PDArray,如果是BOS则等待价格进入折价区
165
+ # 划分 折价(discount)区和溢价(premium)区
166
+ atf_struct_high = atf_latest_struct[self.STRUCT_HIGH_COL]
167
+ atf_struct_low = atf_latest_struct[self.STRUCT_LOW_COL]
168
+ atf_struct_mid = atf_latest_struct[self.STRUCT_MID_COL]
169
+
170
+ if "CHOCH" in atf_struct[self.STRUCT_COL]:
171
+ # 找PDArray,Bullish 则PDArray的mid要小于 atf_struct_mid,Bearish 则PDArray的mid要大于 atf_struct_mid
172
+ # 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 atf_trend == self.BEARISH_TREND else atf_pdArrays_df[self.PD_MID_COL] <= atf_struct_mid
174
+ atf_pdArrays_df = atf_pdArrays_df[mask]
175
+ if len(atf_pdArrays_df) == 0:
176
+ self.logger.info(f"{symbol} : {setp}.1. ATF {atf} 未找到PDArray,不下单")
177
+ return
178
+ else:
179
+ # 找到最新的PDArray
180
+ atf_vaild_pdArray = atf_pdArrays_df.iloc[-1]
181
+ self.logger.info(f"{symbol} : {setp}.1. ATF {atf} 找到PDArray\n"
182
+ f"{atf_vaild_pdArray[[self.TIMESTAMP_COL,self.PD_TYPE_COL,self.PD_HIGH_COL,self.PD_LOW_COL,self.PD_MID_COL]]}。")
183
+
184
+
185
+ #SMS
186
+ elif "SMS" in atf_struct[self.STRUCT_COL]:
187
+ mask = atf_pdArrays_df[self.PD_MID_COL] >= atf_struct_mid if atf_trend == self.BEARISH_TREND else atf_pdArrays_df[self.PD_MID_COL] <= atf_struct_mid
188
+ atf_pdArrays_df = atf_pdArrays_df[mask]
189
+ if len(atf_pdArrays_df) == 0:
190
+ self.logger.info(f"{symbol} : {setp}.1. ATF {atf} 在{atf_struct_mid:.{precision}f}未找到PDArray,不下单")
191
+ return
192
+ else:
193
+ # 找到最新的PDArray
194
+ atf_vaild_pdArray = atf_pdArrays_df.iloc[-1]
195
+ self.logger.info(f"{symbol} : {setp}.1. ATF {atf} 找到PDArray\n"
196
+ f"{atf_vaild_pdArray[[self.TIMESTAMP_COL,self.PD_TYPE_COL,self.PD_HIGH_COL,self.PD_LOW_COL,self.PD_MID_COL]]}。")
197
+
198
+
199
+ #BMS
200
+ else:
201
+ atf_premium_mid = (atf_struct_mid + atf_struct_low) / 2 if atf_trend == self.BEARISH_TREND else (atf_struct_mid + atf_struct_high) / 2
202
+ mask = atf_pdArrays_df[self.PD_HIGH_COL] >= atf_premium_mid if atf_trend == self.BEARISH_TREND else atf_pdArrays_df[self.PD_LOW_COL] <= atf_premium_mid
203
+ atf_pdArrays_df = atf_pdArrays_df[mask]
204
+ if len(atf_pdArrays_df) == 0:
205
+ self.logger.info(f"{symbol} : {setp}.1. ATF {atf} ,在{atf_premium_mid:.{precision}f}未找到PDArray,不下单")
206
+ return
207
+ else:
208
+ # 找到最新的PDArray
209
+ atf_vaild_pdArray = atf_pdArrays_df.iloc[-1]
210
+ self.logger.info(f"{symbol} : {setp}.1. ATF {atf} 找到PDArray\n"
211
+ f"{atf_vaild_pdArray[[self.TIMESTAMP_COL,self.PD_TYPE_COL,self.PD_HIGH_COL,self.PD_LOW_COL,self.PD_MID_COL]]}")
212
+
213
+
214
+
215
+ setp = "2.7"
216
+
217
+ # 2.7. 等待价格进入 PDArray
218
+
219
+ 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} : {setp}. ATF {atf} market_price={market_price:.{precision}f} 未达到PDArray范围。"
221
+ f"PD_HIGH={atf_vaild_pdArray[self.PD_LOW_COL]:.{precision}f} "
222
+ f"PD_LOW={atf_vaild_pdArray[self.PD_HIGH_COL]:.{precision}f} ")
223
+
224
+ return
225
+ else:
226
+ self.logger.info(f"{symbol} : {setp}. ATF {atf} market_price={market_price:.{precision}f} 已到达PDArray范围。"
227
+ f"PD_HIGH={atf_vaild_pdArray[self.PD_LOW_COL]:.{precision}f} "
228
+ f"PD_LOW={atf_vaild_pdArray[self.PD_HIGH_COL]:.{precision}f} ")
229
+
230
+
231
+ # 3. ETF Step
232
+
233
+ etf_side, etf_struct, etf_trend = None, None, None
234
+ etf_df = self.get_historical_klines_df(symbol=symbol, tf=etf)
235
+ etf_struct =self.build_struct(symbol=symbol, data=etf_df)
236
+ etf_latest_struct = self.get_last_struct(symbol=symbol, data=etf_struct)
237
+
238
+ # 初始化HTF趋势相关变量
239
+
240
+ etf_trend = etf_latest_struct[self.STRUCT_DIRECTION_COL]
241
+ #FIXME etf_trend
242
+ etf_trend = atf_trend
243
+
244
+ etf_side = self.BUY_SIDE if etf_trend == self.BULLISH_TREND else self.SELL_SIDE
245
+
246
+ # 3.1. Price's Current Trend 市场趋势(ETF )
247
+ setp = "3.1"
248
+ self.logger.info(f"{symbol} : {setp}. ETF {etf} Price's Current Trend is {etf_trend}。")
249
+ # 3.2. Who's In Control 供需控制,Bullish 或者 Bearish | Choch 或者 BOS
250
+ setp = "3.2"
251
+ self.logger.info(f"{symbol} : {setp}. ETF {etf} struct is {etf_latest_struct[self.STRUCT_COL]}。")
252
+
253
+
254
+ # 3.3 Reversal Signs 反转信号
255
+ setp = "3.3"
256
+
257
+ if atf_trend != etf_trend:
258
+
259
+ self.logger.info(f"{symbol} : {setp}. ETF {etf} 市场结构{etf_latest_struct[self.STRUCT_COL]}未反转,等待...")
260
+ return
261
+ else:
262
+ self.logger.info(f"{symbol} : {setp}. ETF {etf} 市场结构{etf_latest_struct[self.STRUCT_COL]}已反转。")
263
+
264
+ # TODO "CHOCH"|"BOS" 的PDArray 入场位置不一样
265
+
266
+ # 3.4 找 PD Arrays 价格区间(ETF 看上下的供需区位置)
267
+ setp = "3.4"
268
+ etf_pdArrays_df = self.find_PDArrays(symbol=symbol,struct=etf_struct,side=etf_side)
269
+ # 划分 折价(discount)区和溢价(premium)区
270
+ etf_struct_high = etf_latest_struct[self.STRUCT_HIGH_COL]
271
+ etf_struct_low = etf_latest_struct[self.STRUCT_LOW_COL]
272
+ etf_struct_mid = etf_latest_struct[self.STRUCT_MID_COL]
273
+ mask = etf_pdArrays_df[self.PD_MID_COL] >= etf_struct_mid if etf_trend == self.BEARISH_TREND else etf_pdArrays_df[self.PD_MID_COL] <= etf_struct_mid
274
+ etf_pdArrays_df = etf_pdArrays_df[mask]
275
+ if len(etf_pdArrays_df) == 0:
276
+ self.logger.info(f"{symbol} : {setp}.1. ETF {etf} 未找到PDArray,不下单")
277
+ return
278
+ else:
279
+ # 找到最新的PDArray
280
+ etf_vaild_pdArray = etf_pdArrays_df.iloc[-1]
281
+ self.logger.info(f"{symbol} : {setp}.1. ETF {etf} 找到PDArray.\n"
282
+ f"{etf_vaild_pdArray[[self.TIMESTAMP_COL,self.PD_TYPE_COL,self.PD_HIGH_COL,self.PD_LOW_COL,self.PD_MID_COL]]}。")
283
+
284
+
285
+ 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} : {setp}.2. ETF {etf} market_price={market_price:.{precision}f} 未达到PDArray范围。"
287
+ f"PD_HIGH={etf_vaild_pdArray[self.PD_HIGH_COL]:.{precision}f} "
288
+ f"PD_LOW={etf_vaild_pdArray[self.PD_LOW_COL]:.{precision}f}")
289
+
290
+ return
291
+ else:
292
+ self.logger.info(f"{symbol} : {setp}.2. ETF {etf} market_price={market_price:.{precision}f} 已到达PDArray范围。"
293
+ f"PD_HIGH={etf_vaild_pdArray[self.PD_HIGH_COL]:.{precision}f} "
294
+ f"PD_LOW={etf_vaild_pdArray[self.PD_LOW_COL]:.{precision}f}")
295
+
296
+ # 3.5 Place Order 下单
297
+ setp = "3.5"
298
+ # 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
+ order_price = self.toDecimal(etf_vaild_pdArray[self.PD_MID_COL])
300
+
301
+ latest_order_price = self.toDecimal(self.place_order_prices.get(symbol,0))
302
+ if order_price == latest_order_price:
303
+ self.logger.info(f"{symbol} : {setp}. ETF {etf}, 下单价格 {order_price:.{precision}} 未变化,不进行下单。")
304
+ return
305
+
306
+ self.cancel_all_orders(symbol=symbol)
307
+ self.place_order(symbol=symbol, price=order_price, side=etf_side, pair_config=pair_config)
308
+ self.place_order_prices[symbol] = order_price # 记录下单价格,过滤重复下单
309
+ self.logger.info(f"{symbol} : {setp}. ETF {etf}, {etf_side} 价格={order_price:.{precision}}")
310
+
311
+
312
+ except KeyboardInterrupt:
313
+ self.logger.info("程序收到中断信号,开始退出...")
314
+ except Exception as e:
315
+ error_message = f"程序异常退出: {str(e)}"
316
+ self.logger.error(error_message,exc_info=True)
317
+ traceback.print_exc()
318
+ self.send_feishu_notification(symbol, error_message)
319
+ finally:
320
+ self.logger.info("=" * 60 + "\n")
321
+
322
+
323
+
324
+
maker/StrategyMaker.py ADDED
@@ -0,0 +1,438 @@
1
+ # -*- coding: utf-8 -*-
2
+ from functools import lru_cache
3
+ import traceback
4
+ import pandas as pd
5
+ from datetime import datetime, timedelta
6
+ from decimal import Decimal
7
+ from abc import abstractmethod
8
+ from concurrent.futures import ThreadPoolExecutor, as_completed
9
+ from core.utils.OPTools import OPTools
10
+ from core.Exchange import Exchange
11
+ # 导入SMC相关模块
12
+ from core.smc import (
13
+ SMCBase,
14
+ SMCPDArray,
15
+ SMCStruct,
16
+ SMCOrderBlock,
17
+ SMCFVG
18
+ )
19
+
20
+ class StrategyMaker():
21
+ BUY_SIDE = 'buy'
22
+ SELL_SIDE = 'sell'
23
+ BULLISH_TREND = 'Bullish'
24
+ BEARISH_TREND = 'Bearish'
25
+
26
+ HIGH_COL = SMCBase.SMCBase.HIGH_COL
27
+ LOW_COL = SMCBase.SMCBase.LOW_COL
28
+ CLOSE_COL = SMCBase.SMCBase.CLOSE_COL
29
+ OPEN_COL = SMCBase.SMCBase.OPEN_COL
30
+ TIMESTAMP_COL = SMCBase.SMCBase.TIMESTAMP_COL
31
+ VOLUME_COL = SMCBase.SMCBase.VOLUME_COL
32
+
33
+ STRUCT_COL = SMCStruct.SMCStruct.STRUCT_COL
34
+ STRUCT_HIGH_COL = SMCStruct.SMCStruct.STRUCT_HIGH_COL
35
+ STRUCT_LOW_COL = SMCStruct.SMCStruct.STRUCT_LOW_COL
36
+ STRUCT_MID_COL = SMCStruct.SMCStruct.STRUCT_MID_COL
37
+ STRUCT_HIGH_INDEX_COL = SMCStruct.SMCStruct.STRUCT_HIGH_INDEX_COL
38
+ STRUCT_LOW_INDEX_COL = SMCStruct.SMCStruct.STRUCT_LOW_INDEX_COL
39
+ STRUCT_DIRECTION_COL = SMCStruct.SMCStruct.STRUCT_DIRECTION_COL
40
+ HIGH_START_COL = SMCStruct.SMCStruct.HIGH_START_COL
41
+ LOW_START_COL = SMCStruct.SMCStruct.LOW_START_COL
42
+
43
+ OB_HIGH_COL = SMCOrderBlock.SMCOrderBlock.OB_HIGH_COL
44
+ OB_LOW_COL = SMCOrderBlock.SMCOrderBlock.OB_LOW_COL
45
+ OB_MID_COL = SMCOrderBlock.SMCOrderBlock.OB_MID_COL
46
+ OB_VOLUME_COL = SMCOrderBlock.SMCOrderBlock.OB_VOLUME_COL
47
+ OB_DIRECTION_COL = SMCOrderBlock.SMCOrderBlock.OB_DIRECTION_COL
48
+ OB_ATR = SMCOrderBlock.SMCOrderBlock.OB_ATR
49
+ OB_IS_COMBINED = SMCOrderBlock.SMCOrderBlock.OB_IS_COMBINED
50
+ OB_WAS_CROSSED = SMCOrderBlock.SMCOrderBlock.OB_WAS_CROSSED
51
+
52
+ PD_HIGH_COL = SMCPDArray.SMCPDArray.PD_HIGH_COL
53
+ PD_LOW_COL = SMCPDArray.SMCPDArray.PD_LOW_COL
54
+ PD_MID_COL = SMCPDArray.SMCPDArray.PD_MID_COL
55
+ PD_TYPE_COL = SMCPDArray.SMCPDArray.PD_TYPE_COL
56
+
57
+ def __init__(self, config, platform_config, common_config, feishu_webhook=None, logger=None ,exchangeKey='okx'):
58
+ """_summary_
59
+ 初始化
60
+ Args:
61
+ config (_type_): _description_
62
+ platform_config (_type_): _description_
63
+ common_config (_type_): _description_
64
+ feishu_webhook (_type_, optional): _description_. Defaults to None.
65
+ logger (_type_, optional): _description_. Defaults to None.
66
+ """
67
+ self.logger = logger
68
+ self.g_config = config
69
+
70
+ self.common_config = common_config
71
+ self.feishu_webhook = self.common_config.get('feishu_webhook',"")
72
+
73
+ self.strategy_config = self.g_config.get('strategy', {})
74
+ self.trading_pairs_config = self.g_config.get('tradingPairs', {})
75
+
76
+ self.leverage_value = self.strategy_config.get('leverage', 20)
77
+ self.is_demo_trading = self.common_config.get('is_demo_trading', 1) # live trading: 0, demo trading: 1
78
+ proxies = {
79
+ "http": self.common_config.get('proxy', "http://localhost:7890"),
80
+ "https": self.common_config.get('proxy', "http://localhost:7890")
81
+ }
82
+ try:
83
+ self.exchange = Exchange({
84
+ 'apiKey': platform_config["apiKey"],
85
+ 'secret': platform_config["secret"],
86
+ 'password': platform_config["password"],
87
+ 'timeout': 3000,
88
+ 'rateLimit': 50,
89
+ 'options': {'defaultType': 'future'},
90
+ 'proxies': proxies
91
+ }, exchangeKey)
92
+ except Exception as e:
93
+ self.logger.error(f"连接交易所失败: {e}")
94
+ raise Exception(f"连接交易所失败: {e}")
95
+
96
+ self.smcPDArray = SMCPDArray.SMCPDArray()
97
+ self.smcStruct = SMCStruct.SMCStruct()
98
+ self.smcOB = SMCOrderBlock.SMCOrderBlock()
99
+ self.smcFVG = SMCFVG.SMCFVG()
100
+
101
+ self.interval_map = {
102
+ '1d': 24 * 60 * 60 , # 1天
103
+ '4h': 4 * 60 * 60 , # 4小时
104
+ '1h': 60 * 60 , # 1小时
105
+ '30m': 30 * 60 , # 30分钟
106
+ '15m': 15 * 60 , # 15分钟
107
+ '5m': 5 * 60 , # 5分钟
108
+ }
109
+
110
+ self.place_order_prices = {} # 记录每个symbol的挂单价格
111
+ self.cache_time = {} # 记录缓存时间的字典
112
+
113
+
114
+ def toDecimal(self, price):
115
+ """_summary_
116
+ 将价格转换为Decimal类型
117
+ Args:
118
+ price (_type_): _description_
119
+ Returns:
120
+ _type_: _description_
121
+ """
122
+ return OPTools.toDecimal(price)
123
+
124
+ def get_pair_config(self,symbol):
125
+ # 获取交易对特定配置,如果没有则使用全局策略配置
126
+ pair_config = self.trading_pairs_config.get(symbol, {})
127
+
128
+ # 使用字典推导式合并配置,trading_pairs_config优先级高于strategy_config
129
+ pair_config = {
130
+ **self.strategy_config, # 基础配置
131
+ **pair_config # 交易对特定配置会覆盖基础配置
132
+ }
133
+ return pair_config
134
+
135
+
136
+ def send_feishu_notification(self, symbol, message):
137
+ if self.feishu_webhook:
138
+ try:
139
+ OPTools.send_feishu_notification(self.feishu_webhook,message)
140
+ except Exception as e:
141
+ self.logger.warning(f"{symbol} 发送飞书消息失败: {e}")
142
+
143
+ def get_precision_length(self, symbol):
144
+ """_summary_
145
+ 获取价格的精度长度
146
+ Args:
147
+ price (_type_): _description_
148
+ Returns:
149
+ _type_: _description_
150
+ """
151
+ tick_size = self.exchange.get_tick_size(symbol)
152
+ return self.smcStruct.get_precision_length(tick_size)
153
+
154
+ def get_market_price(self, symbol):
155
+ """_summary_
156
+ 获取最新成交价
157
+ Args:
158
+ symbol (_type_): _description_
159
+ Returns:
160
+ _type_: _description_
161
+ """
162
+ return self.exchange.get_market_price(symbol)
163
+
164
+ def place_order(self, symbol, price:Decimal, side, pair_config, leverage:int=0, order_type='limit'):
165
+ """_summary_
166
+ 下单
167
+ Args:
168
+ symbol (_type_): _description_
169
+ price (_type_): _description_
170
+ amount_usdt (_type_): _description_
171
+ side (_type_): _description_
172
+ order_type (_type_): _description_
173
+ """
174
+ # 获取做多和做空的下单金额配置
175
+ long_amount_usdt = pair_config.get('long_amount_usdt', 5)
176
+ short_amount_usdt = pair_config.get('short_amount_usdt', 5)
177
+
178
+ # 设置杠杆倍数
179
+ leverage = leverage or self.leverage_value
180
+
181
+ # 根据交易方向设置下单金额
182
+ order_amount_usdt = short_amount_usdt if side == self.SELL_SIDE else long_amount_usdt
183
+
184
+ # 记录下单日志
185
+ direction = self.BULLISH_TREND if side == self.BUY_SIDE else self.BEARISH_TREND
186
+ self.logger.debug(f"{symbol} : 触发{direction}下单条件。")
187
+
188
+ # 执行下单
189
+ self.exchange.place_order(
190
+ symbol=symbol,
191
+ price=price,
192
+ amount_usdt=order_amount_usdt,
193
+ side=side,
194
+ leverage=leverage,
195
+ order_type=order_type
196
+ )
197
+
198
+ def cancel_all_orders(self, symbol):
199
+ """_summary_
200
+ 取消所有挂单
201
+ Args:
202
+ symbol (_type_): _description_
203
+ """
204
+ self.exchange.cancel_all_orders(symbol=symbol)
205
+ def get_historical_klines(self, symbol, tf='15m'):
206
+ """_summary_
207
+ 获取历史K线数据
208
+ Args:
209
+ symbol (_type_): _description_
210
+ bar (_type_, optional): _description_. Defaults to '15m'.
211
+ Returns:
212
+ _type_: _description_
213
+ """
214
+ return self.exchange.get_historical_klines(symbol=symbol, bar=tf)
215
+
216
+ @lru_cache(maxsize=32) # 缓存最近32个不同的请求
217
+ def _get_cache_historical_klines_df(self, symbol, tf):
218
+ """被缓存的获取K线数据的方法"""
219
+ return self.get_historical_klines_df(symbol, tf)
220
+ def clear_cache_historical_klines_df(self, symbol=None):
221
+ """
222
+ 清除指定交易对和时间周期的缓存
223
+
224
+ 参数:
225
+ symbol (str, optional): 交易对符号,如为None则清除所有缓存
226
+ tf (str, optional): 时间周期,如为None则清除所有缓存
227
+ """
228
+ if symbol is None:
229
+ # 清除所有缓存
230
+ self._get_cache_historical_klines_df.cache_clear()
231
+ self.cache_time.clear()
232
+ # print("已清除所有K线数据缓存")
233
+ else:
234
+ # 删除所有包含cache_key的缓存
235
+ keys_to_delete = [k for k in self.cache_time.keys() if symbol in k]
236
+ if keys_to_delete:
237
+ for k in keys_to_delete:
238
+ del self.cache_time[k]
239
+ # 由于lru_cache无法单独清除特定键,这里只能清除所有缓存
240
+ self._get_cache_historical_klines_df.cache_clear()
241
+
242
+
243
+ def get_historical_klines_df_by_cache(self, symbol, tf='15m'):
244
+ """_summary_
245
+ 获取历史K线数据
246
+ Args:
247
+ symbol (_type_): _description_
248
+ bar (_type_, optional): _description_. Defaults to '15m'.
249
+ Returns:
250
+ _type_: _description_
251
+ """
252
+ # cache_key = (symbol, tf)
253
+ cache_valid_second = self.interval_map.get(tf, 4 * 60 * 60) # 默认缓存时间为60分钟
254
+ cache_key = (symbol, tf)
255
+
256
+ # 检查缓存是否存在且未过期
257
+ current_time = datetime.now()
258
+ if cache_key in self.cache_time:
259
+ # 计算缓存时间与当前时间的差值(秒)
260
+ cache_age = (current_time - self.cache_time[cache_key]).total_seconds()
261
+ if cache_age <= cache_valid_second:
262
+ # 缓存有效,直接返回
263
+ # print(f"使用缓存数据: {symbol} {tf} (缓存时间: {cache_age:.2f} 分钟前)")
264
+ return self._get_cache_historical_klines_df(symbol, tf)
265
+ else:
266
+ # 缓存过期,清除缓存
267
+ self.logger.debug(f"{symbol} : 缓存已过期: {symbol} {tf} (缓存时间: {cache_age:.2f} 秒前)")
268
+ self._get_cache_historical_klines_df.cache_clear()
269
+
270
+ # 获取新数据并更新缓存时间
271
+ self.logger.debug(f"{symbol} : 重新获取新数据: {symbol} {tf}")
272
+ self.cache_time[cache_key] = current_time
273
+ return self._get_cache_historical_klines_df(symbol, tf)
274
+
275
+
276
+ def get_historical_klines_df(self, symbol, tf='15m'):
277
+ """_summary_
278
+ 获取历史K线数据
279
+ Args:
280
+ symbol (_type_): _description_
281
+ bar (_type_, optional): _description_. Defaults to '15m'.
282
+ Returns:
283
+ _type_: _description_
284
+ """
285
+ return self.exchange.get_historical_klines_df(symbol=symbol, bar=tf)
286
+ def format_klines(self, klines) -> pd.DataFrame:
287
+
288
+ """_summary_
289
+ 格式化K线数据
290
+ Args:
291
+ klines (_type_): _description_
292
+ Returns:
293
+ _type_: _description_
294
+ """
295
+
296
+ return self.exchange.format_klines(klines)
297
+
298
+ def find_PDArrays(self, symbol, struct, side=None, start_index=-1, pair_config=None) -> pd.DataFrame:
299
+ """_summary_
300
+ 寻找PDArray
301
+ Args:
302
+ symbol (_type_): _description_
303
+ data (_type_): _description_
304
+ side (_type_): _description_
305
+ start_index (_type_): _description_
306
+ is_valid (bool, optional): _description_. Defaults to True.
307
+ pair_config (_type_): _description_
308
+ Returns:
309
+ _type_: _description_
310
+ """
311
+ return self.smcPDArray.find_PDArrays(struct=struct, side=side, start_index=start_index)
312
+
313
+ def find_OBs(self, symbol, struct, side=None, start_index=-1, is_valid=True, pair_config=None) -> pd.DataFrame:
314
+ """_summary_
315
+ 识别OB
316
+ Args:
317
+ symbol (_type_): _description_
318
+ data (_type_): _description_
319
+ side (_type_): _description_
320
+ start_index (_type_): _description_
321
+ is_valid (bool, optional): _description_. Defaults to True.
322
+ pair_config (_type_): _description_
323
+ Returns:
324
+ _type_: _description_
325
+ """
326
+
327
+
328
+ return self.smcOB.find_OBs(struct=struct, side=side, start_index=start_index, is_valid=is_valid)
329
+
330
+ def get_lastest_OB(self, symbol, data, trend, start_index=-1) -> dict:
331
+ """_summary_
332
+ 获取最新的Order Block
333
+ Args:
334
+ symbol (_type_): _description_
335
+ data (_type_): _description_
336
+ trend (_type_): _description_
337
+ start_index (_type_): _description_
338
+ Returns:
339
+ _type_: _description_
340
+ """
341
+
342
+ return self.smcOB.get_lastest_OB(data=data, trend=trend, start_index=start_index)
343
+
344
+
345
+ def find_FVGs(self, symbol, data, side, check_balanced=True, start_index=-1, pair_config=None) -> pd.DataFrame:
346
+ """_summary_
347
+ 寻找公允价值缺口
348
+ Args:
349
+ symbol (_type_): _description_
350
+ data (_type_): _description_
351
+ side (_type_): _description_
352
+ check_balanced (bool, optional): _description_. Defaults to True.
353
+ start_index (_type_): _description_
354
+ pair_config (_type_): _description_
355
+ Returns:
356
+ _type_: _description_
357
+ """
358
+
359
+
360
+ return self.smcFVG.find_FVGs(data, side, check_balanced, start_index)
361
+
362
+ def build_struct(self, symbol, data) -> pd.DataFrame:
363
+
364
+ """_summary_
365
+ 构建SMC结构,参考 Tradingview OP@SMC Structures and FVG
366
+ Args:
367
+ symbol (_type_): _description_
368
+ data (_type_): _description_
369
+ Returns:
370
+ _type_: _description_
371
+ """
372
+
373
+
374
+ return self.smcStruct.build_struct(data)
375
+
376
+ def get_last_struct(self, symbol, data) -> dict:
377
+ """_summary_
378
+ 获取最后一个SMC结构
379
+ Args:
380
+ symbol (_type_): _description_
381
+ data (_type_): _description_
382
+ Returns:
383
+ _type_: _description_
384
+ """
385
+ return self.smcStruct.get_last_struct(data)
386
+
387
+ def reset_all_cache(self, symbol):
388
+ """_summary_
389
+ 重置所有缓存数据
390
+ """
391
+ if symbol in self.place_order_prices:
392
+ self.place_order_prices.pop(symbol)
393
+ self.clear_cache_historical_klines_df(symbol)
394
+
395
+ def check_position(self, symbol) -> bool:
396
+ """
397
+ 检查指定交易对是否有持仓,失败时最多重试3次
398
+
399
+ Args:
400
+ symbol: 交易对ID
401
+
402
+ Returns:
403
+ bool: 是否有持仓
404
+ """
405
+ try:
406
+ position = self.exchange.fetch_position(symbol=symbol)
407
+ return position['contracts'] > 0
408
+ except Exception as e:
409
+ error_message = f"{symbol} 检查持仓失败: {e}"
410
+ self.logger.error(error_message)
411
+ self.send_feishu_notification(symbol,error_message)
412
+ return False
413
+
414
+ @abstractmethod
415
+ def process_pair(self, symbol, pair_config):
416
+ """
417
+ 处理单个交易对的策略逻辑
418
+
419
+ Args:
420
+ symbol: 交易对名称
421
+ pair_config: 交易对配置信息
422
+
423
+ Raises:
424
+ NotImplementedError: 子类必须实现此方法
425
+ """
426
+ raise NotImplementedError("必须在子类中实现process_pair方法")
427
+
428
+ def monitor_klines(self):
429
+ symbols = list(self.trading_pairs_config.keys()) # 获取所有币对的ID
430
+ batch_size = 10 # 每批处理的数量
431
+ # while True:
432
+
433
+ for i in range(0, len(symbols), batch_size):
434
+ batch = symbols[i:i + batch_size]
435
+ with ThreadPoolExecutor(max_workers=batch_size) as executor:
436
+ futures = [executor.submit(self.process_pair, symbol,self.get_pair_config(symbol)) for symbol in batch]
437
+ for future in as_completed(futures):
438
+ future.result() # Raise any exceptions caught during execution
@@ -396,7 +396,6 @@ class ThreeLineStrategyMaker:
396
396
  prices: 价格列表。
397
397
  Returns:
398
398
  diff: 计算最高价列的最大值与最小值的差值
399
-
400
399
  """
401
400
  # limit = int(pair_config.get('ema_range_limit', 1))
402
401
  # period = int(pair_config.get('ema_range_period', 3))
maker/main.py CHANGED
@@ -1,98 +1,103 @@
1
1
  import logging
2
2
  import logging.config
3
3
  import yaml
4
+ import importlib
5
+ import importlib.metadata
4
6
  from apscheduler.triggers.interval import IntervalTrigger
5
7
  from apscheduler.schedulers.blocking import BlockingScheduler
6
8
  from datetime import datetime
7
9
  from pyfiglet import Figlet
10
+ from typing import Dict, Any
8
11
 
9
- from maker.ThreeLineStrategyMaker import ThreeLineStrategyMaker
10
- from maker.SMCStrategyMaker import SMCStrategyMaker
11
- from maker.BestFVGStrategyMaker import BestFVGStrategyMaker
12
-
13
-
14
- def read_config_file(file_path):
12
+ def read_config_file(file_path: str) -> Dict[str, Any]:
13
+ """读取并解析YAML配置文件"""
15
14
  try:
16
- # 打开 YAML 文件
17
15
  with open(file_path, 'r', encoding='utf-8') as file:
18
- # 使用 yaml.safe_load 方法解析 YAML 文件内容
19
- data = yaml.safe_load(file)
20
- return data
16
+ return yaml.safe_load(file)
21
17
  except FileNotFoundError:
22
- raise Exception(f"文件 {file_path} 未找到。")
18
+ raise FileNotFoundError(f"文件 {file_path} 未找到。")
23
19
  except yaml.YAMLError as e:
24
- raise Exception(f"解析 {file_path} 文件时出错: {e}")
25
-
26
- def run_bot(bot, logger):
27
- try:
20
+ raise yaml.YAMLError(f"解析 {file_path} 文件时出错: {e}")
28
21
 
22
+ def run_bot(bot: Any, logger: logging.Logger) -> None:
23
+ """执行机器人监控任务"""
24
+ try:
29
25
  bot.monitor_klines()
30
26
  except Exception as e:
31
27
  logger.error(f"执行任务时发生错误: {str(e)}", exc_info=True)
32
28
 
33
- def main():
29
+ def calculate_next_run_time(current_time: datetime, interval: int) -> datetime:
30
+ """计算下一次运行时间"""
31
+ next_run = current_time.replace(second=58, microsecond=0)
32
+ current_minute = next_run.minute
33
+ next_interval = ((current_minute // interval) + 1) * interval - 1
34
+
35
+ if next_interval >= 60:
36
+ next_interval %= 60
37
+ next_run = next_run.replace(hour=next_run.hour + 1)
38
+
39
+ return next_run.replace(minute=next_interval)
34
40
 
35
- import importlib.metadata
36
- package_name = __package__ or "openfund-maker"
41
+ def setup_scheduler(bot: Any, logger: logging.Logger, interval: int) -> None:
42
+ """设置并启动调度器"""
43
+ scheduler = BlockingScheduler()
44
+ next_run = calculate_next_run_time(datetime.now(), interval)
45
+
46
+ scheduler.add_job(
47
+ run_bot,
48
+ IntervalTrigger(minutes=interval),
49
+ args=[bot, logger],
50
+ next_run_time=next_run
51
+ )
52
+
53
+ try:
54
+ logger.info(f"启动定时任务调度器,从 {next_run} 开始每{interval}分钟执行一次...")
55
+ scheduler.start()
56
+ except (KeyboardInterrupt, SystemExit):
57
+ logger.info("程序收到中断信号,正在退出...")
58
+ scheduler.shutdown()
59
+ def create_strategy_instance(maker_name: str, configs: Dict[str, Any], logger: logging.Logger, exchangeKey:str):
60
+ """创建策略实例"""
61
+ module = importlib.import_module(f"maker.{maker_name}")
62
+ strategy_class = getattr(module, maker_name)
63
+ return strategy_class(
64
+ configs,
65
+ configs['platform'][exchangeKey],
66
+ configs['common'],
67
+ logger=logger,
68
+ exchangeKey=exchangeKey
69
+ )
70
+ def main():
71
+ # 获取包信息
37
72
  version = importlib.metadata.version("openfund-maker")
73
+ package_name = __package__ or "openfund-maker"
38
74
 
75
+ # 读取配置
39
76
  maker_config_path = 'maker_config.yaml'
40
77
  config_data = read_config_file(maker_config_path)
41
-
78
+
79
+ # 设置日志
42
80
  logging.config.dictConfig(config_data["Logger"])
43
81
  logger = logging.getLogger("openfund-maker")
44
82
 
45
- f = Figlet(font="standard") # 字体可选(如 "block", "bubble")
46
- logger.info(f"\n{f.renderText("OpenFund Maker")}")
47
-
48
- common_config = config_data['common']
49
- feishu_webhook_url = common_config['feishu_webhook']
50
- maker = common_config.get('actived_maker', 'MACDStrategyMaker')
51
- logger.info(f" ++ {package_name}.{maker}:{version} is doing...")
83
+ # 显示启动标题
84
+ f = Figlet(font="standard")
85
+ logger.info(f"\n{f.renderText('OpenFund Maker')}")
52
86
 
53
- platform_config = config_data['platform']['okx']
54
- # 根据配置动态创建策略实例
55
- strategy_class = globals()[maker]
56
- bot = strategy_class(config_data, platform_config, common_config, feishu_webhook=feishu_webhook_url, logger=logger)
57
-
58
-
59
- # 获取计划配置
87
+ # 获取配置信息
88
+ common_config = config_data['common']
89
+ maker_name = common_config.get('actived_maker', 'StrategyMaker')
90
+ logger.info(f" ++ {package_name}.{maker_name}:{version} is doing...")
91
+ exchangeKey = common_config.get("exchange_key", "okx")
92
+ # 创建并运行策略实例
93
+ bot = create_strategy_instance(maker_name, config_data, logger, exchangeKey)
94
+
95
+ # 处理调度
60
96
  schedule_config = common_config.get('schedule', {})
61
97
  if schedule_config.get('enabled', False):
62
- scheduler = BlockingScheduler()
63
-
64
- # 设置每5分钟执行一次的任务,从整点开始
65
- monitor_interval = int(schedule_config.get('monitor_interval', 4))
66
-
67
- # 计算下一个整点分钟
68
- now = datetime.now()
69
- # 将当前时间的秒和微秒设置为0
70
- next_run = now.replace(second=58, microsecond=0)
71
- # 计算下一个周期的开始时间
72
- current_minute = next_run.minute
73
- # 向上取整到下一个周期时间点, 然后再减去2Units,比如秒就是58秒执行。
74
- next_interval = ((current_minute // monitor_interval) + 1) * monitor_interval -1
75
- # 如果下一个周期时间点超过60分钟,需要调整为下一个小时的对应分钟数
76
- if next_interval >= 60:
77
- next_interval = next_interval % 60
78
- next_run = next_run.replace(hour=next_run.hour + 1)
79
- next_run = next_run.replace(minute=next_interval)
80
-
81
- scheduler.add_job(
82
- run_bot,
83
- IntervalTrigger(minutes=monitor_interval),
84
- args=[bot, logger],
85
- next_run_time=next_run # 从下一个周期整点开始
86
- )
87
-
88
- try:
89
- logger.info(f"启动定时任务调度器,从 {next_run} 开始每{monitor_interval}分钟执行一次...")
90
- scheduler.start()
91
- except (KeyboardInterrupt, SystemExit):
92
- logger.info("程序收到中断信号,正在退出...")
93
- scheduler.shutdown()
98
+ monitor_interval = int(schedule_config.get('monitor_interval', 4))
99
+ setup_scheduler(bot, logger, monitor_interval)
94
100
  else:
95
- # 如果未启用计划,直接运行
96
101
  run_bot(bot, logger)
97
102
 
98
103
  if __name__ == "__main__":
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.3
2
2
  Name: openfund-maker
3
- Version: 2.2.8
3
+ Version: 2.3.1
4
4
  Summary: Openfund-maker.
5
5
  Requires-Python: >=3.9,<4.0
6
6
  Classifier: Programming Language :: Python :: 3
@@ -11,6 +11,7 @@ Classifier: Programming Language :: Python :: 3.12
11
11
  Classifier: Programming Language :: Python :: 3.13
12
12
  Requires-Dist: apscheduler (>=3.11.0,<4.0.0)
13
13
  Requires-Dist: ccxt (>=4.4.26,<5.0.0)
14
+ Requires-Dist: openfund-core (>=1.0.0,<2.0.0)
14
15
  Requires-Dist: pandas (>=2.2.3,<3.0.0)
15
16
  Requires-Dist: pyfiglet (>=1.0.2,<2.0.0)
16
17
  Requires-Dist: pyyaml (>=6.0.2,<7.0.0)
@@ -1,15 +1,17 @@
1
- maker/BestFVGStrategyMaker.py,sha256=xIiDIvMw8RwHayUuz5Won3stSG9BxdYIQgeXDok_SqM,12336
1
+ maker/BestFVGStrategyMaker.py,sha256=a9UfClrfzkgX6jXL2FODzANtawrmGeZ_PVeO1-tweDc,12532
2
+ maker/BestTopDownStrategyMaker.py,sha256=w3g9U6sQO5ybY8dx7F6ojsWjXbQ06WANa9M_z1045jA,18009
2
3
  maker/MACDStrategyMaker.py,sha256=WX8wqpF9h5W4WclN1NjZ_Bur7KFi_aMTvacfLyHzEcI,12681
3
4
  maker/SMCStrategyMaker.py,sha256=hkDqymWnuyYDo1gTYY_uyO4H4yOwCw8NBTM9RcfLRPc,28780
4
- maker/ThreeLineStrategyMaker.py,sha256=jCdyTvI9oTMixCM0tQF9D_-6ig0-bYstKuRFECE4deE,32099
5
+ maker/StrategyMaker.py,sha256=2nF5TPqHUIAVo3qK2AyZvfsWCrhDNtwULPJ2bIrOb-g,16550
6
+ maker/ThreeLineStrategyMaker.py,sha256=K4NZB1rH8IZMVrCFEnzwXctZQbyI9ZdyTMrYObpl-vM,32095
5
7
  maker/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
6
8
  maker/history_code/WickReversalStrategyMaker.py,sha256=7DqPDVJot4EM0_lSAcFAHrR9rNvkIds9KLMoDOiAHEc,17486
7
9
  maker/history_code/config.py,sha256=YPxghO5i0vgRg9Cja8kGj9O7pgSbbtzOgf3RexqXXwY,1188
8
10
  maker/history_code/okxapi.py,sha256=_9G0U_o0ZC8NxaT6PqpiLgxBm9gPobC9PsFHZE1c5w0,553
9
11
  maker/history_code/zhen.py.bak,sha256=HNkrQbJts8G9umE9chEFsc0cLQApcM9KOVNMYPpkBXM,10918
10
12
  maker/history_code/zhen_2.py,sha256=4IaHVtTCMSlrLGSTZrWpW2q-f7HZsUNRkW_-5QgWv24,10509
11
- maker/main.py,sha256=kumgkgyYj1tVKrHTQmSx0prFs9K4i5KpvcOnqimV2eA,3805
12
- openfund_maker-2.2.8.dist-info/METADATA,sha256=sXu087Rbc_JVoJ3aKcWrt2RCJQ7rhB5u0zgblu2Rcf4,1955
13
- openfund_maker-2.2.8.dist-info/WHEEL,sha256=IYZQI976HJqqOpQU6PHkJ8fb3tMNBFjg-Cn-pwAbaFM,88
14
- openfund_maker-2.2.8.dist-info/entry_points.txt,sha256=gKMytICEKcMRFQDFkHZLnIpID7UQFoTIM_xcpiiV6Ns,50
15
- openfund_maker-2.2.8.dist-info/RECORD,,
13
+ maker/main.py,sha256=PRCP2qCUiUFPQyi1YbvnmW9KqeCZcc0zGjy9OBvMWbM,3723
14
+ openfund_maker-2.3.1.dist-info/METADATA,sha256=hIo1FIfLMrxBxds9aEpfrW1yI3iuNLZZAkBVXll3kvc,2001
15
+ openfund_maker-2.3.1.dist-info/WHEEL,sha256=IYZQI976HJqqOpQU6PHkJ8fb3tMNBFjg-Cn-pwAbaFM,88
16
+ openfund_maker-2.3.1.dist-info/entry_points.txt,sha256=gKMytICEKcMRFQDFkHZLnIpID7UQFoTIM_xcpiiV6Ns,50
17
+ openfund_maker-2.3.1.dist-info/RECORD,,