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.
- {openfund_taker-2.1.15.dist-info → openfund_taker-2.2.2.dist-info}/METADATA +2 -2
- {openfund_taker-2.1.15.dist-info → openfund_taker-2.2.2.dist-info}/RECORD +7 -7
- taker/SMCSLAndTPTaker.py +232 -69
- taker/TrailingSLTaker.py +75 -55
- taker/main.py +3 -0
- {openfund_taker-2.1.15.dist-info → openfund_taker-2.2.2.dist-info}/WHEEL +0 -0
- {openfund_taker-2.1.15.dist-info → openfund_taker-2.2.2.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.
|
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=
|
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=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
|
14
|
-
openfund_taker-2.
|
15
|
-
openfund_taker-2.
|
16
|
-
openfund_taker-2.
|
17
|
-
openfund_taker-2.
|
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.
|
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
@@ -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
|
-
|
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:
|
@@ -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
|
-
|
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 =
|
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
|
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 =
|
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 =
|
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) ->
|
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 =
|
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
|
-
|
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
|
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
|
-
|
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) ->
|
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
|
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
|
-
|
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
|
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()
|
File without changes
|
File without changes
|