openfund-maker 2.2.6__py3-none-any.whl → 2.2.8__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/BestFVGStrategyMaker.py +12 -12
- maker/MACDStrategyMaker.py +2 -2
- maker/SMCStrategyMaker.py +47 -187
- maker/ThreeLineStrategyMaker.py +88 -49
- maker/main.py +16 -33
- {openfund_maker-2.2.6.dist-info → openfund_maker-2.2.8.dist-info}/METADATA +1 -1
- openfund_maker-2.2.8.dist-info/RECORD +15 -0
- maker/main_m.py +0 -378
- openfund_maker-2.2.6.dist-info/RECORD +0 -16
- /maker/{WickReversalStrategyMaker.py → history_code/WickReversalStrategyMaker.py} +0 -0
- /maker/{config.py → history_code/config.py} +0 -0
- /maker/{okxapi.py → history_code/okxapi.py} +0 -0
- /maker/{zhen.py.bak → history_code/zhen.py.bak} +0 -0
- /maker/{zhen_2.py → history_code/zhen_2.py} +0 -0
- {openfund_maker-2.2.6.dist-info → openfund_maker-2.2.8.dist-info}/WHEEL +0 -0
- {openfund_maker-2.2.6.dist-info → openfund_maker-2.2.8.dist-info}/entry_points.txt +0 -0
maker/BestFVGStrategyMaker.py
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
# -*- coding: utf-8 -*-
|
2
2
|
import traceback
|
3
3
|
from typing import override
|
4
|
-
import pandas as pd
|
5
4
|
|
6
|
-
from maker.SMCStrategyMaker import SMCStrategyMaker
|
7
5
|
|
6
|
+
from maker.SMCStrategyMaker import SMCStrategyMaker
|
8
7
|
|
9
8
|
class BestFVGStrategyMaker(SMCStrategyMaker):
|
10
|
-
def __init__(self, config, platform_config, feishu_webhook=None,logger=None):
|
11
|
-
super().__init__(config, platform_config, feishu_webhook, logger)
|
9
|
+
def __init__(self, config, platform_config, common_config, feishu_webhook=None,logger=None):
|
10
|
+
super().__init__(config, platform_config, common_config, feishu_webhook, logger)
|
11
|
+
|
12
12
|
|
13
13
|
self.htf_last_CHoCH = {} #记录HTF的CHoCH struct
|
14
14
|
|
@@ -97,7 +97,7 @@ class BestFVGStrategyMaker(SMCStrategyMaker):
|
|
97
97
|
|
98
98
|
htf_Klines = self.get_historical_klines(symbol=symbol, bar=htf)
|
99
99
|
htf_df = self.format_klines(htf_Klines)
|
100
|
-
|
100
|
+
precision = self.get_precision_length(symbol)
|
101
101
|
|
102
102
|
# 初始化HTF趋势相关变量
|
103
103
|
htf_side, valid_htf_struct = None, None
|
@@ -153,7 +153,7 @@ class BestFVGStrategyMaker(SMCStrategyMaker):
|
|
153
153
|
}
|
154
154
|
|
155
155
|
self.logger.info(f"{symbol} : {htf} 趋势={htf_struct_label} 匹配 {htf_entry_struct} struct")
|
156
|
-
self.logger.debug(f"{symbol} : {htf}\npivot_high={htf_pivot_high} pivot_low={htf_pivot_low} mid_line={htf_mid_line}\n溢价区={premium_box}\n折价区={discount_box}")
|
156
|
+
self.logger.debug(f"{symbol} : {htf}\npivot_high={htf_pivot_high:.{precision}} pivot_low={htf_pivot_low:.{precision}} mid_line={htf_mid_line:.{precision}}\n溢价区={premium_box}\n折价区={discount_box}")
|
157
157
|
|
158
158
|
# 3. find HTF FVG
|
159
159
|
|
@@ -171,7 +171,7 @@ class BestFVGStrategyMaker(SMCStrategyMaker):
|
|
171
171
|
self.logger.debug(f"{symbol} : 价格[未进入] HTF_FVG区域,不进行下单")
|
172
172
|
return
|
173
173
|
else:
|
174
|
-
self.logger.debug(f"{symbol} : 价格[进入] HTF_FVG
|
174
|
+
self.logger.debug(f"{symbol} : 价格[进入] HTF_FVG区域,开始下单。\n fvgbox={htf_fvg_boxes[-1]}")
|
175
175
|
|
176
176
|
# 4. LTF 判断struct趋势是否有CHoCH
|
177
177
|
|
@@ -199,7 +199,7 @@ class BestFVGStrategyMaker(SMCStrategyMaker):
|
|
199
199
|
}
|
200
200
|
|
201
201
|
self.logger.info(f"{symbol} : {ltf} 趋势={ltf_struct_label} struct={ltf_struct}")
|
202
|
-
self.logger.debug(f"{symbol} : {ltf} \npivot_high={ltf_last_pivot_high} pivot_low={ltf_last_pivot_low} mid_line={ltf_last_mid_line}\n溢价区={ltf_premium_box}\n折价区={ltf_discount_box}")
|
202
|
+
self.logger.debug(f"{symbol} : {ltf} \npivot_high={ltf_last_pivot_high} pivot_low={ltf_last_pivot_low:.{precision}} mid_line={ltf_last_mid_line:.{precision}}\n溢价区={ltf_premium_box}\n折价区={ltf_discount_box}")
|
203
203
|
|
204
204
|
|
205
205
|
# 5. LTF 寻找FVG,下单
|
@@ -227,11 +227,11 @@ class BestFVGStrategyMaker(SMCStrategyMaker):
|
|
227
227
|
|
228
228
|
|
229
229
|
# 4. LTF 寻找FVG,下单
|
230
|
-
order_price = ltf_fvg_boxes[-1]["top"] if ltf_struct_side == "buy" else ltf_fvg_boxes[-1]["bot"]
|
230
|
+
order_price = self.toDecimal(ltf_fvg_boxes[-1]["top"] if ltf_struct_side == "buy" else ltf_fvg_boxes[-1]["bot"] )
|
231
231
|
|
232
|
-
latest_order_price = self.place_order_prices.get(symbol,0
|
232
|
+
latest_order_price = self.toDecimal(self.place_order_prices.get(symbol,0))
|
233
233
|
if order_price == latest_order_price:
|
234
|
-
self.logger.debug(f"{symbol} : 下单价格 {order_price} 未变化,不进行下单。")
|
234
|
+
self.logger.debug(f"{symbol} : 下单价格 {order_price:.{precision}} 未变化,不进行下单。")
|
235
235
|
return
|
236
236
|
|
237
237
|
|
@@ -239,7 +239,7 @@ class BestFVGStrategyMaker(SMCStrategyMaker):
|
|
239
239
|
self.cancel_all_orders(symbol=symbol)
|
240
240
|
self.place_order(symbol=symbol, price=order_price, side=ltf_struct_side, pair_config=pair_config)
|
241
241
|
self.place_order_prices[symbol] = order_price # 记录下单价格,过滤重复下单
|
242
|
-
self.logger.debug(f"{symbol} : {ltf_struct_side}, 下单价格 {order_price}")
|
242
|
+
self.logger.debug(f"{symbol} : {ltf_struct_side}, 下单价格 {order_price:.{precision}}")
|
243
243
|
|
244
244
|
|
245
245
|
except KeyboardInterrupt:
|
maker/MACDStrategyMaker.py
CHANGED
@@ -8,8 +8,8 @@ from maker.ThreeLineStrategyMaker import ThreeLineStrategyMaker
|
|
8
8
|
|
9
9
|
|
10
10
|
class MACDStrategyMaker(ThreeLineStrategyMaker):
|
11
|
-
def __init__(self, config, platform_config, feishu_webhook=None,logger=None):
|
12
|
-
super().__init__(config, platform_config, feishu_webhook, logger)
|
11
|
+
def __init__(self, config, platform_config, common_config, feishu_webhook=None,logger=None):
|
12
|
+
super().__init__(config, platform_config, common_config, feishu_webhook, logger)
|
13
13
|
|
14
14
|
def get_macd_cross_direction(self, symbol, kLines, strategy=None) -> dict:
|
15
15
|
# 计算最近三个交叉点
|
maker/SMCStrategyMaker.py
CHANGED
@@ -1,16 +1,18 @@
|
|
1
1
|
# -*- coding: utf-8 -*-
|
2
2
|
import traceback
|
3
3
|
import pandas as pd
|
4
|
+
from decimal import Decimal
|
4
5
|
|
5
|
-
from maker.ThreeLineStrategyMaker import ThreeLineStrategyMaker
|
6
6
|
|
7
|
+
from maker.ThreeLineStrategyMaker import ThreeLineStrategyMaker
|
7
8
|
|
8
9
|
class SMCStrategyMaker(ThreeLineStrategyMaker):
|
9
|
-
def __init__(self, config, platform_config, feishu_webhook=None,logger=None):
|
10
|
-
super().__init__(config, platform_config, feishu_webhook, logger)
|
10
|
+
def __init__(self, config, platform_config, common_config, feishu_webhook=None,logger=None):
|
11
|
+
super().__init__(config, platform_config, common_config, feishu_webhook, logger)
|
12
|
+
|
11
13
|
self.place_order_prices = {} # 记录每个symbol的挂单价格
|
12
14
|
|
13
|
-
def place_order(self,symbol, price, side,pair_config):
|
15
|
+
def place_order(self,symbol, price:Decimal, side,pair_config):
|
14
16
|
"""_summary_
|
15
17
|
下单
|
16
18
|
Args:
|
@@ -81,8 +83,8 @@ class SMCStrategyMaker(ThreeLineStrategyMaker):
|
|
81
83
|
OB_boxes = [
|
82
84
|
{
|
83
85
|
'index': idx,
|
84
|
-
'top':
|
85
|
-
'bot':
|
86
|
+
'top': self.toDecimal(df.loc[idx, 'low']), # OB低点为当前K线的最低点
|
87
|
+
'bot': self.toDecimal(df.loc[idx - 1 if idx > df.index[0] else idx, 'body_high']) # OB高点为前一根K线的实体高点
|
86
88
|
}
|
87
89
|
for idx in df.index
|
88
90
|
# 判断条件:是第一根K线(极值点)或当前下降趋势大于前一个,且前一根K线实体高点小于阈值
|
@@ -94,8 +96,8 @@ class SMCStrategyMaker(ThreeLineStrategyMaker):
|
|
94
96
|
OB_boxes = [
|
95
97
|
{
|
96
98
|
'index': idx,
|
97
|
-
'top':
|
98
|
-
'bot':
|
99
|
+
'top': self.toDecimal(df.loc[idx, 'high']), # OB高点为当前K线的最高点
|
100
|
+
'bot': self.toDecimal(df.loc[idx - 1 if idx > df.index[0] else idx, 'body_low']) # OB低点为前一根K线的实体低点
|
99
101
|
}
|
100
102
|
for idx in df.index
|
101
103
|
# 判断条件:是第一根K线(极值点)或当前上升趋势小于前一个,且前一根K线实体低点大于阈值
|
@@ -106,7 +108,7 @@ class SMCStrategyMaker(ThreeLineStrategyMaker):
|
|
106
108
|
return OB_boxes
|
107
109
|
|
108
110
|
|
109
|
-
def find_fvg_boxes(self, data, side, threshold, check_balanced=True, pivot_index=0, symbol=None, pair_config=None) -> list:
|
111
|
+
def find_fvg_boxes(self, data, side, threshold:Decimal, check_balanced=True, pivot_index=0, symbol=None, pair_config=None) -> list:
|
110
112
|
"""_summary_
|
111
113
|
寻找公允价值缺口
|
112
114
|
Args:
|
@@ -142,8 +144,8 @@ class SMCStrategyMaker(ThreeLineStrategyMaker):
|
|
142
144
|
fvg_boxes = [
|
143
145
|
{
|
144
146
|
'index': idx - 2, # FVG的索引
|
145
|
-
'top': min(
|
146
|
-
'bot':
|
147
|
+
'top': min(self.toDecimal(df.loc[idx - 1, 'low']),threshold), # FVG高点为右1K线的最低点
|
148
|
+
'bot': self.toDecimal(df.loc[idx - 3, 'high']) # FVG低点为左1K线的最高点
|
147
149
|
}
|
148
150
|
# [df.loc[idx - 1, 'low'], df.loc[idx - 3, 'high'], idx - 2]
|
149
151
|
for idx in valid_indices
|
@@ -167,8 +169,8 @@ class SMCStrategyMaker(ThreeLineStrategyMaker):
|
|
167
169
|
fvg_boxes = [
|
168
170
|
{
|
169
171
|
'index': idx - 2, # FVG的索引
|
170
|
-
'top':
|
171
|
-
'bot': max(
|
172
|
+
'top': self.toDecimal(df.loc[idx - 3, 'low']), # FVG高点为右1K线的最高点
|
173
|
+
'bot': max(self.toDecimal(df.loc[idx - 1, 'high']),threshold) # FVG低点为左1K线的最低点
|
172
174
|
}
|
173
175
|
|
174
176
|
for idx in valid_indices
|
@@ -179,153 +181,6 @@ class SMCStrategyMaker(ThreeLineStrategyMaker):
|
|
179
181
|
|
180
182
|
return fvg_boxes
|
181
183
|
|
182
|
-
|
183
|
-
# def detect_struct(self, data, prd=10, struct_key=None, check_valid_range=True, check_bounds=True, global_extremum=False, s1=True, resp=7) -> dict:
|
184
|
-
# """_summary_
|
185
|
-
# 识别SMC结构,参考 Tradingview Smart Money Concepts Probability (Expo)@Openfund
|
186
|
-
|
187
|
-
# Args:
|
188
|
-
# data (df): df格式的K线数据
|
189
|
-
# prd (int): 计算Swing Points的bar数量
|
190
|
-
# struct_key (str): 结构类型,如 'CHoCH'|'SMS'|'BMS'
|
191
|
-
# check_valid_range (bool): 结构类型在 pivot_high_index 和 pivot_low_index 之间为有效范围内,默认为False
|
192
|
-
# check_bounds (bool): 计算Swing Points是否检查边界,默认为True
|
193
|
-
# global_extremum (bool): 是否使用全局极值点,默认为False
|
194
|
-
# s1 (bool): 结构响应布尔值
|
195
|
-
# resp (int): 响应周期
|
196
|
-
# Returns:
|
197
|
-
# dict: 包含结构识别结果的字典,包含以下字段:
|
198
|
-
# "struct": 结构类型,如 'Bullish_CHoCH'|'Bullish_SMS'|'Bullish_BMS'|'Bearish_CHoCH'|'Bearish_SMS'|'Bearish_BMS'
|
199
|
-
# "index": 结构出现的位置索引
|
200
|
-
# "pivot_high": 枢轴高点价格
|
201
|
-
# "pivot_high_index": 枢轴高点索引
|
202
|
-
# "pivot_low": 枢轴低点价格
|
203
|
-
# "pivot_low_index": 枢轴低点索引
|
204
|
-
# "side": 交易方向,'buy'或'sell'
|
205
|
-
|
206
|
-
|
207
|
-
# """
|
208
|
-
|
209
|
-
# # data = data.copy()
|
210
|
-
# data['Up'] = None
|
211
|
-
# data['Dn'] = None
|
212
|
-
# data['iUp'] = None
|
213
|
-
# data['iDn'] = None
|
214
|
-
# data['pos'] = 0
|
215
|
-
# data['pattern'] = None
|
216
|
-
|
217
|
-
# # 初始化 Up 和 Dn 的第一个值
|
218
|
-
# data.at[0, 'Up'] = data.at[0, 'high']
|
219
|
-
# data.at[0, 'Dn'] = data.at[0, 'low']
|
220
|
-
|
221
|
-
|
222
|
-
# for index in range(1, len(data)):
|
223
|
-
|
224
|
-
# data.at[index, 'Up'] = max(data.at[index - 1, 'Up'], data.at[index, 'high'])
|
225
|
-
# data.at[index, 'Dn'] = min(data.at[index - 1, 'Dn'], data.at[index, 'low'])
|
226
|
-
# data.at[index, 'pos'] = data.at[index - 1, 'pos']
|
227
|
-
# data.at[index, 'iUp'] = data.at[max(0,index - 1), 'iUp'] if data.at[max(0,index - 1), 'iUp'] is not None else index
|
228
|
-
# data.at[index, 'iDn'] = data.at[max(0,index - 1), 'iDn'] if data.at[max(0,index - 1), 'iDn'] is not None else index
|
229
|
-
|
230
|
-
# # 寻找枢轴高点和低点
|
231
|
-
# pvtHi = self.is_pivot_high(data, index, prd, check_bounds)
|
232
|
-
# pvtLo = self.is_pivot_low(data, index, prd, check_bounds)
|
233
|
-
|
234
|
-
# if pvtHi:
|
235
|
-
# data.at[index, 'Up'] = data.at[index, 'high']
|
236
|
-
# data.at[index, 'iUp'] = index
|
237
|
-
# if pvtLo:
|
238
|
-
# data.at[index, 'Dn'] = data.at[index, 'low']
|
239
|
-
# data.at[index, 'iDn'] = index
|
240
|
-
# # 寻找Bullish结构
|
241
|
-
# if data.at[index, 'Up'] > data.at[index - 1, 'Up']:
|
242
|
-
|
243
|
-
# data.at[index, 'iUp'] = index
|
244
|
-
# if data.at[index - 1, 'pos'] <= 0:
|
245
|
-
# # data.at[index, 'pattern'] = 'CHoCH (Bullish)'
|
246
|
-
# data.at[index, 'pattern'] = 'Bullish_CHoCH'
|
247
|
-
# data.at[index, 'pos'] = 1
|
248
|
-
# elif data.at[index - 1, 'pos'] == 1 \
|
249
|
-
# and data.at[index - 1, 'Up'] == data.at[max(0,index - (resp if s1 else prd)), 'Up']:
|
250
|
-
# data.at[index, 'pattern'] = 'Bullish_SMS'
|
251
|
-
# data.at[index, 'pos'] = 2
|
252
|
-
|
253
|
-
# elif data.at[index - 1, 'pos'] > 1 \
|
254
|
-
# and data.at[index - 1, 'Up'] == data.at[max(0,index - (resp if s1 else prd)), 'Up']:
|
255
|
-
# data.at[index, 'pattern'] = 'Bullish_BMS'
|
256
|
-
# data.at[index, 'pos'] = data.at[index - 1, 'pos'] + 1
|
257
|
-
|
258
|
-
# elif global_extremum and data.at[index, 'Up'] < data.at[index - 1, 'Up']:
|
259
|
-
# data.at[index, 'iUp'] = data.at[index - 1, 'iUp']
|
260
|
-
|
261
|
-
# # # 寻找Bearish结构
|
262
|
-
# if data.at[index, 'Dn'] < data.at[index - 1, 'Dn']:
|
263
|
-
# data.at[index, 'iDn'] = index
|
264
|
-
# if data.at[index - 1, 'pos'] >= 0:
|
265
|
-
|
266
|
-
# data.at[index, 'pattern'] = 'Bearish_CHoCH'
|
267
|
-
# data.at[index, 'pos'] = -1
|
268
|
-
# elif data.at[index - 1, 'pos'] == -1 \
|
269
|
-
# and data.at[index - 1, 'Dn'] == data.at[max(0,index - (resp if s1 else prd)), 'Dn']:
|
270
|
-
# data.at[index, 'pattern'] = 'Bearish_SMS'
|
271
|
-
# data.at[index, 'pos'] = -2
|
272
|
-
# elif data.at[index - 1, 'pos'] < -1 \
|
273
|
-
# and data.at[index - 1, 'Dn'] == data.at[max(0,index - (resp if s1 else prd)), 'Dn']:
|
274
|
-
# data.at[index, 'pattern'] = 'Bearish_BMS'
|
275
|
-
# data.at[index, 'pos'] = data.at[index - 1, 'pos'] - 1
|
276
|
-
|
277
|
-
# elif global_extremum and data.at[index, 'Dn'] > data.at[index - 1, 'Dn']:
|
278
|
-
# data.at[index, 'iDn'] = data.at[index - 1, 'iDn']
|
279
|
-
|
280
|
-
# # 获取最后一个结构和位置
|
281
|
-
# last_struct = {
|
282
|
-
# "struct": None,
|
283
|
-
# "index": -1,
|
284
|
-
# "pivot_high": None,
|
285
|
-
# "pivot_high_index": -1,
|
286
|
-
# "pivot_low": None,
|
287
|
-
# "pivot_low_index": -1,
|
288
|
-
# "side": None
|
289
|
-
|
290
|
-
# }
|
291
|
-
|
292
|
-
|
293
|
-
# for i in range(len(data)-1, -1, -1):
|
294
|
-
# if check_valid_range:
|
295
|
-
# # 检查是否在pivot_high_index和pivot_low_index之间的有效范围内
|
296
|
-
# if data.at[i, 'iUp'] != -1 and data.at[i, 'iDn'] != -1:
|
297
|
-
# pivot_high_index = data.at[i, 'iUp']
|
298
|
-
# pivot_low_index = data.at[i, 'iDn']
|
299
|
-
# if i < min(pivot_high_index, pivot_low_index) or i > max(pivot_high_index, pivot_low_index):
|
300
|
-
# continue
|
301
|
-
|
302
|
-
# if data.at[i, 'pattern'] is not None:
|
303
|
-
# if struct_key is not None and struct_key not in data.at[i, 'pattern']:
|
304
|
-
# continue
|
305
|
-
# last_struct["struct"] = data.at[i, 'pattern']
|
306
|
-
# last_struct["index"] = i
|
307
|
-
|
308
|
-
# break
|
309
|
-
|
310
|
-
# if last_struct['struct'] is not None :
|
311
|
-
# # 找到最后一个结构的枢轴高点和低点,如果当前是孤立点,则取前一个孤立点
|
312
|
-
# # 判断交易方向
|
313
|
-
# if 'Bearish' in last_struct["struct"]:
|
314
|
-
# last_struct["side"] = 'sell'
|
315
|
-
# else :
|
316
|
-
# last_struct["side"] = 'buy'
|
317
|
-
|
318
|
-
# last_struct["pivot_high_index"] = int(data["iUp"].iloc[-1])
|
319
|
-
# last_struct["pivot_low_index"] = int(data["iDn"].iloc[-1])
|
320
|
-
|
321
|
-
# last_struct["pivot_high"] = float(data.loc[last_struct["pivot_high_index"], 'high'])
|
322
|
-
# last_struct["pivot_low"] = float(data.loc[last_struct["pivot_low_index"], 'low'])
|
323
|
-
# else:
|
324
|
-
# last_struct['struct'] = "None"
|
325
|
-
# last_struct["index"] = -1
|
326
|
-
|
327
|
-
|
328
|
-
# return last_struct
|
329
184
|
def build_struct(self, df, prd=20, check_bounds=True, global_extremum=False) :
|
330
185
|
|
331
186
|
"""_summary_
|
@@ -345,9 +200,16 @@ class SMCStrategyMaker(ThreeLineStrategyMaker):
|
|
345
200
|
|
346
201
|
|
347
202
|
for index in range(1, len(data)):
|
203
|
+
prev_up = self.toDecimal(data.at[index - 1, 'Up'])
|
204
|
+
curr_high = self.toDecimal(data.at[index, 'high'])
|
205
|
+
prev_dn = self.toDecimal(data.at[index - 1, 'Dn'])
|
206
|
+
curr_low = self.toDecimal(data.at[index, 'low'])
|
207
|
+
|
208
|
+
data.at[index, 'Up'] = max(prev_up, curr_high)
|
209
|
+
data.at[index, 'Dn'] = min(prev_dn, curr_low)
|
348
210
|
|
349
|
-
data.at[index, 'Up'] = max(data.at[index - 1, 'Up'], data.at[index, 'high'])
|
350
|
-
data.at[index, 'Dn'] = min(data.at[index - 1, 'Dn'], data.at[index, 'low'])
|
211
|
+
# data.at[index, 'Up'] = max(data.at[index - 1, 'Up'], data.at[index, 'high'])
|
212
|
+
# data.at[index, 'Dn'] = min(data.at[index - 1, 'Dn'], data.at[index, 'low'])
|
351
213
|
data.at[index, 'pos'] = data.at[index - 1, 'pos']
|
352
214
|
data.at[index, 'iUp'] = data.at[max(0,index - 1), 'iUp'] if data.at[max(0,index - 1), 'iUp'] is not None else index
|
353
215
|
data.at[index, 'iDn'] = data.at[max(0,index - 1), 'iDn'] if data.at[max(0,index - 1), 'iDn'] is not None else index
|
@@ -357,10 +219,10 @@ class SMCStrategyMaker(ThreeLineStrategyMaker):
|
|
357
219
|
pvtLo = self.is_pivot_low(data, index, prd, check_bounds)
|
358
220
|
|
359
221
|
if pvtHi:
|
360
|
-
data.at[index, 'Up'] = data.at[index, 'high']
|
222
|
+
data.at[index, 'Up'] = self.toDecimal(data.at[index, 'high'])
|
361
223
|
data.at[index, 'iUp'] = index
|
362
224
|
if pvtLo:
|
363
|
-
data.at[index, 'Dn'] = data.at[index, 'low']
|
225
|
+
data.at[index, 'Dn'] = self.toDecimal(data.at[index, 'low'])
|
364
226
|
data.at[index, 'iDn'] = index
|
365
227
|
# 寻找Bullish结构
|
366
228
|
if data.at[index, 'Up'] > data.at[index - 1, 'Up']:
|
@@ -444,8 +306,8 @@ class SMCStrategyMaker(ThreeLineStrategyMaker):
|
|
444
306
|
pivot_high_index = last_struct["pivot_high_index"] = int(data["iUp"].iloc[-1])
|
445
307
|
pivot_low_index = last_struct["pivot_low_index"] = int(data["iDn"].iloc[-1])
|
446
308
|
|
447
|
-
last_struct["pivot_high"] =
|
448
|
-
last_struct["pivot_low"] =
|
309
|
+
last_struct["pivot_high"] = self.toDecimal(data.loc[last_struct["pivot_high_index"], 'high'])
|
310
|
+
last_struct["pivot_low"] = self.toDecimal(data.loc[last_struct["pivot_low_index"], 'low'])
|
449
311
|
|
450
312
|
for i in range(len(data)-1, -1, -1):
|
451
313
|
if check_valid_range:
|
@@ -510,17 +372,12 @@ class SMCStrategyMaker(ThreeLineStrategyMaker):
|
|
510
372
|
next_lows = data['low'].iloc[index+1 :min(len(data),index + period)+1]
|
511
373
|
return all(current_low <= prev_lows) and all(current_low < next_lows)
|
512
374
|
|
513
|
-
def round_price(self,symbol, price):
|
514
|
-
|
515
|
-
return super().round_price_to_tick(price, tick_size)
|
375
|
+
# def round_price(self,symbol, price: Decimal) -> Decimal:
|
376
|
+
# return super().round_price_to_tick(symbol, price)
|
516
377
|
|
517
|
-
def calculate_ce(self,symbol,pivot_high , pivot_low) ->
|
378
|
+
def calculate_ce(self, symbol, pivot_high:Decimal , pivot_low:Decimal) -> Decimal:
|
518
379
|
ce = (pivot_high + pivot_low) / 2
|
519
|
-
return
|
520
|
-
|
521
|
-
def calculate_pe(self,symbol,pivot_high, pivot_low) -> float:
|
522
|
-
pe = (pivot_high + pivot_low) / 2
|
523
|
-
return float(self.round_price(symbol, pe))
|
380
|
+
return self.round_price_to_tick(symbol, ce)
|
524
381
|
|
525
382
|
def reset_all_cache(self, symbol):
|
526
383
|
"""_summary_
|
@@ -568,7 +425,7 @@ class SMCStrategyMaker(ThreeLineStrategyMaker):
|
|
568
425
|
swing_points_length = smc_strategy.get('swing_points_length',10)
|
569
426
|
htf_last_struct = self.detect_struct(htf_df,prd=swing_points_length)
|
570
427
|
htf_last_struct_label = htf_last_struct["struct"]
|
571
|
-
|
428
|
+
precision = self.get_precision_length(symbol)
|
572
429
|
|
573
430
|
if htf_last_struct_label is None:
|
574
431
|
self.logger.debug(f"{symbol} : {htf} 未形成 struct,不下单。{htf_last_struct}。")
|
@@ -586,8 +443,8 @@ class SMCStrategyMaker(ThreeLineStrategyMaker):
|
|
586
443
|
|
587
444
|
|
588
445
|
# 2. HTF 获取最新的两个极值点,设置折价(discount)区和溢价(premium)区
|
589
|
-
pivot_high = htf_last_struct["pivot_high"]
|
590
|
-
pivot_low = htf_last_struct["pivot_low"]
|
446
|
+
pivot_high = self.toDecimal(htf_last_struct["pivot_high"])
|
447
|
+
pivot_low = self.toDecimal(htf_last_struct["pivot_low"])
|
591
448
|
mid_line = self.calculate_ce(symbol,pivot_high,pivot_low)
|
592
449
|
|
593
450
|
# 计算溢价和折价区
|
@@ -602,13 +459,16 @@ class SMCStrategyMaker(ThreeLineStrategyMaker):
|
|
602
459
|
'ce': self.calculate_ce(symbol,mid_line,pivot_low)
|
603
460
|
}
|
604
461
|
|
605
|
-
self.logger.debug(f"{symbol} : {htf} 趋势={htf_last_struct_label}: \
|
462
|
+
self.logger.debug(f"{symbol} : {htf} 趋势={htf_last_struct_label}: \n" \
|
463
|
+
f"pivot_high={pivot_high:.{precision}} pivot_low={pivot_low:.{precision}} mid_line={mid_line:.{precision}}\n" \
|
464
|
+
f"溢价区={premium_box}\n"
|
465
|
+
f"折价区={discount_box}")
|
606
466
|
|
607
467
|
# 3. 根据HTF结构来分析下单位置和止盈位置
|
608
|
-
threshold = 0.0
|
468
|
+
threshold = self.toDecimal(0.0)
|
609
469
|
order_side = side
|
610
470
|
# 获取当前市场价格
|
611
|
-
market_price = ctf_df['close'].iloc[-1]
|
471
|
+
market_price = self.toDecimal(ctf_df['close'].iloc[-1])
|
612
472
|
|
613
473
|
if 'CHoCH' in htf_last_struct_label:
|
614
474
|
"""
|
@@ -661,14 +521,14 @@ class SMCStrategyMaker(ThreeLineStrategyMaker):
|
|
661
521
|
|
662
522
|
|
663
523
|
if threshold == 0.0:
|
664
|
-
self.logger.debug(f"{symbol} : 价格{market_price}不在目标区域,不下单。")
|
524
|
+
self.logger.debug(f"{symbol} : 价格{market_price:.{precision}}不在目标区域,不下单。")
|
665
525
|
# 取消所有未成交订单
|
666
526
|
self.cancel_all_orders(symbol=symbol)
|
667
527
|
return
|
668
528
|
|
669
529
|
|
670
530
|
# 4. 在CTF折价区获取FVG的位置
|
671
|
-
order_price = 0.0
|
531
|
+
order_price = self.toDecimal(0.0)
|
672
532
|
|
673
533
|
if enable_FVG and order_price == 0.0:
|
674
534
|
|
@@ -694,7 +554,7 @@ class SMCStrategyMaker(ThreeLineStrategyMaker):
|
|
694
554
|
if len(fvg_boxes) != 0 and order_price == 0.0:
|
695
555
|
last_fvg_box = fvg_boxes[-1]
|
696
556
|
ce_price = self.calculate_ce(symbol,last_fvg_box['top'],last_fvg_box['bot'])
|
697
|
-
self.logger.info(f"{symbol} : 方向={order_side}, FVG_ce={ce_price} FVG={last_fvg_box} ")
|
557
|
+
self.logger.info(f"{symbol} : 方向={order_side}, FVG_ce={ce_price:.{precision}} FVG={last_fvg_box} ")
|
698
558
|
order_price = ce_price
|
699
559
|
|
700
560
|
# 4. 找OB位置,OB规则孤立高点+实体低点 孤立低点+实体高点
|
@@ -713,7 +573,7 @@ class SMCStrategyMaker(ThreeLineStrategyMaker):
|
|
713
573
|
if len(OB_boxes) != 0 :
|
714
574
|
last_OB_box = OB_boxes[-1]
|
715
575
|
ce_price = self.calculate_ce(symbol,last_OB_box['top'],last_OB_box['bot'])
|
716
|
-
self.logger.info(f"{symbol} : 方向={order_side}, OB_ce={ce_price} , OB={last_OB_box} ")
|
576
|
+
self.logger.info(f"{symbol} : 方向={order_side}, OB_ce={ce_price:.{precision}} , OB={last_OB_box} ")
|
717
577
|
order_price = ce_price
|
718
578
|
|
719
579
|
if order_price == 0.0:
|
@@ -723,7 +583,7 @@ class SMCStrategyMaker(ThreeLineStrategyMaker):
|
|
723
583
|
|
724
584
|
latest_order_price = self.place_order_prices.get(symbol,0.0)
|
725
585
|
if order_price == latest_order_price:
|
726
|
-
self.logger.debug(f"{symbol} : 下单价格 {order_price} 未变化,不进行下单。")
|
586
|
+
self.logger.debug(f"{symbol} : 下单价格 {order_price:.{precision}} 未变化,不进行下单。")
|
727
587
|
return
|
728
588
|
|
729
589
|
|
@@ -731,7 +591,7 @@ class SMCStrategyMaker(ThreeLineStrategyMaker):
|
|
731
591
|
self.cancel_all_orders(symbol=symbol)
|
732
592
|
self.place_order(symbol=symbol, price=order_price, side=order_side, pair_config=pair_config)
|
733
593
|
self.place_order_prices[symbol] = order_price # 记录下单价格,过滤重复下单
|
734
|
-
self.logger.debug(f"{symbol} : {side}, 下单价格 {order_price}")
|
594
|
+
self.logger.debug(f"{symbol} : {side}, 下单价格 {order_price:.{precision}}")
|
735
595
|
|
736
596
|
|
737
597
|
except KeyboardInterrupt:
|
maker/ThreeLineStrategyMaker.py
CHANGED
@@ -4,25 +4,31 @@ import ccxt
|
|
4
4
|
import traceback
|
5
5
|
import requests
|
6
6
|
import pandas as pd
|
7
|
+
from decimal import Decimal
|
7
8
|
|
8
9
|
from concurrent.futures import ThreadPoolExecutor, as_completed
|
9
10
|
|
10
11
|
|
12
|
+
|
11
13
|
class ThreeLineStrategyMaker:
|
12
|
-
def __init__(self, config, platform_config, feishu_webhook=None,logger=None):
|
14
|
+
def __init__(self, config, platform_config, common_config, feishu_webhook=None,logger=None):
|
13
15
|
|
14
16
|
self.g_config = config
|
15
17
|
self.feishu_webhook = feishu_webhook
|
16
|
-
self.
|
18
|
+
self.common_config = common_config
|
19
|
+
self.strategy_config = self.g_config.get('strategy', {})
|
20
|
+
|
17
21
|
self.trading_pairs_config = self.g_config.get('tradingPairs', {})
|
22
|
+
|
23
|
+
self.leverage_value = self.strategy_config.get('leverage', 20)
|
24
|
+
self.is_demo_trading = self.common_config.get('is_demo_trading', 1) # live trading: 0, demo trading: 1
|
25
|
+
|
18
26
|
self.highest_total_profit = 0 # 记录最高总盈利
|
19
|
-
self.leverage_value = self.g_config.get('leverage', 2)
|
20
|
-
self.is_demo_trading = self.g_config.get('is_demo_trading', 1) # live trading: 0, demo trading: 1
|
21
27
|
# self.instrument_info_dict = {}
|
22
28
|
self.cross_directions = {} # 持仓期间,存储每个交易对的交叉方向
|
23
29
|
proxies = {
|
24
|
-
"http": self.
|
25
|
-
"https": self.
|
30
|
+
"http": self.common_config.get('proxy', "http://localhost:7890"),
|
31
|
+
"https": self.common_config.get('proxy', "http://localhost:7890")
|
26
32
|
}
|
27
33
|
|
28
34
|
# 配置交易所
|
@@ -35,22 +41,39 @@ class ThreeLineStrategyMaker:
|
|
35
41
|
'options': {'defaultType': 'future'},
|
36
42
|
'proxies': proxies
|
37
43
|
})
|
38
|
-
|
39
|
-
|
40
44
|
|
41
45
|
self.logger = logger
|
42
46
|
self.position_mode = self.get_position_mode() # 获取持仓模式
|
43
47
|
|
48
|
+
def format_price(self, symbol, price:Decimal) -> str:
|
49
|
+
precision = self.get_precision_length(symbol)
|
50
|
+
return f"{price:.{precision}f}"
|
51
|
+
|
52
|
+
@staticmethod
|
53
|
+
def toDecimal(value):
|
54
|
+
return Decimal(str(value))
|
55
|
+
|
56
|
+
|
44
57
|
def getMarket(self,symbol):
|
45
58
|
self.exchange.load_markets()
|
46
59
|
return self.exchange.market(symbol)
|
47
60
|
|
48
|
-
def get_tick_size(self,symbol):
|
49
|
-
|
50
|
-
|
51
|
-
|
61
|
+
def get_tick_size(self,symbol) -> Decimal:
|
62
|
+
|
63
|
+
try:
|
64
|
+
market = self.getMarket(symbol)
|
65
|
+
if market and 'precision' in market and 'price' in market['precision']:
|
66
|
+
|
67
|
+
return self.toDecimal(market['precision']['price'])
|
68
|
+
else:
|
69
|
+
# self.logger.error(f"{symbol}: 无法从市场数据中获取价格精度")
|
70
|
+
# return self.toDecimal("0.00001") # 返回默认精度
|
71
|
+
raise ValueError(f"{symbol}: 无法从市场数据中获取价格精度")
|
72
|
+
except Exception as e:
|
73
|
+
self.logger.error(f"{symbol}: 获取市场价格精度时发生错误: {str(e)}")
|
74
|
+
self.send_feishu_notification(f"{symbol}: 获取市场价格精度时发生错误: {str(e)}")
|
52
75
|
|
53
|
-
def convert_contract(self, symbol, amount, price:
|
76
|
+
def convert_contract(self, symbol, amount, price:Decimal, direction='cost_to_contract'):
|
54
77
|
"""
|
55
78
|
进行合约与币的转换
|
56
79
|
:param symbol: 交易对符号,如 'BTC/USDT:USDT'
|
@@ -60,11 +83,12 @@ class ThreeLineStrategyMaker:
|
|
60
83
|
"""
|
61
84
|
|
62
85
|
# 获取合约规模
|
63
|
-
market_contractSize = self.getMarket(symbol)['contractSize']
|
86
|
+
market_contractSize = self.toDecimal(self.getMarket(symbol)['contractSize'])
|
87
|
+
amount = self.toDecimal(amount)
|
64
88
|
if direction == 'amount_to_contract':
|
65
|
-
contract_size =
|
89
|
+
contract_size = amount / market_contractSize
|
66
90
|
elif direction == 'cost_to_contract':
|
67
|
-
contract_size =
|
91
|
+
contract_size = amount / price / market_contractSize
|
68
92
|
else:
|
69
93
|
raise Exception(f"{symbol}:{direction} 是无效的转换方向,请输入 'amount_to_contract' 或 'cost_to_contract'。")
|
70
94
|
|
@@ -74,7 +98,7 @@ class ThreeLineStrategyMaker:
|
|
74
98
|
# 获取价格精度
|
75
99
|
def get_precision_length(self,symbol) -> int:
|
76
100
|
tick_size = self.get_tick_size(symbol)
|
77
|
-
return len(f"{tick_size:.
|
101
|
+
return len(f"{tick_size:.15f}".rstrip('0').split('.')[1]) if '.' in f"{tick_size:.15f}" else 0
|
78
102
|
|
79
103
|
def get_position_mode(self):
|
80
104
|
try:
|
@@ -129,13 +153,13 @@ class ThreeLineStrategyMaker:
|
|
129
153
|
else:
|
130
154
|
raise ValueError("Unexpected response structure or missing 'last' key")
|
131
155
|
|
132
|
-
def round_price_to_tick(self,
|
156
|
+
def round_price_to_tick(self, symbol, price: Decimal) -> Decimal:
|
157
|
+
tick_size = self.get_tick_size(symbol)
|
133
158
|
# 计算 tick_size 的小数位数
|
134
|
-
tick_decimals =
|
135
|
-
|
159
|
+
tick_decimals = self.get_precision_length(symbol)
|
136
160
|
# 调整价格为 tick_size 的整数倍
|
137
161
|
adjusted_price = round(price / tick_size) * tick_size
|
138
|
-
return f"{adjusted_price:.{tick_decimals}f}"
|
162
|
+
return self.toDecimal(f"{adjusted_price:.{tick_decimals}f}")
|
139
163
|
|
140
164
|
def get_historical_klines(self,symbol, bar='1m', limit=241):
|
141
165
|
# response = market_api.get_candlesticks(instId, bar=bar, limit=limit)
|
@@ -214,7 +238,7 @@ class ThreeLineStrategyMaker:
|
|
214
238
|
|
215
239
|
return diff
|
216
240
|
|
217
|
-
def calculate_place_order_price(self, symbol,side,base_price, amplitude_limit, offset=1) ->
|
241
|
+
def calculate_place_order_price(self, symbol,side,base_price:Decimal, amplitude_limit: float, offset=1) -> Decimal:
|
218
242
|
"""
|
219
243
|
计算开仓价格
|
220
244
|
Args:
|
@@ -227,15 +251,16 @@ class ThreeLineStrategyMaker:
|
|
227
251
|
place_order_price: 开仓价格
|
228
252
|
"""
|
229
253
|
tick_size = self.get_tick_size(symbol)
|
254
|
+
precision= self.get_precision_length(symbol)
|
230
255
|
place_order_price = None
|
231
256
|
# 计算止盈价格,用市场价格(取持仓期间历史最高)减去开仓价格的利润,再乘以不同阶段的止盈百分比。
|
232
257
|
|
233
258
|
if side == 'buy':
|
234
|
-
place_order_price = base_price * (1- amplitude_limit/100) - offset * tick_size
|
259
|
+
place_order_price = base_price * (1- self.toDecimal(amplitude_limit)/100) - offset * tick_size
|
235
260
|
else:
|
236
|
-
place_order_price = base_price * (1 + amplitude_limit/100) + offset * tick_size
|
237
|
-
self.logger.debug(f"++++ {symbol} 下单价格: {place_order_price:.
|
238
|
-
return
|
261
|
+
place_order_price = base_price * (1 + self.toDecimal(amplitude_limit)/100) + offset * tick_size
|
262
|
+
self.logger.debug(f"++++ {symbol} 下单价格: {place_order_price:.{precision}f} 方向 {side} 基准价格{base_price:.{precision}} 振幅限制 {amplitude_limit} ")
|
263
|
+
return self.round_price_to_tick(place_order_price,tick_size)
|
239
264
|
|
240
265
|
# 定义根据均线斜率判断 K 线方向的函数: 0 空 1 多 -1 平
|
241
266
|
def judge_k_line_direction(self, symbol, ema: pd.Series, klines, period=3) -> int:
|
@@ -427,18 +452,18 @@ class ThreeLineStrategyMaker:
|
|
427
452
|
self.logger.warning(f"{symbol} 检查持仓失败,正在进行第{retry_count}次重试: {str(e)}")
|
428
453
|
time.sleep(0.1) # 重试前等待0.1秒
|
429
454
|
|
430
|
-
def place_order(self,symbol, price, amount_usdt, side,order_type='limit'):
|
431
|
-
|
432
|
-
|
433
|
-
|
434
|
-
|
435
|
-
|
436
|
-
|
437
|
-
|
438
|
-
|
439
|
-
|
440
|
-
|
441
|
-
adjusted_price = self.
|
455
|
+
def place_order(self, symbol, price: Decimal, amount_usdt, side, order_type='limit'):
|
456
|
+
"""
|
457
|
+
下单
|
458
|
+
Args:
|
459
|
+
symbol: 交易对
|
460
|
+
price: 下单价格
|
461
|
+
amount_usdt: 下单金额
|
462
|
+
side: 下单方向
|
463
|
+
order_type: 订单类型
|
464
|
+
"""
|
465
|
+
# 格式化价格
|
466
|
+
adjusted_price = self.format_price(symbol, price)
|
442
467
|
|
443
468
|
if amount_usdt > 0:
|
444
469
|
if side == 'buy':
|
@@ -448,7 +473,7 @@ class ThreeLineStrategyMaker:
|
|
448
473
|
# 设置杠杆
|
449
474
|
self.set_leverage(symbol=symbol, leverage=self.leverage_value, mgnMode='isolated',posSide=pos_side)
|
450
475
|
# 20250220 SWAP类型计算合约数量
|
451
|
-
contract_size = self.convert_contract(symbol=symbol, price = adjusted_price ,amount=amount_usdt)
|
476
|
+
contract_size = self.convert_contract(symbol=symbol, price = self.toDecimal(adjusted_price) ,amount=amount_usdt)
|
452
477
|
|
453
478
|
params = {
|
454
479
|
|
@@ -456,7 +481,7 @@ class ThreeLineStrategyMaker:
|
|
456
481
|
"side": side,
|
457
482
|
"ordType": order_type,
|
458
483
|
"sz": contract_size,
|
459
|
-
"px":
|
484
|
+
"px": adjusted_price
|
460
485
|
}
|
461
486
|
|
462
487
|
# 模拟盘(demo_trading)需要 posSide
|
@@ -470,7 +495,7 @@ class ThreeLineStrategyMaker:
|
|
470
495
|
'side': side,
|
471
496
|
'type': 'limit',
|
472
497
|
'amount': contract_size,
|
473
|
-
'price':
|
498
|
+
'price': adjusted_price,
|
474
499
|
'params': params
|
475
500
|
}
|
476
501
|
# 使用ccxt创建订单
|
@@ -486,7 +511,9 @@ class ThreeLineStrategyMaker:
|
|
486
511
|
)
|
487
512
|
# self.logger.debug(f"{symbol} ++ Order placed rs : {order_result}")
|
488
513
|
except Exception as e:
|
489
|
-
|
514
|
+
error_message = f"{symbol} Failed to place order: {e}"
|
515
|
+
self.logger.error(error_message)
|
516
|
+
self.send_feishu_notification(error_message)
|
490
517
|
self.logger.info(f"--------- ++ {symbol} Order placed done! --------")
|
491
518
|
|
492
519
|
def cancel_all_orders(self, symbol):
|
@@ -514,7 +541,9 @@ class ThreeLineStrategyMaker:
|
|
514
541
|
except Exception as e:
|
515
542
|
retry_count += 1
|
516
543
|
if retry_count == max_retries:
|
517
|
-
|
544
|
+
error_message = f"{symbol} 取消挂单失败(重试{retry_count}次): {str(e)}"
|
545
|
+
self.logger.error(error_message)
|
546
|
+
self.send_feishu_notification(error_message)
|
518
547
|
return False
|
519
548
|
self.logger.warning(f"{symbol} 取消挂单失败,正在进行第{retry_count}次重试: {str(e)}")
|
520
549
|
time.sleep(0.1) # 重试前等待0.1秒
|
@@ -616,8 +645,8 @@ class ThreeLineStrategyMaker:
|
|
616
645
|
df_3 = pd.DataFrame(klines[-4:-1], columns=['timestamp', 'open', 'high', 'low', 'close', 'volume'])
|
617
646
|
low_prices = df_3['low']
|
618
647
|
high_prices = df_3['high']
|
619
|
-
max_high = high_prices.max()
|
620
|
-
min_low = low_prices.min()
|
648
|
+
max_high = self.toDecimal(high_prices.max())
|
649
|
+
min_low = self.toDecimal(low_prices.min())
|
621
650
|
|
622
651
|
# 计算当前 振幅是否超过amplitude_limit
|
623
652
|
long_amount_usdt = pair_config.get('long_amount_usdt', 5)
|
@@ -669,7 +698,19 @@ class ThreeLineStrategyMaker:
|
|
669
698
|
self.send_feishu_notification(error_message)
|
670
699
|
|
671
700
|
self.logger.info("-" * 60)
|
672
|
-
|
701
|
+
|
702
|
+
def get_pair_config(self,symbol):
|
703
|
+
# 获取交易对特定配置,如果没有则使用全局策略配置
|
704
|
+
pair_config = self.trading_pairs_config.get(symbol, {})
|
705
|
+
|
706
|
+
# 使用字典推导式合并配置,trading_pairs_config优先级高于strategy_config
|
707
|
+
pair_config = {
|
708
|
+
**self.strategy_config, # 基础配置
|
709
|
+
**pair_config # 交易对特定配置会覆盖基础配置
|
710
|
+
}
|
711
|
+
return pair_config
|
712
|
+
|
713
|
+
|
673
714
|
def monitor_klines(self):
|
674
715
|
symbols = list(self.trading_pairs_config.keys()) # 获取所有币对的ID
|
675
716
|
batch_size = 5 # 每批处理的数量
|
@@ -678,8 +719,6 @@ class ThreeLineStrategyMaker:
|
|
678
719
|
for i in range(0, len(symbols), batch_size):
|
679
720
|
batch = symbols[i:i + batch_size]
|
680
721
|
with ThreadPoolExecutor(max_workers=batch_size) as executor:
|
681
|
-
futures = [executor.submit(self.process_pair, symbol,self.
|
722
|
+
futures = [executor.submit(self.process_pair, symbol,self.get_pair_config(symbol)) for symbol in batch]
|
682
723
|
for future in as_completed(futures):
|
683
724
|
future.result() # Raise any exceptions caught during execution
|
684
|
-
|
685
|
-
# time.sleep(self.monitor_interval)
|
maker/main.py
CHANGED
@@ -1,35 +1,15 @@
|
|
1
1
|
import logging
|
2
|
+
import logging.config
|
2
3
|
import yaml
|
3
|
-
from logging.handlers import TimedRotatingFileHandler
|
4
4
|
from apscheduler.triggers.interval import IntervalTrigger
|
5
5
|
from apscheduler.schedulers.blocking import BlockingScheduler
|
6
6
|
from datetime import datetime
|
7
7
|
from pyfiglet import Figlet
|
8
8
|
|
9
|
-
from maker.WickReversalStrategyMaker import WickReversalStrategyMaker
|
10
9
|
from maker.ThreeLineStrategyMaker import ThreeLineStrategyMaker
|
11
|
-
# from maker.MACDStrategyMaker import MACDStrategyMaker
|
12
10
|
from maker.SMCStrategyMaker import SMCStrategyMaker
|
13
11
|
from maker.BestFVGStrategyMaker import BestFVGStrategyMaker
|
14
12
|
|
15
|
-
def build_logger(log_config) -> logging.Logger:
|
16
|
-
# 配置日志
|
17
|
-
|
18
|
-
log_file = log_config["file"]
|
19
|
-
logger = logging.getLogger(__name__)
|
20
|
-
logger.setLevel(log_config["level"])
|
21
|
-
|
22
|
-
file_handler = TimedRotatingFileHandler(log_file, when='midnight', interval=1, backupCount=7, encoding='utf-8')
|
23
|
-
file_handler.suffix = "%Y-%m-%d"
|
24
|
-
formatter = logging.Formatter('%(asctime)s - %(name)s - %(lineno)d - %(levelname)s - %(message)s')
|
25
|
-
file_handler.setFormatter(formatter)
|
26
|
-
logger.addHandler(file_handler)
|
27
|
-
|
28
|
-
console_handler = logging.StreamHandler()
|
29
|
-
console_handler.setFormatter(formatter)
|
30
|
-
logger.addHandler(console_handler)
|
31
|
-
|
32
|
-
return logger
|
33
13
|
|
34
14
|
def read_config_file(file_path):
|
35
15
|
try:
|
@@ -51,36 +31,39 @@ def run_bot(bot, logger):
|
|
51
31
|
logger.error(f"执行任务时发生错误: {str(e)}", exc_info=True)
|
52
32
|
|
53
33
|
def main():
|
34
|
+
|
54
35
|
import importlib.metadata
|
36
|
+
package_name = __package__ or "openfund-maker"
|
55
37
|
version = importlib.metadata.version("openfund-maker")
|
56
38
|
|
57
39
|
maker_config_path = 'maker_config.yaml'
|
58
40
|
config_data = read_config_file(maker_config_path)
|
59
41
|
|
60
|
-
|
61
|
-
|
62
|
-
logger = build_logger(config_data["Logger"])
|
63
|
-
package_name = __package__ or "maker"
|
64
|
-
|
65
|
-
|
66
|
-
maker = config_data.get('actived_maker', 'MACDStrategyMaker')
|
42
|
+
logging.config.dictConfig(config_data["Logger"])
|
43
|
+
logger = logging.getLogger("openfund-maker")
|
67
44
|
|
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...")
|
68
52
|
|
53
|
+
platform_config = config_data['platform']['okx']
|
69
54
|
# 根据配置动态创建策略实例
|
70
55
|
strategy_class = globals()[maker]
|
71
|
-
bot = strategy_class(config_data, platform_config, feishu_webhook=feishu_webhook_url, logger=logger)
|
56
|
+
bot = strategy_class(config_data, platform_config, common_config, feishu_webhook=feishu_webhook_url, logger=logger)
|
72
57
|
|
73
|
-
logger.info(f" ++ {package_name}.{maker}:{version} is doing...")
|
74
58
|
|
75
59
|
# 获取计划配置
|
76
|
-
schedule_config =
|
60
|
+
schedule_config = common_config.get('schedule', {})
|
77
61
|
if schedule_config.get('enabled', False):
|
78
62
|
scheduler = BlockingScheduler()
|
79
63
|
|
80
64
|
# 设置每5分钟执行一次的任务,从整点开始
|
81
65
|
monitor_interval = int(schedule_config.get('monitor_interval', 4))
|
82
|
-
|
83
|
-
logger.info(f"\n{f.renderText("OpenFund Maker")}")
|
66
|
+
|
84
67
|
# 计算下一个整点分钟
|
85
68
|
now = datetime.now()
|
86
69
|
# 将当前时间的秒和微秒设置为0
|
@@ -0,0 +1,15 @@
|
|
1
|
+
maker/BestFVGStrategyMaker.py,sha256=xIiDIvMw8RwHayUuz5Won3stSG9BxdYIQgeXDok_SqM,12336
|
2
|
+
maker/MACDStrategyMaker.py,sha256=WX8wqpF9h5W4WclN1NjZ_Bur7KFi_aMTvacfLyHzEcI,12681
|
3
|
+
maker/SMCStrategyMaker.py,sha256=hkDqymWnuyYDo1gTYY_uyO4H4yOwCw8NBTM9RcfLRPc,28780
|
4
|
+
maker/ThreeLineStrategyMaker.py,sha256=jCdyTvI9oTMixCM0tQF9D_-6ig0-bYstKuRFECE4deE,32099
|
5
|
+
maker/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
6
|
+
maker/history_code/WickReversalStrategyMaker.py,sha256=7DqPDVJot4EM0_lSAcFAHrR9rNvkIds9KLMoDOiAHEc,17486
|
7
|
+
maker/history_code/config.py,sha256=YPxghO5i0vgRg9Cja8kGj9O7pgSbbtzOgf3RexqXXwY,1188
|
8
|
+
maker/history_code/okxapi.py,sha256=_9G0U_o0ZC8NxaT6PqpiLgxBm9gPobC9PsFHZE1c5w0,553
|
9
|
+
maker/history_code/zhen.py.bak,sha256=HNkrQbJts8G9umE9chEFsc0cLQApcM9KOVNMYPpkBXM,10918
|
10
|
+
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,,
|
maker/main_m.py
DELETED
@@ -1,378 +0,0 @@
|
|
1
|
-
import time
|
2
|
-
import json
|
3
|
-
import logging
|
4
|
-
import requests
|
5
|
-
import pandas as pd
|
6
|
-
|
7
|
-
import okx.PublicData as PublicData
|
8
|
-
import okx.Trade as Trade
|
9
|
-
import okx.MarketData as MarketData
|
10
|
-
import okx.Account as Account
|
11
|
-
|
12
|
-
from concurrent.futures import ThreadPoolExecutor, as_completed
|
13
|
-
from logging.handlers import TimedRotatingFileHandler
|
14
|
-
|
15
|
-
# import app.okx_buou.Trade_api as TradeAPI
|
16
|
-
# import app.okx_buou.Public_api as PublicAPI
|
17
|
-
# import app.okx_buou.Market_api as MarketAPI
|
18
|
-
# import app.okx_buou.Account_api as AccountAPI
|
19
|
-
|
20
|
-
import os
|
21
|
-
openfund_config_path = os.getenv('wick_reversal_config_path','config_okx.json')
|
22
|
-
# 读取配置文件
|
23
|
-
with open(openfund_config_path, 'r') as f:
|
24
|
-
config = json.load(f)
|
25
|
-
|
26
|
-
# 提取配置
|
27
|
-
okx_config = config['okx']
|
28
|
-
trading_pairs_config = config.get('tradingPairs', {})
|
29
|
-
monitor_interval = config.get('monitor_interval', 60) # 默认60秒
|
30
|
-
feishu_webhook = config.get('feishu_webhook', '')
|
31
|
-
leverage_value = config.get('leverage', 10)
|
32
|
-
api_key = okx_config["apiKey"]
|
33
|
-
secret_key = okx_config["secret"]
|
34
|
-
passphrase = okx_config["password"]
|
35
|
-
flag = "0" # live trading: 0, demo trading: 1
|
36
|
-
|
37
|
-
public_api = PublicData.PublicAPI(api_key, secret_key, passphrase, False, flag)
|
38
|
-
trade_api = Trade.TradeAPI(api_key, secret_key, passphrase, False, flag)
|
39
|
-
market_api = MarketData.MarketAPI(api_key, secret_key, passphrase, False, flag)
|
40
|
-
account_api = Account.AccountAPI(api_key, secret_key, passphrase, False, flag)
|
41
|
-
# trade_api = TradeAPI.TradeAPI(okx_config["apiKey"], okx_config["secret"], okx_config["password"], False, flag)
|
42
|
-
# market_api = MarketAPI.MarketAPI(okx_config["apiKey"], okx_config["secret"], okx_config["password"], False, flag)
|
43
|
-
# public_api = PublicAPI.PublicAPI(okx_config["apiKey"], okx_config["secret"], okx_config["password"], False, flag)
|
44
|
-
# account_api = AccountAPI.AccountAPI(okx_config["apiKey"], okx_config["secret"], okx_config["password"], False, flag)
|
45
|
-
|
46
|
-
log_file = "log/okx.log"
|
47
|
-
logger = logging.getLogger(__name__)
|
48
|
-
logger.setLevel(logging.DEBUG)
|
49
|
-
|
50
|
-
file_handler = TimedRotatingFileHandler(log_file, when='midnight', interval=1, backupCount=7, encoding='utf-8')
|
51
|
-
file_handler.suffix = "%Y-%m-%d"
|
52
|
-
formatter = logging.Formatter('%(asctime)s - %(name)s -%(lineno)d - %(levelname)s - %(message)s')
|
53
|
-
file_handler.setFormatter(formatter)
|
54
|
-
logger.addHandler(file_handler)
|
55
|
-
|
56
|
-
console_handler = logging.StreamHandler()
|
57
|
-
console_handler.setFormatter(formatter)
|
58
|
-
logger.addHandler(console_handler)
|
59
|
-
|
60
|
-
instrument_info_dict = {}
|
61
|
-
|
62
|
-
def fetch_and_store_all_instruments(instType='SWAP'):
|
63
|
-
try:
|
64
|
-
logger.info(f"Fetching all instruments for type: {instType}")
|
65
|
-
|
66
|
-
response = public_api.get_instruments(instType=instType)
|
67
|
-
# response = public_api.get_instruments(instType=instType)
|
68
|
-
# logger.debug(f"data: {response['data']}")
|
69
|
-
if 'data' in response and len(response['data']) > 0:
|
70
|
-
instrument_info_dict.clear()
|
71
|
-
for instrument in response['data']:
|
72
|
-
instId = instrument['instId']
|
73
|
-
instrument_info_dict[instId] = instrument
|
74
|
-
# logger.debug(f"Stored instrument: {instId}")
|
75
|
-
else:
|
76
|
-
raise ValueError("Unexpected response structure or no instrument data available")
|
77
|
-
except Exception as e:
|
78
|
-
logger.error(f"Error fetching instruments: {e}")
|
79
|
-
raise
|
80
|
-
|
81
|
-
def send_feishu_notification(message):
|
82
|
-
if feishu_webhook:
|
83
|
-
headers = {'Content-Type': 'application/json'}
|
84
|
-
data = {"msg_type": "text", "content": {"text": message}}
|
85
|
-
response = requests.post(feishu_webhook, headers=headers, json=data)
|
86
|
-
if response.status_code == 200:
|
87
|
-
logger.debug("飞书通知发送成功")
|
88
|
-
else:
|
89
|
-
logger.error(f"飞书通知发送失败: {response.text}")
|
90
|
-
|
91
|
-
def get_close_price(instId):
|
92
|
-
'''
|
93
|
-
bar =
|
94
|
-
时间粒度,默认值1m
|
95
|
-
如 [1m/3m/5m/15m/30m/1H/2H/4H]
|
96
|
-
香港时间开盘价k线:[6H/12H/1D/2D/3D/1W/1M/3M]
|
97
|
-
UTC时间开盘价k线:[/6Hutc/12Hutc/1Dutc/2Dutc/3Dutc/1Wutc/1Mutc/3Mutc]
|
98
|
-
'''
|
99
|
-
response = market_api.get_candlesticks(instId=instId,bar='1m')
|
100
|
-
if 'data' in response and len(response['data']) > 0:
|
101
|
-
close_price = response['data'][0][4]
|
102
|
-
return float(close_price)
|
103
|
-
else:
|
104
|
-
raise ValueError("Unexpected response structure or missing 'c' value")
|
105
|
-
|
106
|
-
|
107
|
-
def get_mark_price(instId):
|
108
|
-
response = market_api.get_ticker(instId)
|
109
|
-
if 'data' in response and len(response['data']) > 0:
|
110
|
-
last_price = response['data'][0]['last']
|
111
|
-
return float(last_price)
|
112
|
-
else:
|
113
|
-
raise ValueError("Unexpected response structure or missing 'last' key")
|
114
|
-
|
115
|
-
def round_price_to_tick(price, tick_size):
|
116
|
-
# 计算 tick_size 的小数位数
|
117
|
-
tick_decimals = len(f"{tick_size:.10f}".rstrip('0').split('.')[1]) if '.' in f"{tick_size:.10f}" else 0
|
118
|
-
|
119
|
-
# 调整价格为 tick_size 的整数倍
|
120
|
-
adjusted_price = round(price / tick_size) * tick_size
|
121
|
-
return f"{adjusted_price:.{tick_decimals}f}"
|
122
|
-
|
123
|
-
def get_historical_klines(instId, bar='1m', limit=241):
|
124
|
-
response = market_api.get_candlesticks(instId, bar=bar, limit=limit)
|
125
|
-
if 'data' in response and len(response['data']) > 0:
|
126
|
-
return response['data']
|
127
|
-
else:
|
128
|
-
raise ValueError("Unexpected response structure or missing candlestick data")
|
129
|
-
|
130
|
-
def calculate_atr(klines, period=60):
|
131
|
-
trs = []
|
132
|
-
for i in range(1, len(klines)):
|
133
|
-
high = float(klines[i][2])
|
134
|
-
low = float(klines[i][3])
|
135
|
-
prev_close = float(klines[i-1][4])
|
136
|
-
tr = max(high - low, abs(high - prev_close), abs(low - prev_close))
|
137
|
-
trs.append(tr)
|
138
|
-
atr = sum(trs[-period:]) / period
|
139
|
-
return atr
|
140
|
-
|
141
|
-
def calculate_ema_pandas(data, period):
|
142
|
-
"""
|
143
|
-
使用 pandas 计算 EMA
|
144
|
-
:param 收盘价列表
|
145
|
-
:param period: EMA 周期
|
146
|
-
:return: EMA 值
|
147
|
-
"""
|
148
|
-
df = pd.Series(data)
|
149
|
-
ema = df.ewm(span=period, adjust=False).mean()
|
150
|
-
return ema.iloc[-1] # 返回最后一个 EMA 值
|
151
|
-
|
152
|
-
|
153
|
-
def calculate_average_amplitude(klines, period=60):
|
154
|
-
amplitudes = []
|
155
|
-
for i in range(len(klines) - period, len(klines)):
|
156
|
-
high = float(klines[i][2])
|
157
|
-
low = float(klines[i][3])
|
158
|
-
close = float(klines[i][4])
|
159
|
-
amplitude = ((high - low) / close) * 100
|
160
|
-
amplitudes.append(amplitude)
|
161
|
-
average_amplitude = sum(amplitudes) / len(amplitudes)
|
162
|
-
return average_amplitude
|
163
|
-
|
164
|
-
def cancel_all_orders(instId):
|
165
|
-
open_orders = trade_api.get_order_list(instId=instId, state='live')
|
166
|
-
order_ids = [order['ordId'] for order in open_orders['data']]
|
167
|
-
for ord_id in order_ids:
|
168
|
-
trade_api.cancel_order(instId=instId, ordId=ord_id)
|
169
|
-
logger.info(f"{instId}挂单取消成功.")
|
170
|
-
|
171
|
-
def set_leverage(instId, leverage, mgnMode='isolated',posSide=None):
|
172
|
-
try:
|
173
|
-
body = {
|
174
|
-
"instId": instId,
|
175
|
-
"lever": str(leverage),
|
176
|
-
"mgnMode": mgnMode
|
177
|
-
}
|
178
|
-
# 模拟盘需要控制 posSide
|
179
|
-
if flag =='1' and mgnMode == 'isolated' and posSide:
|
180
|
-
body["posSide"] = posSide
|
181
|
-
logger.debug(f"Leverage set parameter is:{body}")
|
182
|
-
response = account_api.set_leverage(**body)
|
183
|
-
if response['code'] == '0':
|
184
|
-
logger.debug(f"Leverage set to {leverage}x for {instId} with mgnMode: {mgnMode}")
|
185
|
-
else:
|
186
|
-
logger.error(f"Failed to set leverage: {response['msg']}")
|
187
|
-
except Exception as e:
|
188
|
-
logger.error(f"Error setting leverage: {e}")
|
189
|
-
|
190
|
-
def place_order(instId, price, amount_usdt, side):
|
191
|
-
if instId not in instrument_info_dict:
|
192
|
-
logger.error(f"Instrument {instId} not found in instrument info dictionary")
|
193
|
-
return
|
194
|
-
tick_size = float(instrument_info_dict[instId]['tickSz'])
|
195
|
-
adjusted_price = round_price_to_tick(price, tick_size)
|
196
|
-
# response = public_api.convert_contract_coin(type='1', instId=instId, sz=str(amount_usdt), px=str(adjusted_price), unit='usdt', opType='open')
|
197
|
-
|
198
|
-
# https://www.okx.com/docs-v5/zh/#public-data-rest-api-unit-convert
|
199
|
-
'''
|
200
|
-
type 转换类型
|
201
|
-
1:币转张
|
202
|
-
2:张转币
|
203
|
-
默认为1
|
204
|
-
'''
|
205
|
-
|
206
|
-
response = public_api.get_convert_contract_coin(type='1', instId=instId, sz=str(amount_usdt), px=str(adjusted_price), unit='usdt')
|
207
|
-
if response['code'] == '0':
|
208
|
-
sz = response['data'][0]['sz']
|
209
|
-
if float(sz) > 0:
|
210
|
-
|
211
|
-
if side == 'buy':
|
212
|
-
pos_side = 'long'
|
213
|
-
else:
|
214
|
-
pos_side = 'short'
|
215
|
-
|
216
|
-
set_leverage(instId=instId, leverage=leverage_value, mgnMode='isolated',posSide=pos_side)
|
217
|
-
|
218
|
-
|
219
|
-
params = {
|
220
|
-
"instId": instId,
|
221
|
-
"tdMode": 'isolated',
|
222
|
-
"side": side,
|
223
|
-
"ordType": 'limit',
|
224
|
-
"sz": sz,
|
225
|
-
"px": str(adjusted_price)
|
226
|
-
}
|
227
|
-
# 模拟盘需要控制 posSide
|
228
|
-
if flag == 1 :
|
229
|
-
params["posSide"] = pos_side
|
230
|
-
|
231
|
-
logger.info(f"Order placed params: {params}")
|
232
|
-
order_result = trade_api.place_order(
|
233
|
-
**params
|
234
|
-
# instId=instId,
|
235
|
-
# #tdMode='isolated',
|
236
|
-
# tdMode='cross',# 保证金模式:isolated:逐仓 ;cross:全仓
|
237
|
-
# side=side, # 订单方向 buy:买, sell:卖
|
238
|
-
# posSide=pos_side, #持仓方向 在开平仓模式下必填,且仅可选择 long 或 short。 仅适用交割、永续。
|
239
|
-
# ordType='limit',
|
240
|
-
# sz=sz,
|
241
|
-
# px=str(adjusted_price)
|
242
|
-
)
|
243
|
-
logger.debug(f"Order placed: {order_result}")
|
244
|
-
else:
|
245
|
-
logger.info(f"{instId}计算出的合约张数太小,无法下单。")
|
246
|
-
else:
|
247
|
-
logger.info(f"{instId}转换失败: {response['msg']}")
|
248
|
-
send_feishu_notification(f"{instId}转换失败: {response['msg']}")
|
249
|
-
logger.info(f"------------------ {instId} Order placed done! ------------------")
|
250
|
-
def check_position(instId, instType='SWAP') -> bool:
|
251
|
-
"""
|
252
|
-
检查指定交易对是否有持仓
|
253
|
-
|
254
|
-
Args:
|
255
|
-
instType: 交易对类型 SPOT、SWAP、FUTURES
|
256
|
-
instId: 交易对ID
|
257
|
-
|
258
|
-
Returns:
|
259
|
-
bool: 是否有持仓
|
260
|
-
"""
|
261
|
-
try:
|
262
|
-
|
263
|
-
positions = account_api.get_positions(instType=instType)
|
264
|
-
if positions and 'data' in positions and len(positions['data']) > 0:
|
265
|
-
logger.debug(f"{instId} 有持仓,{positions['data']}")
|
266
|
-
return True
|
267
|
-
return False
|
268
|
-
except Exception as e:
|
269
|
-
logger.error(f"检查持仓失败 {instId}: {str(e)}")
|
270
|
-
return False
|
271
|
-
|
272
|
-
|
273
|
-
# 处理交易对
|
274
|
-
def process_pair(instId, pair_config):
|
275
|
-
if check_position(instId,instType='SWAP'):
|
276
|
-
logger.info(f"{instId} 有持仓,不下单!")
|
277
|
-
return
|
278
|
-
try:
|
279
|
-
use_market_price = pair_config.get('use_market_price', 1)
|
280
|
-
if use_market_price == 1 :
|
281
|
-
mark_price = get_mark_price(instId)
|
282
|
-
else :
|
283
|
-
mark_price = get_close_price(instId) # 替换成上周期的收盘价格
|
284
|
-
klines = get_historical_klines(instId)
|
285
|
-
|
286
|
-
# 提取收盘价数据用于计算 EMA
|
287
|
-
close_prices = [float(kline[4]) for kline in klines[::-1]] # K线中的收盘价,顺序要新的在最后
|
288
|
-
|
289
|
-
# 计算 EMA
|
290
|
-
ema_value = pair_config.get('ema', 240)
|
291
|
-
# 如果ema值为0 不区分方向,两头都挂单
|
292
|
-
if ema_value == 0:
|
293
|
-
is_bullish_trend = True
|
294
|
-
is_bearish_trend = True
|
295
|
-
else:
|
296
|
-
ema60 = calculate_ema_pandas(close_prices, period=ema_value)
|
297
|
-
logger.info(f"{instId} EMA60: {ema60:.6f}, 当前价格: {mark_price:.6f}")
|
298
|
-
# 判断趋势:多头趋势或空头趋势
|
299
|
-
is_bullish_trend = close_prices[-1] > ema60 # 收盘价在 EMA60 之上
|
300
|
-
is_bearish_trend = close_prices[-1] < ema60 # 收盘价在 EMA60 之下
|
301
|
-
|
302
|
-
# 计算 ATR
|
303
|
-
atr = calculate_atr(klines)
|
304
|
-
# 当前价格/ATR比值
|
305
|
-
price_atr_ratio = (mark_price / atr) / 100
|
306
|
-
logger.info(f"{instId} ATR: {atr:.3f}, 当前价格/ATR比值: {price_atr_ratio:.3f}")
|
307
|
-
# 平均振幅
|
308
|
-
average_amplitude = calculate_average_amplitude(klines)
|
309
|
-
logger.info(f"{instId} 平均振幅: {average_amplitude:.2f}%")
|
310
|
-
|
311
|
-
value_multiplier = pair_config.get('value_multiplier', 2)
|
312
|
-
'''
|
313
|
-
接针的挂单距离,默认计算逻辑是atr/close 跟 振幅ma的区间求最小值 *系数,如果周期小这样其实大部分时候都是采用的振幅,
|
314
|
-
其实可以多试试其他方案,比如改成atr/close 跟 振幅ma的平均值,这样的话atr权重实际会更大,大部分行情还是atr反应更直接。
|
315
|
-
'''
|
316
|
-
# selected_value = (average_amplitude + price_atr_ratio)/2 * value_multiplier
|
317
|
-
|
318
|
-
selected_value = min(average_amplitude, price_atr_ratio) * value_multiplier
|
319
|
-
amplitude_limit = float(pair_config.get('amplitude_limit', 0.8))
|
320
|
-
selected_value = max(selected_value, amplitude_limit)
|
321
|
-
logger.info(f"{instId} selected_value: {selected_value} ")
|
322
|
-
|
323
|
-
|
324
|
-
long_price_factor = 1 - selected_value / 100
|
325
|
-
short_price_factor = 1 + selected_value / 100
|
326
|
-
|
327
|
-
long_amount_usdt = pair_config.get('long_amount_usdt', 5)
|
328
|
-
short_amount_usdt = pair_config.get('short_amount_usdt', 5)
|
329
|
-
|
330
|
-
target_price_long = mark_price * long_price_factor
|
331
|
-
target_price_short = mark_price * short_price_factor
|
332
|
-
|
333
|
-
logger.info(f"{instId} mark_price: {mark_price} Long target price: {target_price_long:.6f}, Short target price: {target_price_short:.6f}")
|
334
|
-
|
335
|
-
cancel_all_orders(instId)
|
336
|
-
|
337
|
-
# 判断趋势后决定是否挂单
|
338
|
-
if is_bullish_trend:
|
339
|
-
logger.info(f"{instId} 当前为多头趋势,允许挂多单")
|
340
|
-
# send_feishu_notification(f"{instId} place_order:+buy+,目标价格:{target_price_long},交易USDT:{long_amount_usdt} ")
|
341
|
-
place_order(instId, target_price_long, long_amount_usdt, 'buy')
|
342
|
-
else:
|
343
|
-
logger.info(f"{instId} 当前非多头趋势,跳过多单挂单")
|
344
|
-
|
345
|
-
if is_bearish_trend:
|
346
|
-
logger.info(f"{instId} 当前为空头趋势,允许挂空单")
|
347
|
-
# send_feishu_notification(f"{instId} place_order:-sell-,目标价格:{target_price_short},交易USDT:{short_amount_usdt} ")
|
348
|
-
place_order(instId, target_price_short, short_amount_usdt, 'sell')
|
349
|
-
else:
|
350
|
-
logger.info(f"{instId} 当前非空头趋势,跳过空单挂单")
|
351
|
-
|
352
|
-
except Exception as e:
|
353
|
-
error_message = f'Error processing {instId}: {e}'
|
354
|
-
logger.error(error_message)
|
355
|
-
send_feishu_notification(error_message)
|
356
|
-
|
357
|
-
def main():
|
358
|
-
import importlib.metadata
|
359
|
-
|
360
|
-
version = importlib.metadata.version("openfund-wick-reversal")
|
361
|
-
logger.info(f" ++ openfund-wick-reversal:{version} is doing...")
|
362
|
-
logger.info(f" ++ api_key : {api_key}")
|
363
|
-
fetch_and_store_all_instruments()
|
364
|
-
inst_ids = list(trading_pairs_config.keys()) # 获取所有币对的ID
|
365
|
-
batch_size = 5 # 每批处理的数量
|
366
|
-
|
367
|
-
while True:
|
368
|
-
for i in range(0, len(inst_ids), batch_size):
|
369
|
-
batch = inst_ids[i:i + batch_size]
|
370
|
-
with ThreadPoolExecutor(max_workers=batch_size) as executor:
|
371
|
-
futures = [executor.submit(process_pair, instId, trading_pairs_config[instId]) for instId in batch]
|
372
|
-
for future in as_completed(futures):
|
373
|
-
future.result() # Raise any exceptions caught during execution
|
374
|
-
|
375
|
-
time.sleep(monitor_interval)
|
376
|
-
|
377
|
-
if __name__ == '__main__':
|
378
|
-
main()
|
@@ -1,16 +0,0 @@
|
|
1
|
-
maker/BestFVGStrategyMaker.py,sha256=YZiHrUlQPVnlFwsfuwbgG81mrNLZDrq6nIHAjh2KQRk,12154
|
2
|
-
maker/MACDStrategyMaker.py,sha256=iS5HO04piKHFJxUI2e5QmicxzGeK-V1aphJSr2n_4Ac,12651
|
3
|
-
maker/SMCStrategyMaker.py,sha256=grmQKqUj03qVhHyo24qUk8eFmPeyTqq1taYebxzSDXw,35351
|
4
|
-
maker/ThreeLineStrategyMaker.py,sha256=JhPfebBLEL7LIQj1YcH0j1mco1Nuocs8ceR6bjdI8qc,30517
|
5
|
-
maker/WickReversalStrategyMaker.py,sha256=7DqPDVJot4EM0_lSAcFAHrR9rNvkIds9KLMoDOiAHEc,17486
|
6
|
-
maker/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
7
|
-
maker/config.py,sha256=YPxghO5i0vgRg9Cja8kGj9O7pgSbbtzOgf3RexqXXwY,1188
|
8
|
-
maker/main.py,sha256=9b7YRh0rOZlCXmTvAXE3oGhmE4b19NCc0557_jY8bV8,4564
|
9
|
-
maker/main_m.py,sha256=0PzDTnuBrxfpy5WDfsIHKAzZ_7pkuvuqqeWik0vpWio,15522
|
10
|
-
maker/okxapi.py,sha256=_9G0U_o0ZC8NxaT6PqpiLgxBm9gPobC9PsFHZE1c5w0,553
|
11
|
-
maker/zhen.py.bak,sha256=HNkrQbJts8G9umE9chEFsc0cLQApcM9KOVNMYPpkBXM,10918
|
12
|
-
maker/zhen_2.py,sha256=4IaHVtTCMSlrLGSTZrWpW2q-f7HZsUNRkW_-5QgWv24,10509
|
13
|
-
openfund_maker-2.2.6.dist-info/METADATA,sha256=qW2yuUoP7qTjOj3cinBb7SzKzzpbRjLMPD54GgeznOg,1955
|
14
|
-
openfund_maker-2.2.6.dist-info/WHEEL,sha256=IYZQI976HJqqOpQU6PHkJ8fb3tMNBFjg-Cn-pwAbaFM,88
|
15
|
-
openfund_maker-2.2.6.dist-info/entry_points.txt,sha256=gKMytICEKcMRFQDFkHZLnIpID7UQFoTIM_xcpiiV6Ns,50
|
16
|
-
openfund_maker-2.2.6.dist-info/RECORD,,
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|