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.
- {openfund_taker-2.1.14.dist-info → openfund_taker-2.2.1.dist-info}/METADATA +2 -2
- {openfund_taker-2.1.14.dist-info → openfund_taker-2.2.1.dist-info}/RECORD +7 -7
- taker/SMCSLAndTPTaker.py +232 -69
- taker/TrailingSLTaker.py +64 -53
- taker/main.py +3 -0
- {openfund_taker-2.1.14.dist-info → openfund_taker-2.2.1.dist-info}/WHEEL +0 -0
- {openfund_taker-2.1.14.dist-info → openfund_taker-2.2.1.dist-info}/entry_points.txt +0 -0
@@ -1,6 +1,6 @@
|
|
1
1
|
Metadata-Version: 2.3
|
2
2
|
Name: openfund-taker
|
3
|
-
Version: 2.1
|
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=
|
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=
|
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
|
14
|
-
openfund_taker-2.1.
|
15
|
-
openfund_taker-2.1.
|
16
|
-
openfund_taker-2.1.
|
17
|
-
openfund_taker-2.1.
|
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.
|
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
|
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 +
|
178
|
-
return all(current_high >= prev_highs) and all(current_high
|
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
|
193
|
-
return all(current_low <= prev_lows) and all(current_low
|
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
|
-
|
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
|
213
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
260
|
+
# },
|
261
|
+
# "中档": {
|
262
|
+
# "stop_loss_pct": float(pair_config.get('trail_stop_loss_pct',self.trail_stop_loss_pct)),
|
229
263
|
|
230
|
-
|
231
|
-
|
232
|
-
|
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
|
-
|
271
|
+
# if current_tier in tier_config and self.open_trail_profit:
|
272
|
+
# config = tier_config[current_tier]
|
239
273
|
|
240
274
|
|
241
|
-
|
242
|
-
|
243
|
-
|
244
|
-
|
245
|
-
|
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.
|
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}:
|
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
|
-
|
269
|
-
|
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,
|
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
|
-
|
327
|
+
liquidity_type (str): 流动性类型,'BSL' 或 'BSL'
|
328
|
+
|
288
329
|
"""
|
289
330
|
df = data.copy()
|
290
331
|
|
291
|
-
is_buy =
|
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
|
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
|
-
|
357
|
-
|
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
|
360
|
-
threshold
|
361
|
-
|
362
|
-
|
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.
|
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.
|
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.
|
584
|
+
htf_liquidity = self.htf_liquidities_TP.get(symbol,None)
|
422
585
|
if htf_liquidity is None:
|
423
|
-
htf_liquidity = self.
|
424
|
-
self.
|
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
|
-
|
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
|
-
|
102
|
-
else:
|
103
|
-
|
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
|
-
|
109
|
-
|
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
|
-
|
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 =
|
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
|
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 =
|
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 =
|
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) ->
|
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 =
|
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
|
-
|
716
|
-
|
717
|
-
|
718
|
-
|
719
|
-
|
720
|
-
|
721
|
-
|
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
|
-
|
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) ->
|
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
|
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
|
-
|
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
|
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()
|
File without changes
|
File without changes
|