openfund-taker 2.1.15__py3-none-any.whl → 2.2.2__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.15
3
+ Version: 2.2.2
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=WmgWfQim1cu5l2NQT4dfmSYaerhjYUUMKVKTF54W_y4,48884
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.15.dist-info/METADATA,sha256=TbLpFl75HpRslZyTGhxBjoDl_QlZ242l_nU8KnUfDEQ,7528
15
- openfund_taker-2.1.15.dist-info/WHEEL,sha256=IYZQI976HJqqOpQU6PHkJ8fb3tMNBFjg-Cn-pwAbaFM,88
16
- openfund_taker-2.1.15.dist-info/entry_points.txt,sha256=a7mG8F7aOA5-Gk2vPWuAR4537faxaHUgM_jwIDBZoEc,50
17
- openfund_taker-2.1.15.dist-info/RECORD,,
13
+ taker/main.py,sha256=3M1y_1sRIfkYNGWP0Wb3DfZFgHb0y4Cd45ERPnc1J9w,2927
14
+ openfund_taker-2.2.2.dist-info/METADATA,sha256=UrFYvntwGqdn1STVAn8ieZ0LrXNARrrhSvGqIemP2fE,7529
15
+ openfund_taker-2.2.2.dist-info/WHEEL,sha256=IYZQI976HJqqOpQU6PHkJ8fb3tMNBFjg-Cn-pwAbaFM,88
16
+ openfund_taker-2.2.2.dist-info/entry_points.txt,sha256=a7mG8F7aOA5-Gk2vPWuAR4537faxaHUgM_jwIDBZoEc,50
17
+ openfund_taker-2.2.2.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
@@ -4,7 +4,8 @@ import time
4
4
  import requests
5
5
  import traceback
6
6
  import pandas as pd
7
- import talib as ta
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:
@@ -232,7 +240,16 @@ class TrailingSLTaker:
232
240
  macd['timestamp'] = pd.to_datetime(macd['timestamp'], unit='ms').dt.strftime('%m-%d %H:%M')
233
241
 
234
242
  # 使用 TA-Lib 计算 MACD
235
- macd[['macd', 'signal', 'hist']] = pd.DataFrame(ta.MACD(macd['close'], fastperiod=12, slowperiod=26, signalperiod=9)).T
243
+ # macd[['macd', 'signal', 'hist']] = pd.DataFrame(ta.MACD(macd['close'], fastperiod=12, slowperiod=26, signalperiod=9)).T
244
+ # 计算EMA12和EMA26
245
+ exp1 = macd['close'].ewm(span=12, adjust=False).mean()
246
+ exp2 = macd['close'].ewm(span=26, adjust=False).mean()
247
+ # 计算MACD线
248
+ macd['macd'] = exp1 - exp2
249
+ # 计算信号线
250
+ macd['signal'] = macd['macd'].ewm(span=9, adjust=False).mean()
251
+ # 计算柱状图
252
+ macd['hist'] = macd['macd'] - macd['signal']
236
253
  # self.logger.debug(f"{symbol} : MACD Values = \n {macd.tail(5)}")
237
254
 
238
255
 
@@ -379,7 +396,7 @@ class TrailingSLTaker:
379
396
 
380
397
  # 计算平均利润
381
398
  def calculate_average_profit(self,symbol,position):
382
- # positions = self.fetch_positions()
399
+
383
400
  total_profit_pct = 0.0
384
401
  num_positions = 0
385
402
 
@@ -433,15 +450,15 @@ class TrailingSLTaker:
433
450
  self.min_market_price[symbol] = float('inf') # 初始化为浮点数最大值
434
451
  self.cross_directions[symbol] = None
435
452
 
436
- def round_price_to_tick(self,symbol, price):
437
- tick_size = float(self.exchange.market(symbol)['info']['tickSz'])
453
+ def round_price_to_tick(self,symbol, price) -> Decimal:
454
+ tick_size = self.get_tick_size(symbol)
438
455
  # 计算 tick_size 的整数倍
439
- tick_decimals = len(f"{tick_size:.10f}".rstrip('0').split('.')[1]) if '.' in f"{tick_size:.10f}" else 0
456
+ # tick_decimals = len(f"{tick_size:.10f}".rstrip('0').split('.')[1]) if '.' in f"{tick_size:.10f}" else 0
440
457
 
441
458
  # 调整价格为 tick_size 的整数倍
442
- adjusted_price = round(price / tick_size) * tick_size
443
- return f"{adjusted_price:.{tick_decimals}f}"
444
- # 放弃当前委托
459
+ adjusted_price = round(Decimal(price) / tick_size) * tick_size
460
+ return adjusted_price
461
+
445
462
 
446
463
  def cancel_all_algo_orders(self, symbol, attachType=None) -> bool:
447
464
  """_summary_
@@ -515,13 +532,13 @@ class TrailingSLTaker:
515
532
 
516
533
 
517
534
  # 止损单逻辑
518
- adjusted_price = float(self.round_price_to_tick(symbol, take_profit_price))
535
+ adjusted_price = self.round_price_to_tick(symbol, take_profit_price)
519
536
 
520
537
  tp_params = {
521
538
 
522
539
 
523
- 'tpTriggerPx':adjusted_price,
524
- 'tpOrdPx' : adjusted_price,
540
+ 'tpTriggerPx':str(adjusted_price),
541
+ 'tpOrdPx' : str(adjusted_price),
525
542
  'tpOrdKind': 'condition',
526
543
  'tpTriggerPxType':'last',
527
544
 
@@ -604,11 +621,11 @@ class TrailingSLTaker:
604
621
  return
605
622
 
606
623
  # 止损单逻辑
607
- adjusted_price = float(self.round_price_to_tick(symbol, stop_loss_price))
624
+ adjusted_price = self.round_price_to_tick(symbol, stop_loss_price)
608
625
 
609
626
  # 默认市价止损,委托价格为-1时,执行市价止损。
610
627
  sl_params = {
611
- 'slTriggerPx':adjusted_price ,
628
+ 'slTriggerPx':str(adjusted_price) ,
612
629
  'slOrdPx':'-1', # 委托价格为-1时,执行市价止损
613
630
  # 'slOrdPx' : adjusted_price,
614
631
  'slTriggerPxType':'last',
@@ -673,7 +690,7 @@ class TrailingSLTaker:
673
690
  return True
674
691
 
675
692
 
676
- def calculate_sl_price_by_pct(self, symbol, position, sl_pct) -> float:
693
+ def calculate_sl_price_by_pct(self, symbol, position, sl_pct) -> Decimal:
677
694
  side = position['side']
678
695
  if side == 'long':
679
696
  sl_price = position['entryPrice'] * (1 - sl_pct/100)
@@ -682,7 +699,7 @@ class TrailingSLTaker:
682
699
  sl_price = position['entryPrice'] * (1 + sl_pct/100)
683
700
 
684
701
 
685
- sl_order_price = float(self.round_price_to_tick(symbol, sl_price))
702
+ sl_order_price = self.round_price_to_tick(symbol, sl_price)
686
703
 
687
704
  return sl_order_price
688
705
 
@@ -712,16 +729,13 @@ class TrailingSLTaker:
712
729
 
713
730
  # 验证止损价格是否合理20250312
714
731
  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
732
+ # 验证止损价格是否合理,如果止损价格已经触发则直接平仓
733
+ if (side == 'long' and sl_order_price >= mark_price) or \
734
+ (side == 'short' and sl_order_price <= mark_price):
735
+ self.close_all_positions(symbol, position)
736
+ direction = "多头" if side == "long" else "空头"
737
+ self.logger.warning(f"{symbol}: !! {direction}止损价格= {sl_order_price} {'大于等于' if side=='long' else '小于等于'}市场价格= {mark_price},触发止损")
738
+ return
725
739
 
726
740
  # 250228 没有指定止损回撤阈值stop_loss_pct...
727
741
  open_swing_point = pair_config.get('open_swing_point', False)
@@ -736,12 +750,12 @@ class TrailingSLTaker:
736
750
  if len(isolated_points) > 0 :
737
751
  last_isolated_point = isolated_points[0]
738
752
 
753
+ # 多头仓位,取孤立点的低点和原止损价的最小值作为新的止损价
739
754
  if side == 'long':
740
-
741
- sl_order_price = min(last_isolated_point['low'],sl_order_price)
755
+ sl_order_price = min(last_isolated_point['low'], sl_order_price)
756
+ # 空头仓位,取孤立点的高点和原止损价的最大值作为新的止损价
742
757
  else:
743
-
744
- sl_order_price = max(last_isolated_point['high'],sl_order_price)
758
+ sl_order_price = max(last_isolated_point['high'], sl_order_price)
745
759
 
746
760
  self.logger.debug(f"{symbol} : {side} 止损价={sl_order_price} 孤立点=\n{last_isolated_point} ")
747
761
 
@@ -775,7 +789,7 @@ class TrailingSLTaker:
775
789
  traceback.print_exc()
776
790
  self.send_feishu_notification(error_msg)
777
791
 
778
- def calculate_take_profile_price(self, symbol, position, take_profile_pct, offset=1) -> float:
792
+ def calculate_take_profile_price(self, symbol, position, take_profile_pct, offset=1) -> Decimal:
779
793
  tick_size = self.get_tick_size(symbol)
780
794
 
781
795
  entry_price = position['entryPrice']
@@ -784,15 +798,15 @@ class TrailingSLTaker:
784
798
  # 计算止盈价格。
785
799
 
786
800
  if side == 'long':
787
- take_profile_price = entry_price * (1+take_profile_pct/100) - offset * tick_size
801
+ take_profile_price = Decimal(entry_price * (1+take_profile_pct/100)) - offset * tick_size
788
802
 
789
803
 
790
804
  elif side == 'short':
791
805
 
792
806
  # base_price = entry_price * (1-take_profile_pct)
793
- take_profile_price = entry_price * (1-take_profile_pct/100) + offset * tick_size
807
+ take_profile_price = Decimal(entry_price * (1-take_profile_pct/100)) + offset * tick_size
794
808
 
795
- return float(self.round_price_to_tick(symbol,take_profile_price))
809
+ return Decimal(self.round_price_to_tick(symbol,take_profile_price))
796
810
 
797
811
  # 计算回撤止盈价格
798
812
  def calculate_stop_loss_price(self, symbol, position, stop_loss_pct, offset=1) -> float:
@@ -807,7 +821,7 @@ class TrailingSLTaker:
807
821
  last_max_market_price = self.max_market_price.get(symbol,0.0)
808
822
  self.max_market_price[symbol] = max(market_price,last_max_market_price)
809
823
  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
824
+ stop_loss_price = Decimal(entry_price + base_price) - offset * tick_size
811
825
  if latest_stop_loss_price :
812
826
  stop_loss_price = max(stop_loss_price,latest_stop_loss_price)
813
827
 
@@ -815,7 +829,7 @@ class TrailingSLTaker:
815
829
  last_min_market_price = self.min_market_price.get(symbol,float('inf'))
816
830
  self.min_market_price[symbol] = min(market_price,last_min_market_price)
817
831
  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
832
+ stop_loss_price = Decimal(entry_price - base_price) + offset * tick_size
819
833
  if latest_stop_loss_price :
820
834
  stop_loss_price = min(stop_loss_price,latest_stop_loss_price)
821
835
  return float(self.round_price_to_tick(symbol,stop_loss_price))
@@ -1025,14 +1039,19 @@ class TrailingSLTaker:
1025
1039
 
1026
1040
  def monitor_total_profit(self):
1027
1041
  self.logger.info("启动主循环,开始监控总盈利...")
1028
- # previous_position_size = sum(
1029
- # abs(float(position['contracts'])) for position in self.fetch_positions()) # 初始总仓位大小
1042
+
1030
1043
  while True:
1031
1044
  try:
1032
-
1033
1045
  positions = self.fetch_positions()
1046
+
1047
+ except Exception as e:
1048
+ self.logger.warning(f"获取仓位信息时发生错误: {e}")
1049
+ time.sleep(1)
1050
+ continue
1051
+
1052
+ try:
1034
1053
  # 检查是否有仓位
1035
- if not positions:
1054
+ if len(positions) == 0:
1036
1055
  # self.logger.debug("没有持仓,等待下一次检查...")
1037
1056
  self.close_all_cache()
1038
1057
  time.sleep(1)
@@ -1077,6 +1096,7 @@ class TrailingSLTaker:
1077
1096
  traceback.print_exc()
1078
1097
  self.logger.error(error_message)
1079
1098
  self.send_feishu_notification(error_message)
1099
+ self.logger.info("-" * 60)
1080
1100
  continue
1081
1101
  except KeyboardInterrupt:
1082
1102
  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()