openfund-taker 2.0.2__tar.gz → 2.0.4__tar.gz

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.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.3
2
2
  Name: openfund-taker
3
- Version: 2.0.2
3
+ Version: 2.0.4
4
4
  Summary: Openfund-taker
5
5
  Requires-Python: >=3.9,<4.0
6
6
  Classifier: Programming Language :: Python :: 3
@@ -1,6 +1,6 @@
1
1
  [tool.poetry]
2
2
  name = "openfund-taker"
3
- version = "2.0.2"
3
+ version = "2.0.4"
4
4
  description = "Openfund-taker"
5
5
  authors = []
6
6
  readme = "README.md"
@@ -20,7 +20,7 @@ pyyaml = "^6.0.2"
20
20
  pytest = "^7.4.4"
21
21
  pytest-mock = "^3.14.0"
22
22
  pytest-asyncio = "^0.23.3"
23
-
23
+ pytest-cov = "^6.1.1"
24
24
 
25
25
 
26
26
  [[tool.poetry.source]]
@@ -0,0 +1,470 @@
1
+ from typing import override
2
+ import pandas as pd
3
+
4
+ from taker.TrailingSLTaker import TrailingSLTaker
5
+
6
+ class SMCSLAndTPTaker(TrailingSLTaker):
7
+ def __init__(self,g_config, platform_config, feishu_webhook=None, monitor_interval=4,logger=None):
8
+ super().__init__(g_config, platform_config, feishu_webhook, monitor_interval,logger)
9
+ self.global_symbol_take_profit_flag = {} # 记录每个symbol是否设置全局止盈标志
10
+ self.global_symbol_take_profit_price = {} # 记录每个symbol的止盈价格
11
+ self.htf_liquidities = {}
12
+
13
+ @override
14
+ def check_reverse_position(self,symbol,position,pair_config):
15
+ """
16
+ 检查是否有反向持仓
17
+ """
18
+ self.logger.debug(f"{symbol}: 检查LTF-Struceture是否市价清仓。")
19
+ # 根据LTF的Struceture识别
20
+
21
+ def format_klines(self,klines) -> pd.DataFrame:
22
+
23
+ """_summary_
24
+ 格式化K线数据
25
+ Args:
26
+ klines (_type_): _description_
27
+ """
28
+ klines_df = pd.DataFrame(klines, columns=['timestamp', 'open', 'high', 'low', 'close', 'volume'])
29
+ # 转换时间戳为日期时间
30
+ klines_df['timestamp'] = pd.to_datetime(klines_df['timestamp'], unit='ms').dt.tz_localize('UTC').dt.tz_convert('Asia/Shanghai')
31
+
32
+ return klines_df
33
+
34
+ def build_struct(self, df, prd=20, check_bounds=True, global_extremum=False) :
35
+
36
+ """_summary_
37
+ 构建SMC结构,参考 Tradingview Smart Money Concepts Probability (Expo)@Openfund
38
+ """
39
+ data = df.copy()
40
+ data['Up'] = None
41
+ data['Dn'] = None
42
+ data['iUp'] = None
43
+ data['iDn'] = None
44
+ data['pos'] = 0
45
+ data['pattern'] = None
46
+
47
+ # 初始化 Up 和 Dn 的第一个值
48
+ data.at[0, 'Up'] = data.at[0, 'high']
49
+ data.at[0, 'Dn'] = data.at[0, 'low']
50
+
51
+
52
+ for index in range(1, len(data)):
53
+
54
+ data.at[index, 'Up'] = max(data.at[index - 1, 'Up'], data.at[index, 'high'])
55
+ data.at[index, 'Dn'] = min(data.at[index - 1, 'Dn'], data.at[index, 'low'])
56
+ data.at[index, 'pos'] = data.at[index - 1, 'pos']
57
+ data.at[index, 'iUp'] = data.at[max(0,index - 1), 'iUp'] if data.at[max(0,index - 1), 'iUp'] is not None else index
58
+ data.at[index, 'iDn'] = data.at[max(0,index - 1), 'iDn'] if data.at[max(0,index - 1), 'iDn'] is not None else index
59
+
60
+ # 寻找枢轴高点和低点
61
+ pvtHi = self.is_pivot_high(data, index, prd, check_bounds)
62
+ pvtLo = self.is_pivot_low(data, index, prd, check_bounds)
63
+
64
+ if pvtHi:
65
+ data.at[index, 'Up'] = data.at[index, 'high']
66
+ data.at[index, 'iUp'] = index
67
+ if pvtLo:
68
+ data.at[index, 'Dn'] = data.at[index, 'low']
69
+ data.at[index, 'iDn'] = index
70
+ # 寻找Bullish结构
71
+ if data.at[index, 'Up'] > data.at[index - 1, 'Up']:
72
+ data.at[index, 'iUp'] = index # TODO
73
+ if data.at[index - 1, 'pos'] <= 0:
74
+ # data.at[index, 'pattern'] = 'CHoCH (Bullish)'
75
+ data.at[index, 'pattern'] = 'Bullish_CHoCH'
76
+ data.at[index, 'pos'] = 1
77
+ elif data.at[index - 1, 'pos'] == 1 \
78
+ and data.at[index - 1, 'Up'] == data.at[max(0,index - prd), 'Up']:
79
+ data.at[index, 'pattern'] = 'Bullish_SMS'
80
+ data.at[index, 'pos'] = 2
81
+
82
+ elif data.at[index - 1, 'pos'] > 1 \
83
+ and data.at[index - 1, 'Up'] == data.at[max(0,index - prd), 'Up']:
84
+ data.at[index, 'pattern'] = 'Bullish_BMS'
85
+ data.at[index, 'pos'] = data.at[index - 1, 'pos'] + 1
86
+
87
+ elif global_extremum and data.at[index, 'Up'] < data.at[index - 1, 'Up']:
88
+ data.at[index, 'iUp'] = data.at[index - 1, 'iUp']
89
+
90
+ # # 寻找Bearish结构
91
+ if data.at[index, 'Dn'] < data.at[index - 1, 'Dn']:
92
+ data.at[index, 'iDn'] = index # TODO
93
+ if data.at[index - 1, 'pos'] >= 0:
94
+
95
+ data.at[index, 'pattern'] = 'Bearish_CHoCH'
96
+ data.at[index, 'pos'] = -1
97
+ elif data.at[index - 1, 'pos'] == -1 \
98
+ and data.at[index - 1, 'Dn'] == data.at[max(0,index - prd), 'Dn']:
99
+ data.at[index, 'pattern'] = 'Bearish_SMS'
100
+ data.at[index, 'pos'] = -2
101
+ elif data.at[index - 1, 'pos'] < -1 \
102
+ and data.at[index - 1, 'Dn'] == data.at[max(0,index - prd), 'Dn']:
103
+ data.at[index, 'pattern'] = 'Bearish_BMS'
104
+ data.at[index, 'pos'] = data.at[index - 1, 'pos'] - 1
105
+
106
+ elif global_extremum and data.at[index, 'Dn'] > data.at[index - 1, 'Dn']:
107
+ data.at[index, 'iDn'] = data.at[index - 1, 'iDn']
108
+
109
+ return data
110
+
111
+ def detect_struct(self, data, prd=20, check_valid_range=True, struct_key=None, check_bounds=True, global_extremum=False) -> dict:
112
+ """_summary_
113
+ 识别智能资金结构
114
+
115
+ :param data: 包含 'high' 和 'low' 列的 DataFrame
116
+ :param prd: 结构周期
117
+ :param s1: 结构响应布尔值
118
+ :param resp: 响应周期
119
+ :return: 包含结构识别结果的 DataFrame
120
+ """
121
+
122
+ data = self.build_struct(data, prd, check_bounds, global_extremum)
123
+
124
+
125
+ # 获取最后一个结构和位置
126
+ last_struct = {
127
+ "struct": None,
128
+ "index": -1,
129
+ "pivot_high": None,
130
+ "pivot_high_index": -1,
131
+ "pivot_low": None,
132
+ "pivot_low_index": -1,
133
+ "side": None
134
+
135
+ }
136
+
137
+ pivot_high_index = last_struct["pivot_high_index"] = int(data["iUp"].iloc[-1])
138
+ pivot_low_index = last_struct["pivot_low_index"] = int(data["iDn"].iloc[-1])
139
+
140
+ last_struct["pivot_high"] = float(data.loc[last_struct["pivot_high_index"], 'high'])
141
+ last_struct["pivot_low"] = float(data.loc[last_struct["pivot_low_index"], 'low'])
142
+
143
+ for i in range(len(data)-1, -1, -1):
144
+ if check_valid_range:
145
+ # 检查是否在pivot_high_index和pivot_low_index之间的有效范围内
146
+ if data.at[i, 'iUp'] != -1 and data.at[i, 'iDn'] != -1:
147
+ # pivot_high_index = data.at[i, 'iUp']
148
+ # pivot_low_index = data.at[i, 'iDn']
149
+ if i < min(pivot_high_index, pivot_low_index) or i > max(pivot_high_index, pivot_low_index):
150
+ continue
151
+
152
+ if data.at[i, 'pattern'] is not None:
153
+ if struct_key is not None and struct_key not in data.at[i, 'pattern']:
154
+ continue
155
+ last_struct["struct"] = data.at[i, 'pattern']
156
+ last_struct["index"] = i
157
+
158
+ break
159
+
160
+ if last_struct['struct'] is not None :
161
+ # 找到最后一个结构的枢轴高点和低点,如果当前是孤立点,则取前一个孤立点
162
+ # 判断交易方向
163
+ if 'Bearish' in last_struct["struct"]:
164
+ last_struct["side"] = 'sell'
165
+ else :
166
+ last_struct["side"] = 'buy'
167
+ else:
168
+ last_struct['struct'] = 'None'
169
+ last_struct["index"] = -1
170
+
171
+
172
+ return last_struct
173
+
174
+ def is_pivot_high(self, data, index, period, check_bounds=False):
175
+ """
176
+ 判断当前索引处是否为枢轴高点
177
+ :param data: 包含 'high' 列的 DataFrame
178
+ :param index: 当前索引
179
+ :param period: 前后比较的周期数
180
+ :return: 是否为枢轴高点
181
+ """
182
+ if check_bounds and (index < period or index >= len(data) - period):
183
+ return False
184
+ current_high = data.at[index, 'high']
185
+ prev_highs = data['high'].iloc[max(0,index - period):index]
186
+ next_highs = data['high'].iloc[index :min(len(data),index + period + 1)]
187
+ return all(current_high >= prev_highs) and all(current_high >= next_highs)
188
+
189
+ def is_pivot_low(self, data, index, period, check_bounds=False):
190
+ """
191
+ 判断当前索引处是否为枢轴低点
192
+ :param data: 包含 'low' 列的 DataFrame
193
+ :param index: 当前索引
194
+ :param period: 前后比较的周期数
195
+ :return: 是否为枢轴低点
196
+ """
197
+ if check_bounds and (index < period or index >= len(data) - period):
198
+ return False
199
+ current_low = data.at[index, 'low']
200
+ prev_lows = data['low'].iloc[max(0,index - period):index]
201
+ next_lows = data['low'].iloc[index :min(len(data),index + period + 1)]
202
+ return all(current_low <= prev_lows) and all(current_low <= next_lows)
203
+ @override
204
+ def set_stop_loss_take_profit(self, symbol, position, stop_loss_price=None,take_profit_price=None) -> bool:
205
+ if not stop_loss_price :
206
+ self.logger.warning(f"{symbol}: No stop loss price or take profit price provided for {symbol}")
207
+ return False
208
+ if not position:
209
+ self.logger.warning(f"{symbol}: No position found for {symbol}")
210
+ return False
211
+
212
+ return self.set_stop_loss(symbol=symbol, position=position, stop_loss_price=stop_loss_price)
213
+
214
+
215
+ def set_sl_by_profit(self, symbol, position, profit, pair_config, kLines=None):
216
+
217
+ """
218
+ 根据利润设置止损价
219
+ Args:
220
+ symbol: 交易对
221
+ position: 仓位信息
222
+ profit: 利润
223
+ pair_config: 交易对配置
224
+ """
225
+
226
+ total_profit = profit
227
+
228
+ current_tier = '无'
229
+ # 各档止盈逻辑
230
+ # 确定当前盈利档位
231
+ if total_profit >= self.second_trail_profit_threshold:
232
+ current_tier = "高档"
233
+
234
+ elif total_profit>= self.first_trail_profit_threshold:
235
+ current_tier = "中档"
236
+
237
+ elif total_profit >= self.low_trail_profit_threshold:
238
+ current_tier = "低档"
239
+
240
+ # 根据不同档位设置止损价格,没有单独为交易对设置,用全局参数代替
241
+ tier_config = {
242
+ "低档": {
243
+ "stop_loss_pct": float(pair_config.get('low_trail_stop_loss_pct',self.low_trail_stop_loss_pct)),
244
+
245
+ },
246
+ "中档": {
247
+ "stop_loss_pct": float(pair_config.get('trail_stop_loss_pct',self.trail_stop_loss_pct)),
248
+
249
+ },
250
+ "高档": {
251
+ "stop_loss_pct": float(pair_config.get('higher_trail_stop_loss_pct',self.higher_trail_stop_loss_pct)),
252
+
253
+ }
254
+ }
255
+
256
+ if current_tier in tier_config:
257
+ config = tier_config[current_tier]
258
+
259
+
260
+ # 计算回撤止损价格
261
+ sl_price = self.calculate_stop_loss_price(
262
+ symbol=symbol,
263
+ position=position,
264
+ stop_loss_pct=config['stop_loss_pct']
265
+ )
266
+
267
+ # 检查价格是否变化
268
+ latest_sl_price = self.exchange.safe_float(self.global_symbol_stop_loss_price, symbol, 0.0)
269
+ if sl_price == latest_sl_price:
270
+ self.logger.debug(f"{symbol}: 回撤止损价格{latest_sl_price}未变化,不设置")
271
+ return
272
+
273
+ # 移动止损保护
274
+ if_success = self.set_stop_loss(symbol=symbol, position=position, stop_loss_price=sl_price)
275
+
276
+ if if_success:
277
+ # 更新回撤止损价格
278
+
279
+ self.global_symbol_stop_loss_price[symbol] = sl_price
280
+ self.global_symbol_stop_loss_flag[symbol] = True
281
+
282
+
283
+ # 发送通知
284
+ msg = (f"{symbol}: 盈利达到【{current_tier}】阈值,最高总盈利: {cur_highest_total_profit:.2f}%,"
285
+ f"当前盈利回撤到: {total_profit:.2f}%,市场价格:{position['markPrice']},"
286
+ f"设置回撤止损位: {sl_price:.9f}")
287
+ self.logger.info(msg)
288
+ self.send_feishu_notification(msg)
289
+
290
+ else:
291
+
292
+ # 默认全局止损
293
+ self.set_global_stop_loss(symbol=symbol, position=position, pair_config=pair_config ,kLines = kLines)
294
+
295
+ def find_liquidity(self, symbol, data, side) -> pd.DataFrame:
296
+ """
297
+ 寻找流动性,根据side判断是做多还是做空,做多则寻找iUp,做空则寻找iDn
298
+ Args:
299
+ symbol (str): 交易对
300
+ data (pd.DataFrame): 数据
301
+ side (str): 交易方向,'long' 或'short'
302
+ """
303
+ df = data.copy()
304
+
305
+ is_buy = side == 'long'
306
+
307
+ col_prefix = 'iUp' if is_buy else 'iDn'
308
+ price_col = 'Up' if is_buy else 'Dn'
309
+
310
+ return df[df.index == df[col_prefix]].sort_index(ascending=False)
311
+
312
+ def detect_liquidity(self, symbol, data, side , market_price) -> pd.DataFrame:
313
+ """
314
+ 校对流动性,用市场价格校验流动性是否有效,做多则流动性在市场价格之上,做空流动性要在市场价格之下。
315
+ Args:
316
+ symbol (str): 交易对
317
+ side (str): 交易方向,'long' 或 'short'
318
+ df_liquidities (pd.DataFrame): 流动性数据
319
+ market_price (float): 当前市场价格
320
+
321
+ """
322
+ df_liquidities = self.find_liquidity(symbol, data, side)
323
+
324
+ df_valid_liquidities = df_liquidities.copy()
325
+ is_buy = side == 'long'
326
+ col_prefix = 'iUp' if is_buy else 'iDn'
327
+ price_col = 'Up' if is_buy else 'Dn'
328
+
329
+ result_indices = []
330
+ current_price = float('-inf') if is_buy else float('inf')
331
+ current_i = float('inf')
332
+
333
+ # 遍历并筛选符合条件的记录
334
+ for idx, row in df_valid_liquidities.iterrows():
335
+ if is_buy:
336
+ if row[price_col] > current_price and row[price_col] > market_price and row[col_prefix] < current_i:
337
+ result_indices.append(idx)
338
+ current_price = row[price_col]
339
+ current_i = row[col_prefix]
340
+ else:
341
+ if row[price_col] < current_price and row[price_col] < market_price and row[col_prefix] < current_i:
342
+ result_indices.append(idx)
343
+ current_price = row[price_col]
344
+ current_i = row[col_prefix]
345
+
346
+ return df_valid_liquidities.loc[result_indices].sort_index(ascending=False)
347
+
348
+ def calculate_tp_price_by_liquidity(self, symbol, position, df_liquidities, offset=1) -> float:
349
+ """_summary_
350
+ 计算止盈价格,根据流动性,做多则止盈价格在流动性之上,做空则止盈价格在流动性之下。
351
+ Args:
352
+ symbol (_type_): _description_
353
+ position (_type_): _description_
354
+ df_liquidities (_type_): _description_
355
+
356
+ Returns:
357
+ float: _description_
358
+ """
359
+ tp_price = 0.0
360
+ market_price = float(position['markPrice'])
361
+
362
+ is_buy = position['side'] == 'long'
363
+ price_col = 'Up' if is_buy else 'Dn'
364
+
365
+ sl_price = self.global_symbol_stop_loss_price.get(symbol, 0.0)
366
+ entry_price = float(position['entryPrice'])
367
+ threshold = market_price
368
+ if is_buy and sl_price > 0 :
369
+ threshold = (entry_price - sl_price ) * 1.5 + entry_price
370
+ elif not is_buy and sl_price > 0:
371
+ threshold = entry_price - (sl_price - entry_price ) * 1.5
372
+
373
+ # 过滤有效的流动性价格
374
+ valid_mask = df_liquidities[price_col] > threshold if is_buy else df_liquidities[price_col] < threshold
375
+ df_valid_liquidities = df_liquidities[valid_mask]
376
+
377
+ # 获取止盈价格
378
+ tp_price = df_valid_liquidities.iloc[0][price_col] if len(df_valid_liquidities) > 0 else 0.0
379
+ tick_size = self.get_tick_size(symbol)
380
+
381
+ # 计算止盈价格 , 做多则止盈价格在流动性之下tick_size,做空则止盈价格在流动性之上tick_size。
382
+ if is_buy:
383
+ tp_price = tp_price - offset * tick_size
384
+ else:
385
+ tp_price = tp_price + offset * tick_size
386
+
387
+
388
+ return tp_price
389
+
390
+ @override
391
+ def close_all_cache(self):
392
+ super().close_all_cache()
393
+ self.htf_liquidities.clear()
394
+ self.global_symbol_take_profit_flag.clear()
395
+ self.global_symbol_take_profit_price.clear()
396
+
397
+ @override
398
+ def reset_all_cache(self, symbol):
399
+ super().reset_all_cache(symbol)
400
+ self.htf_liquidities[symbol] = None
401
+ self.global_symbol_take_profit_flag[symbol] = False
402
+ self.global_symbol_take_profit_price[symbol] = None
403
+
404
+ def set_tp_by_structure(self, symbol, position, pair_config, htf_Klines=None):
405
+ """
406
+ 根据结构设置止盈
407
+ """
408
+ # 如果已经触发过全局止盈,则跳过
409
+ if self.global_symbol_take_profit_flag.get(symbol, False):
410
+ self.logger.info(f"{symbol}: 已经设置过全局止盈 tp_price={self.global_symbol_take_profit_price[symbol]}")
411
+ return
412
+
413
+ smc_strategy = pair_config.get('smc_strategy',{})
414
+ htf = str(smc_strategy.get('HTF','15m'))
415
+ htf_prd = int(smc_strategy.get('HTF_swing_points_length',3))
416
+
417
+ # 寻找HTF的流动性,作为止盈位置
418
+ if htf_Klines is None:
419
+ htf_Klines = self.get_historical_klines(symbol=symbol, bar=htf)
420
+ htf_df = self.format_klines(htf_Klines)
421
+ htf_df_with_struct = self.build_struct(df=htf_df,prd=htf_prd)
422
+
423
+ # 寻找流动性
424
+ htf_liquidity = self.htf_liquidities.get(symbol,None)
425
+ if htf_liquidity is None:
426
+ htf_liquidity = self.detect_liquidity(symbol, htf_df_with_struct, position['side'], position['markPrice'])
427
+ self.htf_liquidities[symbol] = htf_liquidity
428
+
429
+ if len(htf_liquidity) <= 0:
430
+ self.logger.info(f"{symbol} : 没有找到流动性,不设置止盈")
431
+ return
432
+
433
+ tp_price = self.calculate_tp_price_by_liquidity(symbol, position, htf_liquidity)
434
+
435
+ if self.set_take_profit(symbol, position, tp_price):
436
+ self.global_symbol_take_profit_flag[symbol] = True
437
+ self.global_symbol_take_profit_price[symbol] = tp_price
438
+ self.logger.info(f"{symbol} : [{position['side']}] 设置全局止盈价={tp_price}")
439
+
440
+
441
+ def check_total_profit(self, symbol, position):
442
+ """
443
+ 检查当前总盈利
444
+ """
445
+ pair_config = self.get_pair_config(symbol)
446
+ total_profit = self.calculate_average_profit(symbol, position)
447
+ cur_highest_total_profit = self.highest_total_profit.get(symbol, 0.0)
448
+ if total_profit > cur_highest_total_profit:
449
+ self.highest_total_profit[symbol] = total_profit
450
+
451
+ msg = f"{symbol} : 盈利={total_profit:.2f}% 方向={position['side']} 开仓={position['entryPrice']:.6f} 市价={position['markPrice']:.6f}"
452
+ self.logger.info(msg)
453
+ self.send_feishu_notification(msg)
454
+
455
+ # self.cancel_all_algo_orders(symbol=symbol)
456
+
457
+ smc_strategy = pair_config.get('smc_strategy',{})
458
+ htf = str(smc_strategy.get('HTF','15m'))
459
+ htf_Klines = self.get_historical_klines(symbol=symbol, bar=htf)
460
+
461
+
462
+ # 1. 根据总盈利设置止损
463
+ self.set_sl_by_profit(symbol=symbol, position=position, profit=total_profit, pair_config=pair_config, kLines=htf_Klines)
464
+
465
+ # 2. 根据结构设置止盈
466
+ self.set_tp_by_structure(symbol=symbol, position=position, pair_config=pair_config, htf_Klines=htf_Klines)
467
+
468
+
469
+ return
470
+
@@ -6,17 +6,16 @@ from taker.TrailingSLTaker import TrailingSLTaker
6
6
  class TrailingSLAndTPTaker(TrailingSLTaker):
7
7
  def __init__(self,g_config, platform_config, feishu_webhook=None, monitor_interval=4,logger=None):
8
8
  super().__init__(g_config, platform_config, feishu_webhook, monitor_interval,logger)
9
- self.all_TP_LS_ratio = float(platform_config.get("all_TP_LS_ratio",1.5)) #The profit-loss ratio 盈亏比
10
- self.all_take_profit_pct = self.stop_loss_pct * self.all_TP_LS_ratio
9
+ self.all_TP_SL_ratio = float(platform_config.get("all_TP_SL_ratio",1.5)) #The profit-loss ratio 盈亏比
10
+ self.all_take_profit_pct = self.stop_loss_pct * self.all_TP_SL_ratio
11
11
 
12
12
  def set_stop_loss_take_profit(self, symbol, position, stop_loss_price=None, take_profit_price=None) -> bool:
13
13
  is_successful = super().set_stop_loss_take_profit(symbol, position, stop_loss_price, take_profit_price)
14
- # take_profile_pct = self.stop_loss_pct * 2
14
+
15
15
  order_take_profit_price = take_profit_price
16
16
  if take_profit_price is None:
17
17
  order_take_profit_price = self.calculate_take_profile_price(symbol, position, self.all_take_profit_pct)
18
- is_successful = self.set_take_profit(symbol, position, order_take_profit_price)
19
-
18
+ is_successful = self.set_take_profit(symbol, position, order_take_profit_price)
20
19
 
21
20
  return is_successful
22
21
 
@@ -122,18 +122,25 @@ class TrailingSLTaker:
122
122
 
123
123
  # 获取历史K线,不包含最新的一条
124
124
  def get_historical_klines_except_last(self,symbol, bar='1m', limit=241):
125
- # response = market_api.get_candlesticks(instId, bar=bar, limit=limit)
125
+
126
+ klines = self.get_historical_klines(symbol, bar=bar, limit=limit)
127
+ return klines[:-1]
128
+
129
+
130
+ def get_historical_klines(self,symbol, bar='1m', limit=241):
131
+
126
132
  params = {
127
133
  # 'instId': instId,
128
134
  }
129
135
  klines = self.exchange.fetch_ohlcv(symbol, timeframe=bar,limit=limit,params=params)
130
- # if 'data' in response and len(response['data']) > 0:
136
+
131
137
  if klines :
132
- # return response['data']
133
- return klines[:-1]
138
+
139
+ return klines
134
140
  else:
135
141
  raise ValueError("Unexpected response structure or missing candlestick data")
136
142
 
143
+
137
144
  def is_pivot_high(self, data, index, period, check_bounds=False):
138
145
  """
139
146
  判断当前索引处是否为枢轴高点
@@ -323,8 +330,8 @@ class TrailingSLTaker:
323
330
 
324
331
  # 根据情况 重新修正止损
325
332
  self.global_symbol_stop_loss_flag[symbol] = False
326
- find_swing_point = pair_config.get('open_swing_point', False)
327
- self.set_global_stop_loss(symbol=symbol,position=position,stop_loss_pct=order_stop_loss_pct ,find_swing_point=find_swing_point)
333
+
334
+ self.set_global_stop_loss(symbol=symbol,position=position, pair_config=pair_config, stop_loss_pct=order_stop_loss_pct)
328
335
 
329
336
 
330
337
  except Exception as e:
@@ -456,6 +463,9 @@ class TrailingSLTaker:
456
463
  self.logger.warning(f"{symbol}: No position found for {symbol}")
457
464
  return False
458
465
 
466
+ # 取消所有策略订单
467
+ self.cancel_all_algo_orders(symbol=symbol)
468
+
459
469
  if_stop_loss_success = True
460
470
  if_take_profit_success = True
461
471
 
@@ -560,10 +570,6 @@ class TrailingSLTaker:
560
570
  self.logger.warning(f"{symbol}: amount is 0 for {symbol}")
561
571
  return
562
572
 
563
- # 取消所有策略订单
564
- self.cancel_all_algo_orders(symbol=symbol)
565
-
566
-
567
573
  # 止损单逻辑
568
574
  adjusted_price = self.round_price_to_tick(symbol, stop_loss_price)
569
575
 
@@ -638,17 +644,17 @@ class TrailingSLTaker:
638
644
  return True
639
645
 
640
646
 
641
- def set_global_stop_loss(self, symbol, position, stop_loss_pct=None,find_swing_point=False):
647
+ def set_global_stop_loss(self, symbol, position, pair_config , stop_loss_pct=None, kLines=None):
642
648
  """设置全局止损
643
649
 
644
650
  Args:
645
651
  symbol: 交易对
646
652
  position: 持仓信息
647
- side: 持仓方向
648
- stop_loss_algo: 止损算法信息
653
+ pair_config: 交易对配置
654
+
649
655
  """
650
656
  # 如果已经触发过全局止损并且有止损单,则跳过
651
- self.logger.debug(f"{symbol} - 是否设置过全局止损 {self.global_symbol_stop_loss_flag.get(symbol, False)} ")
657
+ self.logger.debug(f"{symbol} : 是否设置过全局止损 {self.global_symbol_stop_loss_flag.get(symbol, False)} ")
652
658
  if self.global_symbol_stop_loss_flag.get(symbol, False):
653
659
  return
654
660
 
@@ -660,17 +666,17 @@ class TrailingSLTaker:
660
666
  side = position['side']
661
667
  mark_price = position['markPrice']
662
668
  if side == 'long':
663
- tp_price = position['entryPrice'] * (1 + stop_loss_pct*2/100)
669
+ # tp_price = position['entryPrice'] * (1 + stop_loss_pct*2/100)
664
670
  sl_price = position['entryPrice'] * (1 - stop_loss_pct/100)
665
671
 
666
672
 
667
673
  elif side == 'short':
668
- tp_price = position['entryPrice'] * (1 - stop_loss_pct*2/100)
674
+ # tp_price = position['entryPrice'] * (1 - stop_loss_pct*2/100)
669
675
  sl_price = position['entryPrice'] * (1 + stop_loss_pct/100)
670
676
 
671
677
 
672
678
  sl_order_price = float(self.round_price_to_tick(symbol, sl_price))
673
- tp_order_price = float(self.round_price_to_tick(symbol, tp_price))
679
+ # tp_order_price = float(self.round_price_to_tick(symbol, tp_price))
674
680
 
675
681
  # 验证止损价格是否合理20250312
676
682
  mark_price = float(position['markPrice'])
@@ -686,32 +692,33 @@ class TrailingSLTaker:
686
692
  return
687
693
 
688
694
  # 250228 没有指定止损回撤阈值stop_loss_pct...
689
- if find_swing_point :
690
- pair_config = self.trading_pairs_config.get(symbol, {})
691
- klines_period = str(pair_config.get('klines_period', '1m'))
692
- kLines = self.get_historical_klines_except_last(symbol=symbol,bar=klines_period)
695
+ open_swing_point = pair_config.get('open_swing_point', False)
696
+ if open_swing_point :
697
+ if kLines is None :
698
+ klines_period = str(pair_config.get('klines_period', '1m'))
699
+ kLines = self.get_historical_klines(symbol=symbol,bar=klines_period)
700
+
693
701
  swing_points_length = pair_config.get('swing_points_length', 20)
694
- if kLines:
695
- isolated_points = self.get_last_solated_point(symbol ,position , kLines,prd=swing_points_length)
696
-
697
- if len(isolated_points) > 0 :
698
- last_isolated_point = isolated_points[0]
699
-
700
- if side == 'long':
701
- # sl_order_price = max(last_isolated_point, sl_order_price)
702
- sl_order_price = last_isolated_point['low']
703
- else:
704
- # sl_order_price = min(last_isolated_point, sl_order_price)
705
- sl_order_price = last_isolated_point['high']
706
-
707
- self.logger.debug(f"{symbol}: - {side} ,止损价={sl_order_price} 孤立点=\n{last_isolated_point} ")
702
+ isolated_points = self.get_last_solated_point(symbol ,position , kLines,prd=swing_points_length)
703
+
704
+ if len(isolated_points) > 0 :
705
+ last_isolated_point = isolated_points[0]
706
+
707
+ if side == 'long':
708
+
709
+ sl_order_price = min(last_isolated_point['low'],sl_order_price)
710
+ else:
711
+
712
+ sl_order_price = max(last_isolated_point['high'],sl_order_price)
713
+
714
+ self.logger.debug(f"{symbol} : {side} 止损价={sl_order_price} 孤立点=\n{last_isolated_point} ")
708
715
 
709
716
 
710
717
 
711
718
  last_sl_price= self.global_symbol_stop_loss_price.get(symbol,None)
712
719
  if last_sl_price is not None and last_sl_price == sl_order_price:
713
720
  self.global_symbol_stop_loss_flag[symbol] = True
714
- self.logger.debug(f"{symbol}: - {side} ,全局止损价没变化: {last_sl_price} = {sl_order_price}")
721
+ self.logger.debug(f"{symbol} : [{side}] 全局止损价没变化, {last_sl_price} = {sl_order_price}")
715
722
  return
716
723
 
717
724
  try:
@@ -719,23 +726,23 @@ class TrailingSLTaker:
719
726
  if_success = self.set_stop_loss_take_profit(
720
727
  symbol=symbol,
721
728
  position=position,
722
- stop_loss_price=sl_order_price,
723
- take_profit_price=tp_order_price
729
+ stop_loss_price=sl_order_price
730
+ # take_profit_price=tp_order_price
724
731
  )
725
732
  if if_success:
726
733
  # 设置全局止损标志
727
- self.logger.debug(f"{symbol} - {side} 设置全局止损价: {sl_order_price}")
734
+ self.logger.info(f"{symbol} : [{side}] 设置全局止损价={sl_order_price}")
728
735
  self.global_symbol_stop_loss_flag[symbol] = True
729
736
  self.global_symbol_stop_loss_price[symbol] = sl_order_price
730
737
 
731
738
  except Exception as e:
732
- error_msg = f"{symbol} - 设置止损时发生错误: {str(e)}"
739
+ error_msg = f"{symbol} : 设置止损时发生错误, {str(e)}"
733
740
  self.logger.error(error_msg)
734
741
  self.send_feishu_notification(error_msg)
735
742
 
736
743
  def calculate_take_profile_price(self, symbol, position, take_profile_pct, offset=1) -> float:
737
- tick_size = float(self.exchange.market(symbol)['precision']['price'])
738
- # market_price = position['markPrice']
744
+ tick_size = self.get_tick_size(symbol)
745
+
739
746
  entry_price = position['entryPrice']
740
747
  side = position['side']
741
748
 
@@ -966,11 +973,20 @@ class TrailingSLTaker:
966
973
  else:
967
974
 
968
975
  # 默认全局止损
969
- find_swing_point = pair_config.get('open_swing_point', False)
970
- self.set_global_stop_loss(symbol, position, find_swing_point=find_swing_point)
976
+
977
+ self.set_global_stop_loss(symbol, position, pair_config)
971
978
  self.logger.info(f"{symbol}: 全局止损阈值: {self.stop_loss_pct:.2f}%")
972
979
 
973
980
  return
981
+
982
+ def close_all_cache(self):
983
+ self.reset_highest_profit_and_tier()
984
+ self.reset_take_profie()
985
+ self.positions_entry_price= {}
986
+
987
+ def reset_all_cache(self, symbol):
988
+ self.reset_highest_profit_and_tier(symbol)
989
+ self.reset_take_profie(symbol)
974
990
 
975
991
  def monitor_total_profit(self):
976
992
  self.logger.info("启动主循环,开始监控总盈利...")
@@ -983,9 +999,7 @@ class TrailingSLTaker:
983
999
  # 检查是否有仓位
984
1000
  if not positions:
985
1001
  # self.logger.debug("没有持仓,等待下一次检查...")
986
- self.reset_highest_profit_and_tier()
987
- self.reset_take_profie()
988
- self.positions_entry_price= {}
1002
+ self.close_all_cache()
989
1003
  time.sleep(1)
990
1004
  continue
991
1005
 
@@ -1006,8 +1020,7 @@ class TrailingSLTaker:
1006
1020
  # 检查仓位是否有变化
1007
1021
  if symbol in self.positions_entry_price and cur_entry_price != self.positions_entry_price[symbol]:
1008
1022
  # 新开仓
1009
- self.reset_highest_profit_and_tier(symbol)
1010
- self.reset_take_profie(symbol)
1023
+ self.reset_all_cache(symbol)
1011
1024
  self.positions_entry_price[symbol] = cur_entry_price
1012
1025
 
1013
1026
  self.check_total_profit(symbol, position)
@@ -5,6 +5,7 @@ from logging.handlers import TimedRotatingFileHandler
5
5
  from taker.TrailingSLTaker import TrailingSLTaker
6
6
  from taker.TrailingSLAndTPTaker import TrailingSLAndTPTaker
7
7
  from taker.ThreeLineTradingTaker import ThreeLineTradingTaker
8
+ from taker.SMCSLAndTPTaker import SMCSLAndTPTaker
8
9
 
9
10
  def build_logger(log_config) -> logging.Logger:
10
11
  # 配置日志
File without changes