openfund-taker 2.1.14__py3-none-any.whl → 2.2.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.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.3
2
2
  Name: openfund-taker
3
- Version: 2.1.14
3
+ Version: 2.2.1
4
4
  Summary: Openfund-taker
5
5
  Requires-Python: >=3.9,<4.0
6
6
  Classifier: Programming Language :: Python :: 3
@@ -9,9 +9,9 @@ Classifier: Programming Language :: Python :: 3.10
9
9
  Classifier: Programming Language :: Python :: 3.11
10
10
  Classifier: Programming Language :: Python :: 3.12
11
11
  Classifier: Programming Language :: Python :: 3.13
12
- Requires-Dist: TA-Lib (>=0.5.1,<0.6.0)
13
12
  Requires-Dist: ccxt (>=4.4.26,<5.0.0)
14
13
  Requires-Dist: pandas (>=2.2.3,<3.0.0)
14
+ Requires-Dist: pyfiglet (>=1.0.2,<2.0.0)
15
15
  Requires-Dist: pyyaml (>=6.0.2,<7.0.0)
16
16
  Description-Content-Type: text/markdown
17
17
 
@@ -1,8 +1,8 @@
1
1
  taker/MultiAssetOldTradingBot.py,sha256=uBh_BxglvcbaHIsWHM7GI9Qa_QjzsxXaXJAAWEOMO5c,15315
2
- taker/SMCSLAndTPTaker.py,sha256=7Gfq4v6nnK-zVdXKe1-lhLlzfTg8BOEmhlRJ81vjDLA,20743
2
+ taker/SMCSLAndTPTaker.py,sha256=rKt_TDT8kKiyYjfz0rC77MDygvuvjsmAO_hXoLDd8dU,28432
3
3
  taker/ThreeLineTradingTaker.py,sha256=ElD9pKDO4nxw5VKNRrvXqyiU0gkV_1Mg_zk-hftfaPs,20553
4
4
  taker/TrailingSLAndTPTaker.py,sha256=OPD1ZNqyM8jZ7Im-bWm_6Cu4_qfwKmNZ30XpbP6-l00,2746
5
- taker/TrailingSLTaker.py,sha256=CIjlYqIM_1h4GTMGnCV67SQnk0QryPDL3u_kXJ3qioc,47853
5
+ taker/TrailingSLTaker.py,sha256=i9t2AwkF4kMweP4BUW-ZfDmOacb0aJO_NLq8hE6SBQg,48489
6
6
  taker/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
7
7
  taker/chua_bitget.py,sha256=YY6XK5Bd-wlArsN5BnAfiqE0h1DPkti6i4TEB4F5zDA,12918
8
8
  taker/chua_bn.py,sha256=GnTePWlgDwdHgroBbEp1Ajcsf5_m_Vn_RV63SYzu2jI,10668
@@ -10,8 +10,8 @@ taker/chua_ok.py,sha256=5pPAoEYbFuKxfZwqNvOO890s-2cy6n69QiI0ZA0GTCQ,12474
10
10
  taker/chua_ok_all.py,sha256=2XnZM6QdB3juSE1pqQIJyh2x1XuhlTlnBKNA3owlJ9E,15267
11
11
  taker/chua_ok_bot.py,sha256=9SW0ujhi6PfN4yR1JZ9NaA37HtnXJ2QAWUfW52NG68w,13109
12
12
  taker/config.py,sha256=YPxghO5i0vgRg9Cja8kGj9O7pgSbbtzOgf3RexqXXwY,1188
13
- taker/main.py,sha256=-EdrzLG6xK0nauItj3eRC8_xva2PnS9CQdzeFJI-6Wc,2769
14
- openfund_taker-2.1.14.dist-info/METADATA,sha256=gDxo5iUe4Pl5pNdTwkzpL6iBbq6bxdADoQ2bCbIQDiI,7528
15
- openfund_taker-2.1.14.dist-info/WHEEL,sha256=IYZQI976HJqqOpQU6PHkJ8fb3tMNBFjg-Cn-pwAbaFM,88
16
- openfund_taker-2.1.14.dist-info/entry_points.txt,sha256=a7mG8F7aOA5-Gk2vPWuAR4537faxaHUgM_jwIDBZoEc,50
17
- openfund_taker-2.1.14.dist-info/RECORD,,
13
+ taker/main.py,sha256=3M1y_1sRIfkYNGWP0Wb3DfZFgHb0y4Cd45ERPnc1J9w,2927
14
+ openfund_taker-2.2.1.dist-info/METADATA,sha256=NP4IEU9ZAnL890nT9m2RpkCZJa4Mt2_EWNb8A2zGU9w,7529
15
+ openfund_taker-2.2.1.dist-info/WHEEL,sha256=IYZQI976HJqqOpQU6PHkJ8fb3tMNBFjg-Cn-pwAbaFM,88
16
+ openfund_taker-2.2.1.dist-info/entry_points.txt,sha256=a7mG8F7aOA5-Gk2vPWuAR4537faxaHUgM_jwIDBZoEc,50
17
+ openfund_taker-2.2.1.dist-info/RECORD,,
taker/SMCSLAndTPTaker.py CHANGED
@@ -1,5 +1,7 @@
1
1
  from typing import override
2
2
  import pandas as pd
3
+ from decimal import Decimal
4
+
3
5
 
4
6
  from taker.TrailingSLTaker import TrailingSLTaker
5
7
 
@@ -8,7 +10,8 @@ class SMCSLAndTPTaker(TrailingSLTaker):
8
10
  super().__init__(g_config, platform_config, feishu_webhook, monitor_interval,logger)
9
11
  self.global_symbol_take_profit_flag = {} # 记录每个symbol是否设置全局止盈标志
10
12
  self.global_symbol_take_profit_price = {} # 记录每个symbol的止盈价格
11
- self.htf_liquidities = {}
13
+ self.htf_liquidities_TP = {}
14
+ self.htf_liquidities_SL = {}
12
15
  self.all_TP_SL_ratio = float(platform_config.get("all_TP_SL_ratio",1.5)) #The profit-loss ratio 盈亏比
13
16
  self.open_trail_profit = bool(platform_config.get("open_trail_profit",True)) # 开仓是否设置止盈
14
17
 
@@ -80,7 +83,7 @@ class SMCSLAndTPTaker(TrailingSLTaker):
80
83
 
81
84
  # # 寻找Bearish结构
82
85
  if data.at[index, 'Dn'] < data.at[index - 1, 'Dn']:
83
- data.at[index, 'iDn'] = index # TODO
86
+ data.at[index, 'iDn'] = index
84
87
  if data.at[index - 1, 'pos'] >= 0:
85
88
 
86
89
  data.at[index, 'pattern'] = 'Bearish_CHoCH'
@@ -174,8 +177,8 @@ class SMCSLAndTPTaker(TrailingSLTaker):
174
177
  return False
175
178
  current_high = data.at[index, 'high']
176
179
  prev_highs = data['high'].iloc[max(0,index - period):index]
177
- next_highs = data['high'].iloc[index :min(len(data),index + period + 1)]
178
- return all(current_high >= prev_highs) and all(current_high >= next_highs)
180
+ next_highs = data['high'].iloc[index+1 :min(len(data),index + period )+1]
181
+ return all(current_high >= prev_highs) and all(current_high > next_highs)
179
182
 
180
183
  def is_pivot_low(self, data, index, period, check_bounds=False):
181
184
  """
@@ -189,67 +192,103 @@ class SMCSLAndTPTaker(TrailingSLTaker):
189
192
  return False
190
193
  current_low = data.at[index, 'low']
191
194
  prev_lows = data['low'].iloc[max(0,index - period):index]
192
- next_lows = data['low'].iloc[index :min(len(data),index + period + 1)]
193
- return all(current_low <= prev_lows) and all(current_low <= next_lows)
195
+ next_lows = data['low'].iloc[index+1 :min(len(data),index + period)+1]
196
+ return all(current_low <= prev_lows) and all(current_low < next_lows)
194
197
 
195
198
 
196
199
  def set_sl_by_profit(self, symbol, position, profit, pair_config, kLines=None):
197
200
 
198
201
  """
199
- 根据利润设置止损价
202
+ 根据扫过的流动性移动止损
200
203
  Args:
201
204
  symbol: 交易对
202
205
  position: 仓位信息
203
206
  profit: 利润
204
207
  pair_config: 交易对配置
208
+ kLines: K线数据
205
209
  """
210
+
211
+ if self.open_trail_profit and self.global_symbol_stop_loss_flag.get(symbol,False):
212
+
213
+ smc_strategy = pair_config.get('smc_strategy',{})
214
+ htf = str(smc_strategy.get('HTF','15m'))
215
+ htf_prd = int(smc_strategy.get('HTF_swing_points_length',3))
216
+ ltf = str(smc_strategy.get('LTF','1m'))
217
+ ltf_prd = int(smc_strategy.get('LTF_swing_points_length',3))
218
+
219
+
220
+ ctf = ltf
221
+ ctf_prd = ltf_prd
222
+ # ctf_Klines = kLines
223
+
224
+ # 寻找LTF的流动性,作为移动止损位置
225
+ ctf_Klines = self.get_historical_klines(symbol=symbol, bar=ctf)
226
+ ctf_df = self.format_klines(ctf_Klines)
227
+ ctf_df_with_struct = self.build_struct(df=ctf_df,prd=ctf_prd)
228
+ # self.logger.debug(f"{symbol} : {ctf} ctf_df_with_struct\n{ctf_df_with_struct}")
206
229
 
207
- total_profit = profit
230
+ # 寻找流动性
231
+ htf_liquidity_SL = self.htf_liquidities_SL.get(symbol,None)
232
+ if htf_liquidity_SL is None:
233
+ htf_liquidity_SL = self.detect_liquidity_for_SL(symbol, ctf_df_with_struct, position['side'], position['markPrice'])
234
+ self.htf_liquidities_SL[symbol] = htf_liquidity_SL
235
+
236
+ if len(htf_liquidity_SL) == 0:
237
+ self.logger.info(f"{symbol} : {ctf} 没有找到扫荡的流动性,不重设止损")
238
+ return
239
+
240
+ sl_price = self.calculate_trailing_sl_price_by_liquidity(symbol, position, htf_liquidity_SL, self.stop_loss_pct)
241
+ # total_profit = profit
208
242
 
209
- current_tier = '无'
210
- # 各档止盈逻辑
211
- # 确定当前盈利档位
212
- if self.open_trail_profit and total_profit >= self.second_trail_profit_threshold:
213
- current_tier = "高档"
243
+ # current_tier = '无'
244
+ # # 各档止盈逻辑
245
+ # # 确定当前盈利档位
246
+ # if self.open_trail_profit and total_profihtf_liquidities_SLt >= self.second_trail_profit_threshold:
247
+ # current_tier = "高档"
214
248
 
215
- elif self.open_trail_profit and total_profit>= self.first_trail_profit_threshold:
216
- current_tier = "中档"
249
+ # elif self.open_trail_profit and total_profit>= self.first_trail_profit_threshold:
250
+ # current_tier = "中档"
217
251
 
218
- elif self.open_trail_profit and total_profit >= self.low_trail_profit_threshold:
219
- current_tier = "低档"
252
+ # elif self.open_trail_profit and total_profit >= self.low_trail_profit_threshold:
253
+ # current_tier = "低档"
220
254
 
221
- # 根据不同档位设置止损价格,没有单独为交易对设置,用全局参数代替
222
- tier_config = {
223
- "低档": {
224
- "stop_loss_pct": float(pair_config.get('low_trail_stop_loss_pct',self.low_trail_stop_loss_pct)),
255
+ # # 根据不同档位设置止损价格,没有单独为交易对设置,用全局参数代替
256
+ # tier_config = {
257
+ # "低档": {
258
+ # "stop_loss_pct": float(pair_config.get('low_trail_stop_loss_pct',self.low_trail_stop_loss_pct)),
225
259
 
226
- },
227
- "中档": {
228
- "stop_loss_pct": float(pair_config.get('trail_stop_loss_pct',self.trail_stop_loss_pct)),
260
+ # },
261
+ # "中档": {
262
+ # "stop_loss_pct": float(pair_config.get('trail_stop_loss_pct',self.trail_stop_loss_pct)),
229
263
 
230
- },
231
- "高档": {
232
- "stop_loss_pct": float(pair_config.get('higher_trail_stop_loss_pct',self.higher_trail_stop_loss_pct)),
264
+ # },
265
+ # "高档": {
266
+ # "stop_loss_pct": float(pair_config.get('higher_trail_stop_loss_pct',self.higher_trail_stop_loss_pct)),
233
267
 
234
- }
235
- }
268
+ # }
269
+ # }
236
270
 
237
- if current_tier in tier_config:
238
- config = tier_config[current_tier]
271
+ # if current_tier in tier_config and self.open_trail_profit:
272
+ # config = tier_config[current_tier]
239
273
 
240
274
 
241
- # 计算回撤止损价格
242
- sl_price = self.calculate_stop_loss_price(
243
- symbol=symbol,
244
- position=position,
245
- stop_loss_pct=config['stop_loss_pct']
246
- )
275
+ # # 计算回撤止损价格
276
+ # sl_price = self.calculate_stop_loss_price(
277
+ # symbol=symbol,
278
+ # position=position,
279
+ # stop_loss_pct=config['stop_loss_pct']
280
+ # )
247
281
 
248
282
  # 检查价格是否变化
249
- latest_sl_price = self.exchange.safe_float(self.global_symbol_stop_loss_price, symbol, 0.0)
283
+ latest_sl_price = self.global_symbol_stop_loss_price.get(symbol,0.0)
284
+ side = position['side']
250
285
  if sl_price == latest_sl_price:
251
- self.logger.debug(f"{symbol}: 回撤止损价格{latest_sl_price}未变化,不设置")
286
+ self.logger.debug(f"{symbol}: 移动止损价格{latest_sl_price}未变化,不设置")
287
+ return
288
+ elif (side == 'long' and sl_price < latest_sl_price) or (side == 'short' and sl_price > latest_sl_price):
289
+ self.logger.debug(f"{symbol}: [{side}] 新移动止损价格{sl_price} 不能替换 {latest_sl_price},不设置")
252
290
  return
291
+
253
292
 
254
293
  self.cancel_all_algo_orders(symbol=symbol, attachType='SL')
255
294
 
@@ -261,12 +300,13 @@ class SMCSLAndTPTaker(TrailingSLTaker):
261
300
 
262
301
  self.global_symbol_stop_loss_price[symbol] = sl_price
263
302
  self.global_symbol_stop_loss_flag[symbol] = True
264
- cur_highest_total_profit = self.highest_total_profit.get(symbol, 0.0)
303
+ # cur_highest_total_profit = self.highest_total_profit.get(symbol, 0.0)
265
304
 
266
305
  # 发送通知
267
- msg = (f"{symbol}: 盈利达到【{current_tier}】阈值,最高总盈利: {cur_highest_total_profit:.2f}%,"
268
- f"当前盈利回撤到: {total_profit:.2f}%,市场价格:{position['markPrice']},"
269
- f"设置回撤止损位: {sl_price:.9f}")
306
+ # msg = (f"{symbol}: 盈利达到【{current_tier}】阈值,最高总盈利: {cur_highest_total_profit:.2f}%,"
307
+ # f"当前盈利回撤到: {total_profit:.2f}%,市场价格:{position['markPrice']},"
308
+ # f"设置回撤止损位: {sl_price:.9f}")
309
+ msg = (f"{symbol}: {ctf}移动止损,找到扫荡的流动性位置,设置止损: {sl_price:.9f}")
270
310
  self.logger.info(msg)
271
311
  self.send_feishu_notification(msg)
272
312
 
@@ -278,26 +318,25 @@ class SMCSLAndTPTaker(TrailingSLTaker):
278
318
  # 更新回撤止损价格
279
319
  self.global_symbol_take_profit_flag[symbol] = False
280
320
 
281
- def find_liquidity(self, symbol, data, side) -> pd.DataFrame:
321
+ def find_liquidity(self, symbol, data, liquidity_type="BSL") -> pd.DataFrame:
282
322
  """
283
323
  寻找流动性,根据side判断是做多还是做空,做多则寻找iUp,做空则寻找iDn
284
324
  Args:
285
325
  symbol (str): 交易对
286
326
  data (pd.DataFrame): 数据
287
- side (str): 交易方向,'long' 或'short'
327
+ liquidity_type (str): 流动性类型,'BSL' 或 'BSL'
328
+
288
329
  """
289
330
  df = data.copy()
290
331
 
291
- is_buy = side == 'long'
292
-
293
- col_prefix = 'iUp' if is_buy else 'iDn'
294
- price_col = 'Up' if is_buy else 'Dn'
332
+ is_buy = liquidity_type == 'BSL'
333
+ col_prefix = 'iDn' if is_buy else 'iUp'
295
334
 
296
335
  return df[df.index == df[col_prefix]].sort_index(ascending=False)
297
336
 
298
- def detect_liquidity(self, symbol, data, side , market_price) -> pd.DataFrame:
337
+ def detect_liquidity_for_TP(self, symbol, data, side , market_price) -> pd.DataFrame:
299
338
  """
300
- 校对流动性,用市场价格校验流动性是否有效,做多则流动性在市场价格之上,做空流动性要在市场价格之下。
339
+ TP校对流动性,用市场价格校验流动性是否有效,做多则流动性在市场价格之上,做空流动性要在市场价格之下。
301
340
  Args:
302
341
  symbol (str): 交易对
303
342
  side (str): 交易方向,'long' 或 'short'
@@ -305,13 +344,17 @@ class SMCSLAndTPTaker(TrailingSLTaker):
305
344
  market_price (float): 当前市场价格
306
345
 
307
346
  """
308
- df_liquidities = self.find_liquidity(symbol, data, side)
309
-
310
- df_valid_liquidities = df_liquidities.copy()
311
347
  is_buy = side == 'long'
312
348
  col_prefix = 'iUp' if is_buy else 'iDn'
313
349
  price_col = 'Up' if is_buy else 'Dn'
314
350
 
351
+ # 设置TP,long寻找SSL,short寻找BSL
352
+ liquidity_type = 'SSL' if is_buy else 'BSL'
353
+
354
+ df_liquidities = self.find_liquidity(symbol, data, liquidity_type=liquidity_type)
355
+
356
+ df_valid_liquidities = df_liquidities.copy()
357
+
315
358
  result_indices = []
316
359
  current_price = float('-inf') if is_buy else float('inf')
317
360
  current_i = float('inf')
@@ -330,7 +373,115 @@ class SMCSLAndTPTaker(TrailingSLTaker):
330
373
  current_i = row[col_prefix]
331
374
 
332
375
  return df_valid_liquidities.loc[result_indices].sort_index(ascending=False)
376
+
377
+ def detect_liquidity_for_SL(self, symbol, data, side, market_price) -> pd.DataFrame:
378
+ """
379
+ SL校对流动性,用市场价格校验流动性是否有效,做多则流动性在市场价格之下,做空流动性要在市场价格之上。
380
+ Args:
381
+ symbol (str): 交易对
382
+ side (str): 交易方向,'long' 或'short'
383
+ df_liquidities (pd.DataFrame): 流动性数据
384
+ market_price (float): 当前市场价格
385
+
386
+ """
387
+ is_buy = side == 'long'
388
+ col_prefix = 'iDn' if is_buy else 'iUp'
389
+ price_col = 'Dn' if is_buy else 'Up'
390
+ df = data.copy()
391
+
392
+
393
+ # 初始化新列
394
+
395
+ df['pivot_price'] = pd.NA
396
+ df['is_extreme'] = False
397
+
398
+ # 1. 根据pos值分组(连续相同pos为一个区间)
399
+
400
+ groups = df['pos'].gt(0).ne(df['pos'].gt(0).shift()).cumsum()
401
+
402
+ # 2. 对每个pos区间处理
403
+ for group_id, group_data in df.groupby(groups):
404
+ pos_val = group_data['pos'].iloc[0]
405
+
406
+ if pos_val > 0:
407
+ # 找high最大值及其第一次出现的位置
408
+ max_high = group_data['high'].max()
409
+ max_idx = group_data['high'].idxmax()
410
+ df.at[max_idx, 'pivot_price'] = max_high
411
+ df.at[max_idx, 'is_extreme'] = True
412
+
413
+ elif pos_val < 0:
414
+ # 找low最小值及其第一次出现的位置
415
+ min_low = group_data['low'].min()
416
+ min_idx = group_data['low'].idxmin()
417
+ df.at[min_idx, 'pivot_price'] = min_low
418
+ df.at[min_idx, 'is_extreme'] = True
419
+
420
+
421
+ # self.logger.debug(f"{df[['timestamp', 'pattern','high','low','pivot_price','is_extreme']]}")
333
422
 
423
+ struct_siginfo = "Bullish" if is_buy else "Bearish"
424
+
425
+ # 3. 筛选CHoCH后符合条件的行
426
+ result_indices = []
427
+ # 倒序遍历数据
428
+ found_choch = False
429
+ for idx in reversed(df.index):
430
+
431
+ # 检查是否有新的pattern
432
+ current_pattern = df.at[idx, 'pattern']
433
+
434
+
435
+ # 如果找到对应的CHoCH结构
436
+ if current_pattern and f"{struct_siginfo}_CHoCH" == current_pattern:
437
+ found_choch = True
438
+ continue
439
+
440
+ # 在找到CHoCH结构后,记录极值点
441
+ if found_choch and df.at[idx, 'is_extreme']:
442
+ result_indices.append(idx)
443
+ found_choch = False
444
+
445
+ self.logger.debug(f"result_indices = {result_indices}")
446
+ took_out_df = df.loc[result_indices].copy()
447
+ valid_mast = took_out_df['pivot_price'] < market_price if is_buy else took_out_df['pivot_price'] > market_price
448
+ took_out_df = took_out_df[valid_mast]
449
+ return took_out_df.sort_index(ascending=False)
450
+
451
+
452
+ def calculate_trailing_sl_price_by_liquidity(self, symbol, position, df_liquidities, stop_loss_pct=2, offset=1) -> float:
453
+ """
454
+ 计算回撤止损价格,根据流动性,做多则回撤止损价格在流动性之下,做空则回撤止损价格在流动性之上。
455
+ Args:
456
+ symbol (str): 交易对
457
+ position (dict): 仓位信息
458
+ df_liquidities (pd.DataFrame): 流动性数据
459
+ stop_loss_pct (int, optional): 回撤百分比. Defaults to 2.
460
+ offset (int, optional): 偏移量. Defaults to 1.
461
+ Returns:
462
+ float: 回撤止损价格
463
+ """
464
+ sl_price = self.calculate_sl_price_by_pct(symbol, position, stop_loss_pct)
465
+
466
+ is_buy = position['side'] == 'long'
467
+ price_col = 'Dn' if is_buy else 'Up'
468
+ self.logger.debug(f"{symbol} : SL side={position['side']} sl_price={sl_price} SL的扫荡流动性=\n {df_liquidities[['timestamp',price_col, 'is_extreme']]}")
469
+
470
+ valid_mask = df_liquidities[price_col] > sl_price if is_buy else df_liquidities[price_col] < sl_price
471
+ df_valid_liquidities = df_liquidities[valid_mask]
472
+ # 获取止损价格
473
+ # trailing_sl = df_valid_liquidities.iloc[0][price_col] if len(df_valid_liquidities) > 0 else sl_price
474
+ trailing_sl = Decimal(df_liquidities[price_col].max() if is_buy else df_liquidities[price_col].min())
475
+
476
+ # 计算移动止损价格 , 做多则止损价格在流动性之下tick_size,做空则止损价格在流动性之上tick_size。
477
+ tick_size = self.get_tick_size(symbol)
478
+ if is_buy:
479
+ trailing_sl = trailing_sl - offset * tick_size
480
+ else:
481
+ trailing_sl = trailing_sl + offset * tick_size
482
+
483
+ return self.round_price_to_tick(symbol, trailing_sl)
484
+
334
485
  def calculate_tp_price_by_liquidity(self, symbol, position, df_liquidities, stop_loss_pct=2, tp_sl_ratio=1.5, offset=1) -> float:
335
486
  """_summary_
336
487
  计算止盈价格,根据流动性,做多则止盈价格在流动性之上,做空则止盈价格在流动性之下。
@@ -353,25 +504,35 @@ class SMCSLAndTPTaker(TrailingSLTaker):
353
504
  price_col = 'Up' if is_buy else 'Dn'
354
505
 
355
506
  # sl_price = self.global_symbol_stop_loss_price.get(symbol, float(position['markPrice']))
356
- entry_price = float(position['entryPrice'])
357
- sl_price = self.calculate_sl_price_by_pct(symbol, position, stop_loss_pct)
507
+ # 获取开仓价格和止损价格
508
+ entry_price = Decimal(position['entryPrice'])
509
+ sl_price = self.calculate_sl_price_by_pct(symbol, position, stop_loss_pct)
510
+
511
+ # 计算止盈阈值
358
512
  threshold = 0.0
359
- if is_buy and sl_price > 0 :
360
- threshold = float(self.round_price_to_tick(symbol, (entry_price - sl_price ) * tp_sl_ratio + entry_price))
361
- elif not is_buy and sl_price > 0:
362
- threshold = float(self.round_price_to_tick(symbol, entry_price - (sl_price - entry_price ) * tp_sl_ratio))
513
+ if sl_price <= 0:
514
+ return threshold
515
+
516
+ # 做多时,止盈阈值 = 开仓价格 + (开仓价格-止损价格)*止盈止损比率
517
+ if is_buy:
518
+ price_diff = entry_price - sl_price
519
+ threshold = self.round_price_to_tick(symbol, entry_price + price_diff * Decimal(tp_sl_ratio))
520
+ # 做空时,止盈阈值 = 开仓价格 - (止损价格-开仓价格)*止盈止损比率
521
+ else:
522
+ price_diff = sl_price - entry_price
523
+ threshold = self.round_price_to_tick(symbol, entry_price - price_diff * Decimal(tp_sl_ratio))
363
524
 
364
525
 
365
526
  # 过滤有效的流动性价格
366
527
  valid_mask = df_liquidities[price_col] > threshold if is_buy else df_liquidities[price_col] < threshold
367
528
  df_valid_liquidities = df_liquidities[valid_mask]
368
- self.logger.debug(f"{symbol} : threshold={threshold} sl_price={sl_price} 有效的流动=\n {df_valid_liquidities[['timestamp','Up','Dn']]}")
529
+ self.logger.debug(f"{symbol} : TP threshold={threshold} sl_price={sl_price} 有效的流动=\n {df_valid_liquidities[['timestamp','Up','Dn']]}")
369
530
  # 获取止盈价格
370
531
  tp_price = df_valid_liquidities.iloc[0][price_col] if len(df_valid_liquidities) > 0 else threshold
371
532
  if is_buy:
372
- tp_price = max(tp_price, threshold)
533
+ tp_price = Decimal(max(tp_price, threshold))
373
534
  else:
374
- tp_price = min(tp_price, threshold)
535
+ tp_price = Decimal(min(tp_price, threshold))
375
536
  tick_size = self.get_tick_size(symbol)
376
537
 
377
538
  # 计算止盈价格 , 做多则止盈价格在流动性之下tick_size,做空则止盈价格在流动性之上tick_size。
@@ -381,12 +542,13 @@ class SMCSLAndTPTaker(TrailingSLTaker):
381
542
  tp_price = tp_price + offset * tick_size
382
543
 
383
544
 
384
- return tp_price
545
+ return self.round_price_to_tick(symbol, tp_price)
385
546
 
386
547
  @override
387
548
  def close_all_cache(self):
388
549
  super().close_all_cache()
389
- self.htf_liquidities.clear()
550
+ self.htf_liquidities_TP.clear()
551
+ self.htf_liquidities_SL.clear()
390
552
  self.global_symbol_take_profit_flag.clear()
391
553
  self.global_symbol_take_profit_price.clear()
392
554
 
@@ -394,7 +556,8 @@ class SMCSLAndTPTaker(TrailingSLTaker):
394
556
  @override
395
557
  def reset_all_cache(self, symbol):
396
558
  super().reset_all_cache(symbol)
397
- self.htf_liquidities[symbol] = None
559
+ self.htf_liquidities_TP[symbol] = None
560
+ self.htf_liquidities_SL[symbol] = None
398
561
  self.global_symbol_take_profit_flag[symbol] = False
399
562
  self.global_symbol_take_profit_price[symbol] = None
400
563
 
@@ -418,10 +581,10 @@ class SMCSLAndTPTaker(TrailingSLTaker):
418
581
  htf_df_with_struct = self.build_struct(df=htf_df,prd=htf_prd)
419
582
 
420
583
  # 寻找流动性
421
- htf_liquidity = self.htf_liquidities.get(symbol,None)
584
+ htf_liquidity = self.htf_liquidities_TP.get(symbol,None)
422
585
  if htf_liquidity is None:
423
- htf_liquidity = self.detect_liquidity(symbol, htf_df_with_struct, position['side'], position['markPrice'])
424
- self.htf_liquidities[symbol] = htf_liquidity
586
+ htf_liquidity = self.detect_liquidity_for_TP(symbol, htf_df_with_struct, position['side'], position['markPrice'])
587
+ self.htf_liquidities_TP[symbol] = htf_liquidity
425
588
 
426
589
  if len(htf_liquidity) <= 0:
427
590
  self.logger.info(f"{symbol} : 没有找到流动性,不设置止盈")
taker/TrailingSLTaker.py CHANGED
@@ -5,6 +5,7 @@ import requests
5
5
  import traceback
6
6
  import pandas as pd
7
7
  import talib as ta
8
+ from decimal import Decimal
8
9
  '''
9
10
  自动设置移动止损单
10
11
  '''
@@ -55,7 +56,7 @@ class TrailingSLTaker:
55
56
  'timeout': 3000,
56
57
  'rateLimit': 50,
57
58
  'options': {'defaultType': 'future'},
58
- 'proxies': {'http': 'http://127.0.0.1:7890', 'https': 'http://127.0.0.1:7890'},
59
+ # 'proxies': {'http': 'http://127.0.0.1:7890', 'https': 'http://127.0.0.1:7890'},
59
60
  })
60
61
  self.logger = logger
61
62
  self.position_mode = self.get_position_mode() # 获取持仓模式
@@ -67,8 +68,20 @@ class TrailingSLTaker:
67
68
  self.exchange.load_markets()
68
69
  return self.exchange.market(symbol)
69
70
  # 获取tick_size
70
- def get_tick_size(self,symbol):
71
- return float(self.getMarket(symbol)['precision']['price'])
71
+ def get_tick_size(self,symbol) -> Decimal:
72
+
73
+ try:
74
+ market = self.getMarket(symbol)
75
+ if market and 'precision' in market and 'price' in market['precision']:
76
+
77
+ return Decimal(str(market['precision']['price']))
78
+ else:
79
+ self.logger.warn(f"{symbol}: 无法从市场数据中获取价格精度")
80
+ return Decimal(0.00001) # 返回默认精度
81
+ except Exception as e:
82
+ self.logger.warn(f"{symbol}: 获取市场价格精度时发生错误: {str(e)}")
83
+ return Decimal(0.00001) # 发生异常时返回默认精度
84
+ # return float(self.getMarket(symbol)['precision']['price'])
72
85
  # 获取价格精度
73
86
  def get_precision_length(self,symbol) -> int:
74
87
  tick_size = self.get_tick_size(symbol)
@@ -97,21 +110,16 @@ class TrailingSLTaker:
97
110
  headers = {'Content-Type': 'application/json'}
98
111
  payload = {"msg_type": "text", "content": {"text": message}}
99
112
  response = requests.post(self.feishu_webhook, json=payload, headers=headers)
100
- if response.status_code == 200:
101
- self.logger.debug("飞书通知发送成功")
102
- else:
103
- self.logger.warn("飞书通知发送失败,状态码: %s", response.status_code)
113
+ # if response.status_code == 200:
114
+ # self.logger.debug("飞书通知发送成功")
115
+ # else:
116
+ # self.logger.warn("飞书通知发送失败,状态码: %s", response.status_code)
104
117
  except Exception as e:
105
118
  self.logger.error("发送飞书通知时出现异常: %s", str(e))
106
119
 
107
- def fetch_positions(self):
108
- try:
109
- positions = self.exchange.fetch_positions()
110
- return positions
111
- except Exception as e:
112
- self.logger.warning(f"Warn fetching positions: {e}")
113
- return []
114
-
120
+ def fetch_positions(self):
121
+ return self.exchange.fetch_positions()
122
+
115
123
  # 获取当前委托
116
124
  def fetch_open_orders(self,symbol,params={}):
117
125
  try:
@@ -379,7 +387,7 @@ class TrailingSLTaker:
379
387
 
380
388
  # 计算平均利润
381
389
  def calculate_average_profit(self,symbol,position):
382
- # positions = self.fetch_positions()
390
+
383
391
  total_profit_pct = 0.0
384
392
  num_positions = 0
385
393
 
@@ -433,15 +441,15 @@ class TrailingSLTaker:
433
441
  self.min_market_price[symbol] = float('inf') # 初始化为浮点数最大值
434
442
  self.cross_directions[symbol] = None
435
443
 
436
- def round_price_to_tick(self,symbol, price):
437
- tick_size = float(self.exchange.market(symbol)['info']['tickSz'])
444
+ def round_price_to_tick(self,symbol, price) -> Decimal:
445
+ tick_size = self.get_tick_size(symbol)
438
446
  # 计算 tick_size 的整数倍
439
- tick_decimals = len(f"{tick_size:.10f}".rstrip('0').split('.')[1]) if '.' in f"{tick_size:.10f}" else 0
447
+ # tick_decimals = len(f"{tick_size:.10f}".rstrip('0').split('.')[1]) if '.' in f"{tick_size:.10f}" else 0
440
448
 
441
449
  # 调整价格为 tick_size 的整数倍
442
- adjusted_price = round(price / tick_size) * tick_size
443
- return f"{adjusted_price:.{tick_decimals}f}"
444
- # 放弃当前委托
450
+ adjusted_price = round(Decimal(price) / tick_size) * tick_size
451
+ return adjusted_price
452
+
445
453
 
446
454
  def cancel_all_algo_orders(self, symbol, attachType=None) -> bool:
447
455
  """_summary_
@@ -515,13 +523,13 @@ class TrailingSLTaker:
515
523
 
516
524
 
517
525
  # 止损单逻辑
518
- adjusted_price = float(self.round_price_to_tick(symbol, take_profit_price))
526
+ adjusted_price = self.round_price_to_tick(symbol, take_profit_price)
519
527
 
520
528
  tp_params = {
521
529
 
522
530
 
523
- 'tpTriggerPx':adjusted_price,
524
- 'tpOrdPx' : adjusted_price,
531
+ 'tpTriggerPx':str(adjusted_price),
532
+ 'tpOrdPx' : str(adjusted_price),
525
533
  'tpOrdKind': 'condition',
526
534
  'tpTriggerPxType':'last',
527
535
 
@@ -604,11 +612,11 @@ class TrailingSLTaker:
604
612
  return
605
613
 
606
614
  # 止损单逻辑
607
- adjusted_price = float(self.round_price_to_tick(symbol, stop_loss_price))
615
+ adjusted_price = self.round_price_to_tick(symbol, stop_loss_price)
608
616
 
609
617
  # 默认市价止损,委托价格为-1时,执行市价止损。
610
618
  sl_params = {
611
- 'slTriggerPx':adjusted_price ,
619
+ 'slTriggerPx':str(adjusted_price) ,
612
620
  'slOrdPx':'-1', # 委托价格为-1时,执行市价止损
613
621
  # 'slOrdPx' : adjusted_price,
614
622
  'slTriggerPxType':'last',
@@ -673,7 +681,7 @@ class TrailingSLTaker:
673
681
  return True
674
682
 
675
683
 
676
- def calculate_sl_price_by_pct(self, symbol, position, sl_pct) -> float:
684
+ def calculate_sl_price_by_pct(self, symbol, position, sl_pct) -> Decimal:
677
685
  side = position['side']
678
686
  if side == 'long':
679
687
  sl_price = position['entryPrice'] * (1 - sl_pct/100)
@@ -682,7 +690,7 @@ class TrailingSLTaker:
682
690
  sl_price = position['entryPrice'] * (1 + sl_pct/100)
683
691
 
684
692
 
685
- sl_order_price = float(self.round_price_to_tick(symbol, sl_price))
693
+ sl_order_price = self.round_price_to_tick(symbol, sl_price)
686
694
 
687
695
  return sl_order_price
688
696
 
@@ -712,16 +720,13 @@ class TrailingSLTaker:
712
720
 
713
721
  # 验证止损价格是否合理20250312
714
722
  mark_price = float(position['markPrice'])
715
- if side == 'long':
716
- if sl_order_price >= mark_price:
717
- self.close_all_positions(symbol,position)
718
- self.logger.warning(f"{symbol}: !! 多头止损价格= {sl_order_price} 大于等于市场价格= {mark_price},触发止损")
719
- return
720
- else: # side == 'short'
721
- if sl_order_price <= mark_price:
722
- self.close_all_positions(symbol,position)
723
- self.logger.warning(f"{symbol}: !! 空头止损价格= {sl_order_price} 小于等于市场价格= {mark_price},触发止损")
724
- return
723
+ # 验证止损价格是否合理,如果止损价格已经触发则直接平仓
724
+ if (side == 'long' and sl_order_price >= mark_price) or \
725
+ (side == 'short' and sl_order_price <= mark_price):
726
+ self.close_all_positions(symbol, position)
727
+ direction = "多头" if side == "long" else "空头"
728
+ self.logger.warning(f"{symbol}: !! {direction}止损价格= {sl_order_price} {'大于等于' if side=='long' else '小于等于'}市场价格= {mark_price},触发止损")
729
+ return
725
730
 
726
731
  # 250228 没有指定止损回撤阈值stop_loss_pct...
727
732
  open_swing_point = pair_config.get('open_swing_point', False)
@@ -736,12 +741,12 @@ class TrailingSLTaker:
736
741
  if len(isolated_points) > 0 :
737
742
  last_isolated_point = isolated_points[0]
738
743
 
744
+ # 多头仓位,取孤立点的低点和原止损价的最小值作为新的止损价
739
745
  if side == 'long':
740
-
741
- sl_order_price = min(last_isolated_point['low'],sl_order_price)
746
+ sl_order_price = min(last_isolated_point['low'], sl_order_price)
747
+ # 空头仓位,取孤立点的高点和原止损价的最大值作为新的止损价
742
748
  else:
743
-
744
- sl_order_price = max(last_isolated_point['high'],sl_order_price)
749
+ sl_order_price = max(last_isolated_point['high'], sl_order_price)
745
750
 
746
751
  self.logger.debug(f"{symbol} : {side} 止损价={sl_order_price} 孤立点=\n{last_isolated_point} ")
747
752
 
@@ -775,7 +780,7 @@ class TrailingSLTaker:
775
780
  traceback.print_exc()
776
781
  self.send_feishu_notification(error_msg)
777
782
 
778
- def calculate_take_profile_price(self, symbol, position, take_profile_pct, offset=1) -> float:
783
+ def calculate_take_profile_price(self, symbol, position, take_profile_pct, offset=1) -> Decimal:
779
784
  tick_size = self.get_tick_size(symbol)
780
785
 
781
786
  entry_price = position['entryPrice']
@@ -784,15 +789,15 @@ class TrailingSLTaker:
784
789
  # 计算止盈价格。
785
790
 
786
791
  if side == 'long':
787
- take_profile_price = entry_price * (1+take_profile_pct/100) - offset * tick_size
792
+ take_profile_price = Decimal(entry_price * (1+take_profile_pct/100)) - offset * tick_size
788
793
 
789
794
 
790
795
  elif side == 'short':
791
796
 
792
797
  # base_price = entry_price * (1-take_profile_pct)
793
- take_profile_price = entry_price * (1-take_profile_pct/100) + offset * tick_size
798
+ take_profile_price = Decimal(entry_price * (1-take_profile_pct/100)) + offset * tick_size
794
799
 
795
- return float(self.round_price_to_tick(symbol,take_profile_price))
800
+ return Decimal(self.round_price_to_tick(symbol,take_profile_price))
796
801
 
797
802
  # 计算回撤止盈价格
798
803
  def calculate_stop_loss_price(self, symbol, position, stop_loss_pct, offset=1) -> float:
@@ -807,7 +812,7 @@ class TrailingSLTaker:
807
812
  last_max_market_price = self.max_market_price.get(symbol,0.0)
808
813
  self.max_market_price[symbol] = max(market_price,last_max_market_price)
809
814
  base_price = abs(self.max_market_price[symbol] - entry_price) * (1-stop_loss_pct)
810
- stop_loss_price = entry_price + base_price - offset * tick_size
815
+ stop_loss_price = Decimal(entry_price + base_price) - offset * tick_size
811
816
  if latest_stop_loss_price :
812
817
  stop_loss_price = max(stop_loss_price,latest_stop_loss_price)
813
818
 
@@ -815,7 +820,7 @@ class TrailingSLTaker:
815
820
  last_min_market_price = self.min_market_price.get(symbol,float('inf'))
816
821
  self.min_market_price[symbol] = min(market_price,last_min_market_price)
817
822
  base_price = abs(self.min_market_price[symbol] - entry_price) * (1-stop_loss_pct)
818
- stop_loss_price = entry_price - base_price + offset * tick_size
823
+ stop_loss_price = Decimal(entry_price - base_price) + offset * tick_size
819
824
  if latest_stop_loss_price :
820
825
  stop_loss_price = min(stop_loss_price,latest_stop_loss_price)
821
826
  return float(self.round_price_to_tick(symbol,stop_loss_price))
@@ -1025,14 +1030,19 @@ class TrailingSLTaker:
1025
1030
 
1026
1031
  def monitor_total_profit(self):
1027
1032
  self.logger.info("启动主循环,开始监控总盈利...")
1028
- # previous_position_size = sum(
1029
- # abs(float(position['contracts'])) for position in self.fetch_positions()) # 初始总仓位大小
1033
+
1030
1034
  while True:
1031
1035
  try:
1032
-
1033
1036
  positions = self.fetch_positions()
1037
+
1038
+ except Exception as e:
1039
+ self.logger.warning(f"获取仓位信息时发生错误: {e}")
1040
+ time.sleep(1)
1041
+ continue
1042
+
1043
+ try:
1034
1044
  # 检查是否有仓位
1035
- if not positions:
1045
+ if len(positions) == 0:
1036
1046
  # self.logger.debug("没有持仓,等待下一次检查...")
1037
1047
  self.close_all_cache()
1038
1048
  time.sleep(1)
@@ -1077,6 +1087,7 @@ class TrailingSLTaker:
1077
1087
  traceback.print_exc()
1078
1088
  self.logger.error(error_message)
1079
1089
  self.send_feishu_notification(error_message)
1090
+ self.logger.info("-" * 60)
1080
1091
  continue
1081
1092
  except KeyboardInterrupt:
1082
1093
  self.logger.info("程序收到中断信号,开始退出...")
taker/main.py CHANGED
@@ -6,6 +6,7 @@ from taker.TrailingSLTaker import TrailingSLTaker
6
6
  from taker.TrailingSLAndTPTaker import TrailingSLAndTPTaker
7
7
  from taker.ThreeLineTradingTaker import ThreeLineTradingTaker
8
8
  from taker.SMCSLAndTPTaker import SMCSLAndTPTaker
9
+ from pyfiglet import Figlet
9
10
 
10
11
  def build_logger(log_config) -> logging.Logger:
11
12
  # 配置日志
@@ -64,6 +65,8 @@ def main():
64
65
  bot = strategy_class(config_data, platform_config, feishu_webhook=feishu_webhook_url, monitor_interval=monitor_interval,logger=logger)
65
66
  logger.info(f" ++ {package_name}.{taker}:{version} is doing...")
66
67
 
68
+ f = Figlet(font="standard") # 字体可选(如 "block", "bubble")
69
+ logger.info(f"\n{f.renderText("OpenFund Taker")}")
67
70
  bot.monitor_total_profit()
68
71
  # bot = ThreeLineTradingBot(platform_config, feishu_webhook=feishu_webhook_url, monitor_interval=monitor_interval)
69
72
  # bot.monitor_klines()