openfund-taker 1.2.13__py3-none-any.whl → 1.3.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-1.2.13.dist-info → openfund_taker-1.3.2.dist-info}/METADATA +2 -2
- {openfund_taker-1.2.13.dist-info → openfund_taker-1.3.2.dist-info}/RECORD +7 -6
- taker/TrailingSLAndTPTaker.py +28 -0
- taker/{MultiAssetNewTradingBot.py → TrailingSLTaker.py} +331 -286
- taker/main.py +3 -2
- {openfund_taker-1.2.13.dist-info → openfund_taker-1.3.2.dist-info}/WHEEL +0 -0
- {openfund_taker-1.2.13.dist-info → openfund_taker-1.3.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: 1.2
|
3
|
+
Version: 1.3.2
|
4
4
|
Summary: Openfund-taker
|
5
5
|
Requires-Python: >=3.9,<4.0
|
6
6
|
Classifier: Programming Language :: Python :: 3
|
@@ -9,7 +9,7 @@ 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.
|
12
|
+
Requires-Dist: TA-Lib (>=0.5.1,<0.6.0)
|
13
13
|
Requires-Dist: ccxt (>=4.4.26,<5.0.0)
|
14
14
|
Requires-Dist: pandas (>=2.2.3,<3.0.0)
|
15
15
|
Requires-Dist: pyyaml (>=6.0.2,<7.0.0)
|
@@ -1,6 +1,7 @@
|
|
1
|
-
taker/MultiAssetNewTradingBot.py,sha256=bpgI6eewTwd7-Q5u2Au0UXd-mvFT1UM9S3H4CXPzf4g,42412
|
2
1
|
taker/MultiAssetOldTradingBot.py,sha256=uBh_BxglvcbaHIsWHM7GI9Qa_QjzsxXaXJAAWEOMO5c,15315
|
3
2
|
taker/ThreeLineTradingBot.py,sha256=oXIoQ8z9AzKzk0z13d0ufj2KGBOk5iHJTJNZQRDKA5U,20625
|
3
|
+
taker/TrailingSLAndTPTaker.py,sha256=gikp-Yahn4nWYk6PmUB02t8Sun--Fo4I7vDEU4U2uso,1263
|
4
|
+
taker/TrailingSLTaker.py,sha256=LL6BxJQo6yhkruXRDA0jQMt9K-GxhMZiTmKrWwIuZzo,42277
|
4
5
|
taker/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
5
6
|
taker/chua_bitget.py,sha256=YY6XK5Bd-wlArsN5BnAfiqE0h1DPkti6i4TEB4F5zDA,12918
|
6
7
|
taker/chua_bn.py,sha256=GnTePWlgDwdHgroBbEp1Ajcsf5_m_Vn_RV63SYzu2jI,10668
|
@@ -8,8 +9,8 @@ taker/chua_ok.py,sha256=5pPAoEYbFuKxfZwqNvOO890s-2cy6n69QiI0ZA0GTCQ,12474
|
|
8
9
|
taker/chua_ok_all.py,sha256=2XnZM6QdB3juSE1pqQIJyh2x1XuhlTlnBKNA3owlJ9E,15267
|
9
10
|
taker/chua_ok_bot.py,sha256=9SW0ujhi6PfN4yR1JZ9NaA37HtnXJ2QAWUfW52NG68w,13109
|
10
11
|
taker/config.py,sha256=YPxghO5i0vgRg9Cja8kGj9O7pgSbbtzOgf3RexqXXwY,1188
|
11
|
-
taker/main.py,sha256=
|
12
|
-
openfund_taker-1.2.
|
13
|
-
openfund_taker-1.2.
|
14
|
-
openfund_taker-1.2.
|
15
|
-
openfund_taker-1.2.
|
12
|
+
taker/main.py,sha256=CYibDo9PEOWbhpSXZ5JTmE-NGJXqDdgvhpadXJNRX60,2557
|
13
|
+
openfund_taker-1.3.2.dist-info/METADATA,sha256=JG97fvJiR96kRpwabOg1MlIRSTNGoBOX9A2bsymTm94,7527
|
14
|
+
openfund_taker-1.3.2.dist-info/WHEEL,sha256=IYZQI976HJqqOpQU6PHkJ8fb3tMNBFjg-Cn-pwAbaFM,88
|
15
|
+
openfund_taker-1.3.2.dist-info/entry_points.txt,sha256=a7mG8F7aOA5-Gk2vPWuAR4537faxaHUgM_jwIDBZoEc,50
|
16
|
+
openfund_taker-1.3.2.dist-info/RECORD,,
|
@@ -0,0 +1,28 @@
|
|
1
|
+
# -*- coding: utf-8 -*-
|
2
|
+
import ccxt
|
3
|
+
import time
|
4
|
+
import requests
|
5
|
+
import traceback
|
6
|
+
import pandas as pd
|
7
|
+
import talib as ta
|
8
|
+
from taker.TrailingSLTaker import TrailingSLTaker
|
9
|
+
'''
|
10
|
+
自动设置移动止损单
|
11
|
+
'''
|
12
|
+
class TrailingSLAndTPTaker(TrailingSLTaker):
|
13
|
+
def __init__(self,g_config, platform_config, feishu_webhook=None, monitor_interval=4,logger=None):
|
14
|
+
super().__init__(g_config, platform_config, feishu_webhook, monitor_interval,logger)
|
15
|
+
self.all_TP_LS_ratio = float(platform_config.get("all_TP_LS_ratio",1.5)) #The profit-loss ratio 盈亏比
|
16
|
+
self.all_take_profit_pct = self.stop_loss_pct * self.all_TP_LS_ratio
|
17
|
+
|
18
|
+
def set_stop_loss_take_profit(self, symbol, position, stop_loss_price=None, take_profit_price=None) -> bool:
|
19
|
+
is_successful = super().set_stop_loss_take_profit(symbol, position, stop_loss_price, take_profit_price)
|
20
|
+
# take_profile_pct = self.stop_loss_pct * 2
|
21
|
+
order_take_profit_price = take_profit_price
|
22
|
+
if take_profit_price is None:
|
23
|
+
order_take_profit_price = self.calculate_take_profile_price(symbol, position, self.all_take_profit_pct)
|
24
|
+
is_successful = self.set_take_profit(symbol, position, order_take_profit_price)
|
25
|
+
|
26
|
+
|
27
|
+
return is_successful
|
28
|
+
|
@@ -4,13 +4,17 @@ import time
|
|
4
4
|
import requests
|
5
5
|
import traceback
|
6
6
|
import pandas as pd
|
7
|
-
|
8
|
-
|
7
|
+
import talib as ta
|
8
|
+
'''
|
9
|
+
自动设置移动止损单
|
10
|
+
'''
|
11
|
+
class TrailingSLTaker:
|
9
12
|
def __init__(self,g_config, platform_config, feishu_webhook=None, monitor_interval=4,logger=None):
|
10
13
|
self.trading_pairs_config = g_config.get('tradingPairs', {})
|
11
14
|
|
12
|
-
|
13
|
-
#
|
15
|
+
|
16
|
+
self.stop_loss_pct = float(platform_config.get("all_stop_loss_pct",2)) # 全局止损百分比
|
17
|
+
# 回撤比例
|
14
18
|
self.low_trail_stop_loss_pct = platform_config["all_low_trail_stop_loss_pct"] # 第一档
|
15
19
|
self.trail_stop_loss_pct = platform_config["all_trail_stop_loss_pct"]# 第二档
|
16
20
|
self.higher_trail_stop_loss_pct = platform_config["all_higher_trail_stop_loss_pct"]# 第三档
|
@@ -22,14 +26,17 @@ class MultiAssetNewTradingBot:
|
|
22
26
|
self.feishu_webhook = feishu_webhook
|
23
27
|
self.monitor_interval = monitor_interval # 监控循环时间是分仓监控的3倍
|
24
28
|
|
25
|
-
self.highest_total_profit =
|
26
|
-
|
29
|
+
self.highest_total_profit = {} # 记录最高总盈利
|
30
|
+
|
31
|
+
# self.current_tier = {} # 记录当前的仓位模式
|
32
|
+
|
33
|
+
self.positions_entry_price = {} # 记录每个symbol的开仓价格
|
27
34
|
|
28
35
|
self.global_symbol_stop_loss_flag = {} # 记录每个symbol是否设置全局止损
|
29
36
|
self.global_symbol_stop_loss_price = {} # 记录每个symbol的止损价格
|
30
37
|
# 保留在止盈挂单中最高最低两个价格,计算止盈价格。
|
31
|
-
self.max_market_price =
|
32
|
-
self.min_market_price =
|
38
|
+
self.max_market_price = {}
|
39
|
+
self.min_market_price = {}
|
33
40
|
|
34
41
|
self.cross_directions = {} # 持仓期间,存储每个交易对的交叉方向
|
35
42
|
|
@@ -104,7 +111,8 @@ class MultiAssetNewTradingBot:
|
|
104
111
|
self.logger.error(f"Error fetching open orders: {e}")
|
105
112
|
return []
|
106
113
|
|
107
|
-
|
114
|
+
# 获取历史K线,不包含最新的一条
|
115
|
+
def get_historical_klines_except_last(self,symbol, bar='1m', limit=241):
|
108
116
|
# response = market_api.get_candlesticks(instId, bar=bar, limit=limit)
|
109
117
|
params = {
|
110
118
|
# 'instId': instId,
|
@@ -113,180 +121,174 @@ class MultiAssetNewTradingBot:
|
|
113
121
|
# if 'data' in response and len(response['data']) > 0:
|
114
122
|
if klines :
|
115
123
|
# return response['data']
|
116
|
-
return klines
|
124
|
+
return klines[:-1]
|
117
125
|
else:
|
118
126
|
raise ValueError("Unexpected response structure or missing candlestick data")
|
119
127
|
|
120
|
-
def
|
121
|
-
#
|
122
|
-
df = pd.DataFrame(
|
123
|
-
|
124
|
-
'slow': slowklines
|
125
|
-
})
|
126
|
-
|
127
|
-
# 判断金叉和死叉
|
128
|
-
df['golden_cross'] = (df['fast'] > df['slow']) & (df['fast'].shift(1) < df['slow'].shift(1))
|
129
|
-
df['death_cross'] = (df['fast'] < df['slow']) & (df['fast'].shift(1) > df['slow'].shift(1))
|
130
|
-
|
131
|
-
# 从后往前找最近的交叉点
|
132
|
-
last_golden = df['golden_cross'].iloc[::-1].idxmax() if df['golden_cross'].any() else None
|
133
|
-
last_death = df['death_cross'].iloc[::-1].idxmax() if df['death_cross'].any() else None
|
134
|
-
# self.logger.debug(f"golden_cross = {last_golden}, death_cross = {last_death}")
|
128
|
+
def get_last_solated_point(self,symbol,position,kLines):
|
129
|
+
# 将K线数据转换为DataFrame格式,最后一个数据可能未完成。
|
130
|
+
df = pd.DataFrame(kLines, columns=['timestamp', 'open', 'high', 'low', 'close', 'volume'])
|
131
|
+
df['timestamp'] = pd.to_datetime(df['timestamp'], unit='ms')
|
135
132
|
|
136
|
-
#
|
137
|
-
|
138
|
-
|
139
|
-
return {
|
140
|
-
'cross': -1, # 无交叉
|
141
|
-
'index': None
|
142
|
-
}
|
133
|
+
# 根据position方向寻找孤立点
|
134
|
+
side = position['side']
|
135
|
+
window = 1 # 设置窗口大小,用于判断局部最值
|
143
136
|
|
144
|
-
|
145
|
-
|
146
|
-
|
147
|
-
|
148
|
-
|
149
|
-
|
150
|
-
|
137
|
+
if side == 'short':
|
138
|
+
# 找最近的孤立高点
|
139
|
+
# 判断局部最高点:当前点比前后window个点都高
|
140
|
+
df['is_high'] = df['high'].rolling(window=window*2+1, center=True).apply(
|
141
|
+
lambda x: x[window] > max(x[:window]) and x[window] > max(x[window+1:]), raw=True
|
142
|
+
)
|
143
|
+
# 获取最近的孤立高点
|
144
|
+
isolated_points = df[df['is_high']==True]['high'].iloc[-3:]
|
145
|
+
|
151
146
|
else:
|
152
|
-
|
153
|
-
|
154
|
-
|
155
|
-
|
156
|
-
|
157
|
-
|
158
|
-
|
159
|
-
|
147
|
+
# 找最近的孤立低点
|
148
|
+
# 判断局部最低点:当前点比前后window个点都低
|
149
|
+
df['is_low'] = df['low'].rolling(window=window*2+1, center=True).apply(
|
150
|
+
lambda x: x[window] < min(x[:window]) and x[window] < min(x[window+1:]), raw=True
|
151
|
+
)
|
152
|
+
# 获取最近的孤立低点
|
153
|
+
isolated_points = df[df['is_low']==True]['low'].iloc[-3:]
|
154
|
+
|
155
|
+
return isolated_points
|
156
|
+
|
157
|
+
|
158
|
+
def judge_correct_postion_side(self, symbol, pair_config, klines=None) -> str:
|
159
|
+
|
160
|
+
'''
|
161
|
+
零轴之上的macd与signal形成金叉
|
162
|
+
零轴之下的死叉
|
163
|
+
零轴之上的死叉-金叉-死叉
|
164
|
+
零轴之下的金叉-死叉-金叉
|
165
|
+
'''
|
166
|
+
|
167
|
+
order_side = 'none'
|
168
|
+
if 'macd_strategy' not in pair_config :
|
169
|
+
return order_side
|
160
170
|
|
161
|
-
|
162
|
-
|
163
|
-
|
164
|
-
|
165
|
-
#
|
166
|
-
|
167
|
-
#
|
168
|
-
df['diff'] = df['ema'].round(precision)-df['sma'].round(precision)
|
169
|
-
df['ema_diff'] = df['ema'] - df['ema'].shift(1)
|
170
|
-
df['sma_diff'] = df['sma'] - df['sma'].shift(1)
|
171
|
-
# 计算斜率,【正】表示两线距离扩张,【负】表示两线距离收缩
|
172
|
-
df['slope'] = df['diff'].abs().diff().round(4)
|
171
|
+
macd = pd.DataFrame(klines, columns=['timestamp', 'open', 'high', 'low', 'close', 'volume'])
|
172
|
+
# 将时间戳转换为日期时间格式
|
173
|
+
macd['timestamp'] = pd.to_datetime(macd['timestamp'], unit='ms').dt.strftime('%m-%d %H:%M')
|
174
|
+
|
175
|
+
# 使用 TA-Lib 计算 MACD
|
176
|
+
macd[['macd', 'signal', 'hist']] = pd.DataFrame(ta.MACD(macd['close'], fastperiod=12, slowperiod=26, signalperiod=9)).T
|
177
|
+
# self.logger.debug(f"{symbol} : MACD Values = \n {macd.tail(5)}")
|
173
178
|
|
174
|
-
self.logger.debug(f"{symbol}: slopes = \n{df[['ema','ema_diff','sma','sma_diff','diff','slope']].iloc[-6:-1]} ")
|
175
179
|
|
176
|
-
#
|
177
|
-
|
178
|
-
|
179
|
-
|
180
|
-
|
181
|
-
# 定义根据均线斜率判断 K 线方向的函数: 0 空 1 多 -1 平
|
182
|
-
|
183
|
-
def judge_range_diff(self,symbol,pair_config,prices:pd.Series) -> bool:
|
184
|
-
"""
|
185
|
-
计算价格列表中最后一个价格与第一个价格的差值。
|
186
|
-
Args:
|
187
|
-
prices: 价格列表。
|
188
|
-
Returns:
|
189
|
-
diff: 计算最高价列的最大值与最小值的差值
|
190
|
-
。
|
191
|
-
"""
|
192
|
-
limit = int(pair_config.get('ema_range_limit', 1))
|
193
|
-
period = int(pair_config.get('ema_range_period', 3))
|
194
|
-
tick_size = self.get_tick_size(symbol)
|
195
|
-
if prices.empty:
|
196
|
-
return None
|
197
|
-
|
198
|
-
diff = prices.tail(period).max() - prices.tail(period).min()
|
199
|
-
self.logger.debug(f"{symbol}: 最高价列的最大值与最小值的差值 = {diff:.9f}")
|
200
|
-
return abs(diff) <= tick_size * limit
|
201
|
-
|
202
|
-
# 定义根据均线斜率判断 K 线方向的函数: 0 空 1 多 -1 平
|
203
|
-
def judge_k_line_direction(self, symbol, pair_config, ema: pd.Series, klines) -> int:
|
204
|
-
"""
|
205
|
-
判断K线方向
|
206
|
-
Args:
|
207
|
-
symbol: 交易对
|
208
|
-
pair_config: 配置参数
|
209
|
-
ema: EMA数据
|
210
|
-
Returns:
|
211
|
-
int: -1:平, 0:空, 1:多
|
212
|
-
"""
|
213
|
-
# 获取配置参数
|
214
|
-
period = int(pair_config.get('ema_range_period', 3))
|
180
|
+
# 计算最近三个交叉点
|
181
|
+
last_up_crosses = []
|
182
|
+
last_down_crosses = []
|
183
|
+
other_crosses = []
|
184
|
+
all_cross = []
|
215
185
|
|
216
|
-
|
217
|
-
|
218
|
-
|
219
|
-
|
220
|
-
|
221
|
-
|
222
|
-
|
223
|
-
|
224
|
-
|
186
|
+
# 从最新K开始往前遍历
|
187
|
+
for i in range(len(macd)-1, 2, -1):
|
188
|
+
# 检查是否发生死叉(MACD从上方穿过Signal)
|
189
|
+
if (macd.iloc[i-1]['macd'] <= macd.iloc[i-1]['signal'] and
|
190
|
+
macd.iloc[i]['macd'] > macd.iloc[i]['signal']
|
191
|
+
):
|
192
|
+
all_cross.append(('golden', i))
|
193
|
+
|
194
|
+
# 判断如果都在零轴之上加入last_up_crosses , 判断如果都在零轴之下加入last_down_crosses
|
195
|
+
if macd.iloc[i]['macd'] > 0 and macd.iloc[i]['signal'] > 0 :
|
196
|
+
last_up_crosses.append(('golden', i))
|
197
|
+
elif macd.iloc[i]['macd'] < 0 and macd.iloc[i]['signal'] < 0 :
|
198
|
+
last_down_crosses.append(('golden', i))
|
199
|
+
else:
|
200
|
+
other_crosses.append(('golden', i))
|
201
|
+
|
202
|
+
# 检查是否发生死叉(MACD从上方穿过Signal)
|
203
|
+
elif macd.iloc[i-1]['macd'] >= macd.iloc[i-1]['signal'] and macd.iloc[i]['macd'] < macd.iloc[i]['signal']:
|
204
|
+
all_cross.append(('death', i))
|
205
|
+
# 判断如果都在零轴之上加入last_up_crosses , 判断如果都在零轴之下加入last_down_crosses
|
206
|
+
if macd.iloc[i]['macd'] > 0 and macd.iloc[i]['signal'] > 0 :
|
207
|
+
last_up_crosses.append(('death', i))
|
208
|
+
elif macd.iloc[i]['macd'] < 0 and macd.iloc[i]['signal'] < 0 :
|
209
|
+
last_down_crosses.append(('death', i))
|
210
|
+
else:
|
211
|
+
other_crosses.append(('golden', i))
|
212
|
+
# 只保留最后三个交叉点
|
213
|
+
if len(last_up_crosses) == 3 or len(last_down_crosses) == 3:
|
214
|
+
break
|
215
|
+
|
216
|
+
self.logger.debug(f"{symbol} : \n- 所有cross {all_cross} \n- 零轴之上cross {last_up_crosses} \n- 零轴之下cross {last_down_crosses} \n- 其他corss {other_crosses}。")
|
217
|
+
|
218
|
+
# valid_klines = pair_config['macd_strategy'].get('valid_klines', 5)
|
219
|
+
# 如果最新的交叉是金叉,且又是零轴上方的金叉
|
220
|
+
if len(last_up_crosses) > 0 and all_cross[0][0] == 'golden' and all_cross[0][1] == last_up_crosses[0][1] :
|
221
|
+
order_side = 'long'
|
222
|
+
self.logger.debug(f"{symbol} : 零轴之上的macd与signal形成金叉{all_cross[0]} 。")
|
223
|
+
|
224
|
+
# 如果最新的交叉是死叉,且又是零轴下方的死叉
|
225
|
+
elif len(last_down_crosses) > 0 and all_cross[0][0] == 'death' and all_cross[0][1] == last_down_crosses[0][1] :
|
226
|
+
order_side ='short'
|
227
|
+
self.logger.debug(f"{symbol} : 零轴之下的macd与signal形成死叉{all_cross[0]} 。")
|
228
|
+
# 分析交叉点模式,要满足连续的三个交叉都是零上
|
229
|
+
elif len(last_up_crosses) == 3 and len(all_cross) == 3:
|
230
|
+
|
231
|
+
# 零轴之上的死叉-金叉-死叉模式
|
232
|
+
if (last_up_crosses[0][0] == 'death' and
|
233
|
+
last_up_crosses[1][0] == 'golden' and
|
234
|
+
last_up_crosses[2][0] == 'death'
|
235
|
+
):
|
236
|
+
order_side = 'short'
|
237
|
+
self.logger.debug(f"{symbol} : 零轴之上的死叉-金叉-死叉模式 {order_side}。")
|
238
|
+
|
239
|
+
elif len(last_down_crosses) == 3 and len(all_cross) == 3:
|
240
|
+
# 零轴之下的金叉-死叉-金叉模式
|
241
|
+
if (last_down_crosses[0][0] == 'golden' and
|
242
|
+
last_down_crosses[1][0] == 'death' and
|
243
|
+
last_down_crosses[2][0] == 'golden'
|
244
|
+
):
|
245
|
+
order_side = 'long'
|
246
|
+
self.logger.debug(f"{symbol} : 零轴之下的金叉-死叉-金叉模式 {order_side}。")
|
247
|
+
|
248
|
+
return order_side
|
249
|
+
|
250
|
+
def is_profitable(self, symbol ,position) -> bool:
|
251
|
+
# 判断是否进入浮赢阶段
|
252
|
+
if position['side'] == 'long':
|
253
|
+
# 多单判断持仓价格是否小于止损价格
|
254
|
+
return position['entryPrice'] < self.global_symbol_stop_loss_price.get(symbol,0.0)
|
225
255
|
else:
|
226
|
-
#
|
227
|
-
|
228
|
-
|
229
|
-
return direction
|
230
|
-
|
256
|
+
# 空单判断持仓价格是否大于止损价格
|
257
|
+
return position['entryPrice'] > self.global_symbol_stop_loss_price.get(symbol,0.0)
|
258
|
+
|
231
259
|
def check_reverse_position(self,symbol,position,pair_config):
|
260
|
+
if 'entryPrice' not in position or self.is_profitable(symbol,position):
|
261
|
+
self.logger.debug(f"{symbol} : 方向={position['side']} 经进入浮赢阶段,不校验全局止损位置,止损价={self.global_symbol_stop_loss_price} ,开仓价={position['entryPrice']}。")
|
262
|
+
return
|
263
|
+
|
232
264
|
side = position['side']
|
233
265
|
try:
|
234
|
-
klines_period = str(pair_config.get('klines_period', '1m'))
|
235
|
-
klines = self.get_historical_klines(symbol=symbol,bar=klines_period)
|
236
266
|
|
237
|
-
|
238
|
-
|
239
|
-
sma_length = pair_config.get('sma', 50)
|
240
|
-
|
241
|
-
# 增加 金叉死叉 方向确认的 20250209
|
242
|
-
fastk = self.calculate_ema_pandas(symbol, klines, period=ema_length)
|
243
|
-
slowk = self.calculate_sma_pandas(symbol, klines, period=sma_length)
|
244
|
-
|
245
|
-
cross_direction = self.judge_cross_direction(symbol=symbol,fastklines=fastk,slowklines=slowk)
|
267
|
+
klines_period = str(pair_config.get('klines_period', '1m'))
|
268
|
+
klines = self.get_historical_klines_except_last(symbol=symbol,bar=klines_period)
|
246
269
|
|
247
|
-
|
248
|
-
if cross_direction['cross'] != -1 : #本次不一定有交叉
|
249
|
-
self.cross_directions[symbol] = cross_direction
|
270
|
+
self.logger.debug(f"开始监控 {symbol} : klines {klines_period} - {len(klines)}")
|
250
271
|
|
251
|
-
|
252
|
-
last_cross_direction = self.exchange.safe_dict(self.cross_directions,symbol,None)
|
253
|
-
# 计算 快线EMA & 慢线SMA
|
254
|
-
# 结合金叉死叉判断是否是周期顶部和底部
|
255
|
-
is_apex = self.judge_ma_apex(symbol=symbol,pair_config=pair_config, fastklines=fastk,slowklines=slowk)
|
272
|
+
correct_side = self.judge_correct_postion_side(symbol=symbol, pair_config=pair_config, klines=klines)
|
256
273
|
|
257
|
-
kline_direction = self.judge_k_line_direction(symbol=symbol, pair_config=pair_config, ema=fastk, klines=klines)
|
258
|
-
# if_inner_range = self.judge_range_diff(symbol=symbol, pair_config=pair_config, prices=fastk)
|
259
|
-
|
260
|
-
self.logger.debug(f"{symbol} cross={last_cross_direction},两线收缩={is_apex},持仓方向={side} ,K线方向={kline_direction}")
|
261
274
|
order_stop_loss_pct = None
|
262
|
-
#
|
263
|
-
|
264
|
-
|
265
|
-
self.logger.debug(f"{symbol} 金叉:{last_cross_direction['cross']},两线收缩={is_apex},持仓方向={side} ,K线方向={kline_direction} ,开始清理多单!!")
|
266
|
-
# self.close_all_positions(symbol=symbol, position=position)
|
275
|
+
# 方向不一致 尽快平仓
|
276
|
+
if correct_side != 'none' and correct_side != side :
|
277
|
+
self.logger.info(f"{symbol}: 持仓方向={side} 与 正确方向={correct_side} 相反 , 减少止损。")
|
267
278
|
order_stop_loss_pct = self.stop_loss_pct / 2
|
268
|
-
self.logger.
|
269
|
-
|
270
|
-
|
271
|
-
# 死叉逻辑 ,如果是死叉,且是周期底部,且K线方向是多头,就清仓空单
|
272
|
-
if last_cross_direction and last_cross_direction['cross'] == 0 and is_apex and side == 'short' and kline_direction == 1:
|
273
|
-
self.logger.debug(f"{symbol} 死叉:{last_cross_direction['cross']},两线收缩={is_apex},持仓方向={side} ,K线方向={kline_direction} ,开始清理空单!!")
|
274
|
-
# self.close_all_positions(symbol=symbol, position=position)
|
275
|
-
order_stop_loss_pct = self.stop_loss_pct / 2
|
276
|
-
self.logger.debug(f"{symbol} 全局止损阈值-修正后= {self.stop_loss_pct:.2f} -> {order_stop_loss_pct:.2f}%")
|
277
|
-
|
278
|
-
# 根据情况 重新修正 止损
|
279
|
-
if order_stop_loss_pct is not None :
|
280
|
-
self.global_symbol_stop_loss_flag[symbol] = False
|
281
|
-
self.set_global_stop_loss(symbol=symbol,position=position,stop_loss_pct=order_stop_loss_pct)
|
279
|
+
self.logger.info(f"{symbol} 全局止损阈值-修正后= {self.stop_loss_pct:.2f} -> {order_stop_loss_pct:.2f}%")
|
282
280
|
else :
|
283
|
-
|
281
|
+
order_stop_loss_pct = self.stop_loss_pct
|
282
|
+
self.logger.info(f"{symbol}: 持仓方向={side} 与 正确方向={correct_side} 相同 , 恢复正常。")
|
284
283
|
|
285
|
-
|
286
|
-
self.
|
284
|
+
# 根据情况 重新修正止损
|
285
|
+
self.global_symbol_stop_loss_flag[symbol] = False
|
286
|
+
self.set_global_stop_loss(symbol=symbol,position=position,stop_loss_pct=order_stop_loss_pct)
|
287
|
+
|
288
|
+
|
287
289
|
except Exception as e:
|
288
290
|
error_message = f"程序异常退出: {str(e)}"
|
289
|
-
self.logger.
|
291
|
+
self.logger.warning(error_message,exc_info=True)
|
290
292
|
traceback.print_exc()
|
291
293
|
self.send_feishu_notification(error_message)
|
292
294
|
|
@@ -345,20 +347,31 @@ class MultiAssetNewTradingBot:
|
|
345
347
|
average_profit_pct = total_profit_pct / num_positions if num_positions > 0 else 0
|
346
348
|
return average_profit_pct
|
347
349
|
|
348
|
-
def reset_highest_profit_and_tier(self):
|
350
|
+
def reset_highest_profit_and_tier(self,symbol=None):
|
349
351
|
"""重置最高总盈利和当前档位状态"""
|
350
|
-
|
351
|
-
|
352
|
-
|
353
|
-
|
352
|
+
if not symbol:
|
353
|
+
self.highest_total_profit.clear()
|
354
|
+
else :
|
355
|
+
self.highest_total_profit[symbol] = 0.0
|
356
|
+
|
357
|
+
# self.logger.debug("已重置最高总盈利")
|
354
358
|
# FIXME 目前只支持 单symbol
|
355
|
-
def reset_take_profie(self):
|
356
|
-
|
357
|
-
|
358
|
-
|
359
|
-
|
360
|
-
|
361
|
-
|
359
|
+
def reset_take_profie(self,symbol=None):
|
360
|
+
if not symbol:
|
361
|
+
self.global_symbol_stop_loss_price.clear()
|
362
|
+
self.global_symbol_stop_loss_flag.clear()
|
363
|
+
# 保留在止盈挂单中最高最低两个价格,计算止盈价格。
|
364
|
+
self.max_market_price.clear()
|
365
|
+
self.min_market_price.clear()
|
366
|
+
self.cross_directions.clear()
|
367
|
+
|
368
|
+
else :
|
369
|
+
self.global_symbol_stop_loss_price[symbol] = 0.0
|
370
|
+
self.global_symbol_stop_loss_flag[symbol] = False
|
371
|
+
# 保留在止盈挂单中最高最低两个价格,计算止盈价格。
|
372
|
+
self.max_market_price[symbol] = 0.0
|
373
|
+
self.min_market_price[symbol] = float('inf') # 初始化为浮点数最大值
|
374
|
+
self.cross_directions[symbol] = None
|
362
375
|
|
363
376
|
def round_price_to_tick(self,symbol, price):
|
364
377
|
tick_size = float(self.exchange.market(symbol)['info']['tickSz'])
|
@@ -378,7 +391,7 @@ class MultiAssetNewTradingBot:
|
|
378
391
|
orders = self.fetch_open_orders(symbol=symbol,params=params)
|
379
392
|
# 如果没有委托订单则直接返回
|
380
393
|
if not orders:
|
381
|
-
self.global_symbol_stop_loss_flag
|
394
|
+
self.global_symbol_stop_loss_flag[symbol] = False
|
382
395
|
self.logger.debug(f"{symbol} 未设置策略订单列表。")
|
383
396
|
return
|
384
397
|
|
@@ -389,7 +402,7 @@ class MultiAssetNewTradingBot:
|
|
389
402
|
"trigger": 'trigger'
|
390
403
|
}
|
391
404
|
rs = self.exchange.cancel_orders(ids=algo_ids, symbol=symbol, params=params)
|
392
|
-
self.global_symbol_stop_loss_flag
|
405
|
+
self.global_symbol_stop_loss_flag[symbol] = False
|
393
406
|
# self.logger.debug(f"Order {algo_ids} cancelled:{rs}")
|
394
407
|
except Exception as e:
|
395
408
|
self.logger.error(f"{symbol} Error cancelling order {algo_ids}: {e}")
|
@@ -454,7 +467,7 @@ class MultiAssetNewTradingBot:
|
|
454
467
|
while retry_count < max_retries:
|
455
468
|
try:
|
456
469
|
|
457
|
-
self.logger.debug(f"{symbol}
|
470
|
+
self.logger.debug(f"{symbol}: {orderSide} - TP at {adjusted_price} Starting.... ")
|
458
471
|
|
459
472
|
self.exchange.create_order(
|
460
473
|
symbol=symbol,
|
@@ -465,7 +478,8 @@ class MultiAssetNewTradingBot:
|
|
465
478
|
amount=amount,
|
466
479
|
params=tp_params
|
467
480
|
)
|
468
|
-
|
481
|
+
|
482
|
+
self.logger.info(f"{symbol}: TP at {adjusted_price} Done.")
|
469
483
|
break
|
470
484
|
|
471
485
|
|
@@ -497,8 +511,7 @@ class MultiAssetNewTradingBot:
|
|
497
511
|
|
498
512
|
|
499
513
|
def set_stop_loss(self, symbol, position, stop_loss_price=None) -> bool:
|
500
|
-
|
501
|
-
|
514
|
+
|
502
515
|
# 计算下单数量
|
503
516
|
amount = abs(float(position['contracts']))
|
504
517
|
|
@@ -537,7 +550,7 @@ class MultiAssetNewTradingBot:
|
|
537
550
|
while retry_count < max_retries:
|
538
551
|
try:
|
539
552
|
|
540
|
-
self.logger.debug(f"{symbol}
|
553
|
+
self.logger.debug(f"{symbol}: {orderSide} - Pre SL at {adjusted_price} Starting.... ")
|
541
554
|
|
542
555
|
self.exchange.create_order(
|
543
556
|
symbol=symbol,
|
@@ -549,7 +562,7 @@ class MultiAssetNewTradingBot:
|
|
549
562
|
amount=amount,
|
550
563
|
params=sl_params
|
551
564
|
)
|
552
|
-
self.logger.
|
565
|
+
self.logger.info(f"{symbol}: SL at {adjusted_price} Done.")
|
553
566
|
break
|
554
567
|
|
555
568
|
|
@@ -590,11 +603,11 @@ class MultiAssetNewTradingBot:
|
|
590
603
|
stop_loss_algo: 止损算法信息
|
591
604
|
"""
|
592
605
|
# 如果已经触发过全局止损并且有止损单,则跳过
|
606
|
+
self.logger.debug(f"{symbol} - 是否设置过全局止损 {self.global_symbol_stop_loss_flag.get(symbol, False)} ")
|
593
607
|
if self.global_symbol_stop_loss_flag.get(symbol, False):
|
594
|
-
|
595
608
|
return
|
596
|
-
|
597
|
-
|
609
|
+
|
610
|
+
|
598
611
|
if stop_loss_pct is None :
|
599
612
|
stop_loss_pct = self.stop_loss_pct
|
600
613
|
|
@@ -609,11 +622,29 @@ class MultiAssetNewTradingBot:
|
|
609
622
|
|
610
623
|
sl_order_price = float(self.round_price_to_tick(symbol, sl_price))
|
611
624
|
tp_order_price = float(self.round_price_to_tick(symbol, tp_price))
|
625
|
+
|
626
|
+
# 250228 没有指定止损回撤阈值stop_loss_pct,则参考最新的孤立点设置止损价格
|
627
|
+
if stop_loss_pct is None :
|
628
|
+
pair_config = self.trading_pairs_config.get(symbol, {})
|
629
|
+
klines_period = str(pair_config.get('klines_period', '1m'))
|
630
|
+
kLines = self.get_historical_klines_except_last(symbol=symbol,bar=klines_period)
|
631
|
+
|
632
|
+
if kLines:
|
633
|
+
isolated_point = self.get_last_solated_point(symbol ,position , kLines)
|
634
|
+
|
635
|
+
if len(isolated_point) > 0 :
|
636
|
+
last_isolated_point = float(isolated_point.iloc[-1])
|
637
|
+
self.logger.debug(f"{symbol}: - {side} ,孤立点={last_isolated_point} ,止损价={sl_order_price}")
|
638
|
+
if side == 'long':
|
639
|
+
sl_order_price = max(last_isolated_point, sl_order_price)
|
640
|
+
elif side =='short':
|
641
|
+
sl_order_price = min(last_isolated_point, sl_order_price)
|
642
|
+
|
612
643
|
|
613
644
|
last_sl_price= self.global_symbol_stop_loss_price.get(symbol,None)
|
614
645
|
if last_sl_price is not None and last_sl_price == sl_order_price:
|
615
646
|
self.global_symbol_stop_loss_flag[symbol] = True
|
616
|
-
self.logger.debug(f"{symbol} - {side}
|
647
|
+
self.logger.debug(f"{symbol}: - {side} ,全局止损价没变化: {last_sl_price} = {sl_order_price}")
|
617
648
|
return
|
618
649
|
|
619
650
|
try:
|
@@ -644,18 +675,17 @@ class MultiAssetNewTradingBot:
|
|
644
675
|
# 计算止盈价格。
|
645
676
|
|
646
677
|
if side == 'long':
|
647
|
-
|
648
|
-
base_price = entry_price * (1-take_profile_pct)
|
649
|
-
take_profile_price = entry_price + base_price - offset * tick_size
|
678
|
+
take_profile_price = entry_price * (1+take_profile_pct/100) - offset * tick_size
|
650
679
|
|
651
680
|
|
652
681
|
elif side == 'short':
|
653
682
|
|
654
|
-
base_price = entry_price * (1-take_profile_pct)
|
655
|
-
take_profile_price = entry_price -
|
683
|
+
# base_price = entry_price * (1-take_profile_pct)
|
684
|
+
take_profile_price = entry_price * (1-take_profile_pct/100) + offset * tick_size
|
656
685
|
|
657
|
-
return take_profile_price
|
686
|
+
return float(self.round_price_to_tick(symbol,take_profile_price))
|
658
687
|
|
688
|
+
# 计算回撤止盈价格
|
659
689
|
def calculate_stop_loss_price(self, symbol, position, stop_loss_pct, offset=1) -> float:
|
660
690
|
tick_size = float(self.exchange.market(symbol)['precision']['price'])
|
661
691
|
market_price = position['markPrice']
|
@@ -665,19 +695,21 @@ class MultiAssetNewTradingBot:
|
|
665
695
|
# 计算止盈价格,用市场价格(取持仓期间历史最高)减去开仓价格的利润,再乘以不同阶段的止盈百分比。
|
666
696
|
latest_stop_loss_price = self.exchange.safe_float(self.global_symbol_stop_loss_price,symbol,None)
|
667
697
|
if side == 'long':
|
668
|
-
self.max_market_price
|
669
|
-
|
698
|
+
last_max_market_price = self.max_market_price.get(symbol,0.0)
|
699
|
+
self.max_market_price[symbol] = max(market_price,last_max_market_price)
|
700
|
+
base_price = abs(self.max_market_price[symbol] - entry_price) * (1-stop_loss_pct)
|
670
701
|
stop_loss_price = entry_price + base_price - offset * tick_size
|
671
702
|
if latest_stop_loss_price :
|
672
703
|
stop_loss_price = max(stop_loss_price,latest_stop_loss_price)
|
673
704
|
|
674
705
|
elif side == 'short':
|
675
|
-
|
676
|
-
|
706
|
+
last_min_market_price = self.min_market_price.get(symbol,float('inf'))
|
707
|
+
self.min_market_price[symbol] = min(market_price,last_min_market_price)
|
708
|
+
base_price = abs(self.min_market_price[symbol] - entry_price) * (1-stop_loss_pct)
|
677
709
|
stop_loss_price = entry_price - base_price + offset * tick_size
|
678
710
|
if latest_stop_loss_price :
|
679
711
|
stop_loss_price = min(stop_loss_price,latest_stop_loss_price)
|
680
|
-
return stop_loss_price
|
712
|
+
return float(self.round_price_to_tick(symbol,stop_loss_price))
|
681
713
|
|
682
714
|
# 市价仓位平仓
|
683
715
|
def close_all_positions(self,symbol,position):
|
@@ -714,7 +746,7 @@ class MultiAssetNewTradingBot:
|
|
714
746
|
params=params
|
715
747
|
)
|
716
748
|
time.sleep(0.1) # 短暂延迟后再试
|
717
|
-
self.reset_take_profie()
|
749
|
+
self.reset_take_profie(symbol)
|
718
750
|
self.logger.info(f"{symbol} Close position response for {symbol}: {order}")
|
719
751
|
self.send_feishu_notification(f"{symbol} 平仓订单完全成交 -{symbol} side: {side}")
|
720
752
|
|
@@ -733,25 +765,27 @@ class MultiAssetNewTradingBot:
|
|
733
765
|
"""
|
734
766
|
latest_stop_loss_price = self.exchange.safe_float(self.global_symbol_stop_loss_price, symbol, 0.0)
|
735
767
|
if latest_stop_loss_price == 0.0:
|
736
|
-
self.logger.warning(f"{symbol} 未设置止损价格,执行平仓")
|
768
|
+
self.logger.warning(f"{symbol}: 未设置止损价格,执行平仓")
|
737
769
|
return True
|
738
770
|
|
739
771
|
mark_price = position['markPrice']
|
740
772
|
side = position['side']
|
741
773
|
|
742
|
-
|
743
|
-
|
774
|
+
sl_price = float(self.round_price_to_tick(symbol=symbol,price=latest_stop_loss_price))
|
775
|
+
|
776
|
+
if side == 'long' and mark_price < sl_price:
|
777
|
+
self.logger.warning(f"{'*'*60}\n[非正常关闭]: {symbol} 方向 {side} - 市场价格 {mark_price} 低于止盈 {latest_stop_loss_price},触发全局止盈\n{'*'*60}")
|
744
778
|
return True
|
745
|
-
elif side == 'short' and mark_price >
|
746
|
-
self.logger.warning(f"
|
779
|
+
elif side == 'short' and mark_price > sl_price:
|
780
|
+
self.logger.warning(f"{'*'*60}\n[非正常关闭]: {symbol} 方向 {side} - 市场价格 {mark_price} 高于止盈价 {latest_stop_loss_price},触发全局止盈\n{'*'*60}")
|
747
781
|
return True
|
748
782
|
|
749
783
|
return False
|
750
784
|
|
751
785
|
def check_position(self, symbol, position):
|
752
786
|
# 清理趋势相反的仓位
|
753
|
-
|
754
|
-
|
787
|
+
pair_config = self.trading_pairs_config.get(symbol, {})
|
788
|
+
self.check_reverse_position(symbol=symbol, position=position, pair_config=pair_config)
|
755
789
|
|
756
790
|
# 检查止损是否触发止盈
|
757
791
|
if self.check_stop_loss_trigger(symbol, position):
|
@@ -760,105 +794,105 @@ class MultiAssetNewTradingBot:
|
|
760
794
|
|
761
795
|
|
762
796
|
def check_total_profit(self, symbol, position):
|
763
|
-
|
797
|
+
"""
|
798
|
+
检查当前总盈利
|
799
|
+
"""
|
800
|
+
|
764
801
|
total_profit = self.calculate_average_profit(symbol, position)
|
765
802
|
if total_profit > 0.0 :
|
766
803
|
self.logger.info(f"{symbol} 当前总盈利: {total_profit:.2f}%")
|
767
804
|
self.send_feishu_notification(f"{symbol} 当前总盈利: {total_profit:.2f}%")
|
768
|
-
|
769
|
-
|
805
|
+
|
806
|
+
cur_highest_total_profit = self.highest_total_profit.get(symbol, 0.0)
|
807
|
+
|
808
|
+
if total_profit > cur_highest_total_profit:
|
809
|
+
cur_highest_total_profit = total_profit
|
810
|
+
self.highest_total_profit[symbol] = total_profit
|
811
|
+
|
812
|
+
current_tier = '无'
|
770
813
|
# 确定当前盈利档位
|
771
|
-
if
|
772
|
-
|
814
|
+
if cur_highest_total_profit >= self.second_trail_profit_threshold:
|
815
|
+
current_tier = "高档"
|
773
816
|
|
774
|
-
elif
|
775
|
-
|
817
|
+
elif cur_highest_total_profit>= self.first_trail_profit_threshold:
|
818
|
+
current_tier = "中档"
|
776
819
|
|
777
|
-
elif
|
778
|
-
|
820
|
+
elif cur_highest_total_profit >= self.low_trail_profit_threshold:
|
821
|
+
current_tier = "低档"
|
779
822
|
|
780
823
|
|
781
824
|
if total_profit > 0.0 :
|
782
825
|
self.logger.info(
|
783
|
-
f"{symbol} 档位[{
|
784
|
-
self.send_feishu_notification(
|
785
|
-
f"{symbol} 档位[{self.current_tier} ]: 当前总盈利: {total_profit:.2f}%,最高总盈利: {self.highest_total_profit:.2f}%")
|
826
|
+
f"{symbol} 档位[{current_tier}]: 当前总盈利: {total_profit:.2f}%,最高总盈利: {cur_highest_total_profit:.2f}%")
|
786
827
|
|
787
828
|
'''
|
788
|
-
|
829
|
+
第一档:低档保护止盈:当盈利达到0.3%触发,要么到第二档,要么回到0.2%止盈
|
789
830
|
第二档:盈利达到1%触发,记录最高价,最高价的80%是止盈位
|
790
831
|
第三档:盈利达到3%触发,记录最高价,最高价的75%是止盈位
|
832
|
+
|
791
833
|
'''
|
792
834
|
# 各档止盈逻辑
|
793
835
|
|
794
|
-
|
795
|
-
|
796
|
-
|
797
|
-
|
798
|
-
|
799
|
-
|
800
|
-
|
801
|
-
|
802
|
-
|
803
|
-
|
804
|
-
|
805
|
-
|
806
|
-
|
807
|
-
|
808
|
-
|
809
|
-
|
810
|
-
|
836
|
+
# 根据不同档位设置止损价格和通知
|
837
|
+
tier_config = {
|
838
|
+
"低档": {
|
839
|
+
"stop_loss_pct": float(self.low_trail_stop_loss_pct)
|
840
|
+
},
|
841
|
+
"中档": {
|
842
|
+
"stop_loss_pct": float(self.trail_stop_loss_pct)
|
843
|
+
},
|
844
|
+
"高档": {
|
845
|
+
"stop_loss_pct": float(self.higher_trail_stop_loss_pct)
|
846
|
+
}
|
847
|
+
}
|
848
|
+
|
849
|
+
if current_tier in tier_config:
|
850
|
+
config = tier_config[current_tier]
|
851
|
+
|
852
|
+
# 记录日志
|
853
|
+
self.logger.debug(f"{symbol} 回撤止盈阈值: {config['stop_loss_pct']*100}%")
|
854
|
+
|
855
|
+
# 计算回撤止损价格
|
856
|
+
sl_price = self.calculate_stop_loss_price(
|
857
|
+
symbol=symbol,
|
858
|
+
position=position,
|
859
|
+
stop_loss_pct=config['stop_loss_pct']
|
860
|
+
)
|
861
|
+
|
862
|
+
# 检查价格是否变化
|
863
|
+
latest_sl_price = self.exchange.safe_float(self.global_symbol_stop_loss_price, symbol, 0.0)
|
864
|
+
if sl_price == latest_sl_price:
|
865
|
+
self.logger.debug(f"{symbol} 回撤止损价格{latest_sl_price}未变化,不设置")
|
811
866
|
return
|
812
|
-
|
813
|
-
|
814
|
-
self.
|
815
|
-
if total_profit >= trail_stop_loss:
|
816
|
-
sl_price = self.calculate_stop_loss_price(symbol=symbol, position=position,stop_loss_pct=self.trail_stop_loss_pct )
|
817
|
-
# 判断止盈价格是否变化,无变化不需要设置
|
818
|
-
latest_sl_price = self.exchange.safe_float(self.global_symbol_stop_loss_price,symbol,0.0)
|
819
|
-
if sl_price == latest_sl_price :
|
820
|
-
self.logger.debug(f"{symbol} 止盈价格未变化,不设置")
|
821
|
-
return
|
822
|
-
if_success = self.set_stop_loss_take_profit(symbol, position, stop_loss_price=sl_price)
|
823
|
-
if if_success:
|
824
|
-
self.logger.info(
|
825
|
-
f"{symbol} 总盈利达到第一档回撤阈值,最高总盈利: {self.highest_total_profit:.2f}%,当前回撤到: {total_profit:.2f}%,市场价格: {position['markPrice']},设置止盈位: {sl_price:.9f}")
|
826
|
-
# 记录一下止盈价格
|
827
|
-
self.global_symbol_stop_loss_price[symbol] = float(sl_price)
|
828
|
-
self.reset_highest_profit_and_tier()
|
829
|
-
self.send_feishu_notification(
|
830
|
-
f"{symbol} 总盈利达到第一档回撤阈值,最高总盈利: {self.highest_total_profit:.2f}%,当前回撤到: {total_profit:.2f}%,市场价格: {position['markPrice']}, 设置止盈位: {sl_price:.9f}")
|
831
|
-
return
|
832
|
-
|
833
|
-
elif self.current_tier == "第二档移动止盈":
|
834
|
-
trail_stop_loss = self.highest_total_profit * (1 - self.higher_trail_stop_loss_pct)
|
835
|
-
self.logger.info(f"{symbol} 第二档回撤止盈阈值: {trail_stop_loss:.2f}%")
|
836
|
-
if total_profit >= trail_stop_loss:
|
837
|
-
sl_price = self.calculate_stop_loss_price(symbol=symbol, position=position,stop_loss_pct=self.higher_trail_stop_loss_pct)
|
838
|
-
# 判断止盈价格是否变化,无变化不需要设置
|
839
|
-
latest_sl_price = self.exchange.safe_float(self.global_symbol_stop_loss_price,symbol,0.0)
|
840
|
-
if sl_price == latest_sl_price:
|
841
|
-
self.logger.debug(f"{symbol} 止盈价格未变化,不设置")
|
842
|
-
return
|
843
|
-
if_success = self.set_stop_loss_take_profit(symbol, position, stop_loss_price=sl_price)
|
844
|
-
if if_success:
|
845
|
-
self.logger.info(f"{symbol} 总盈利达到第二档回撤阈值,最高总盈利: {self.highest_total_profit:.2f}%,当前回撤到: {total_profit:.2f}%,市场价格: {position['markPrice']},设置止盈位: {sl_price:.9f}")
|
846
|
-
# 记录一下止盈价格
|
847
|
-
self.global_symbol_stop_loss_price[symbol] = sl_price
|
848
|
-
self.reset_highest_profit_and_tier()
|
849
|
-
self.send_feishu_notification(f"{symbol} 总盈利达到第二档回撤阈值,最高总盈利: {self.highest_total_profit:.2f}%,当前回撤到: {total_profit:.2f}%,市场价格: {position['markPrice']},设置止盈位: {sl_price:.9f}")
|
850
|
-
return
|
851
|
-
else :
|
852
|
-
self.logger.info(f"{symbol} 全局止损阈值: {self.stop_loss_pct:.2f}%")
|
867
|
+
|
868
|
+
# 设置止损
|
869
|
+
if_success = self.set_stop_loss_take_profit(symbol, position, stop_loss_price=sl_price)
|
853
870
|
|
854
|
-
|
871
|
+
if if_success:
|
872
|
+
# 更新回撤止损价格
|
873
|
+
|
874
|
+
self.global_symbol_stop_loss_price[symbol] = sl_price
|
875
|
+
self.global_symbol_stop_loss_flag[symbol] = True
|
876
|
+
|
877
|
+
# 发送通知
|
878
|
+
msg = (f"{symbol}: 盈利达到【{current_tier}】阈值,最高总盈利: {cur_highest_total_profit:.2f}%,"
|
879
|
+
f"当前盈利回撤到: {total_profit:.2f}%,市场价格:{position['markPrice']},"
|
880
|
+
f"设置回撤止损位: {sl_price:.9f}")
|
881
|
+
self.logger.info(msg)
|
882
|
+
self.send_feishu_notification(msg)
|
883
|
+
|
884
|
+
else:
|
855
885
|
|
856
|
-
|
886
|
+
# 默认全局止损
|
887
|
+
self.set_global_stop_loss(symbol, position)
|
888
|
+
self.logger.info(f"{symbol} 全局止损阈值: {self.stop_loss_pct:.2f}%")
|
889
|
+
|
890
|
+
return
|
857
891
|
|
858
892
|
def monitor_total_profit(self):
|
859
893
|
self.logger.info("启动主循环,开始监控总盈利...")
|
860
|
-
previous_position_size = sum(
|
861
|
-
|
894
|
+
# previous_position_size = sum(
|
895
|
+
# abs(float(position['contracts'])) for position in self.fetch_positions()) # 初始总仓位大小
|
862
896
|
while True:
|
863
897
|
try:
|
864
898
|
|
@@ -868,21 +902,31 @@ class MultiAssetNewTradingBot:
|
|
868
902
|
# self.logger.debug("没有持仓,等待下一次检查...")
|
869
903
|
self.reset_highest_profit_and_tier()
|
870
904
|
self.reset_take_profie()
|
905
|
+
self.positions_entry_price= {}
|
871
906
|
time.sleep(1)
|
872
907
|
continue
|
908
|
+
|
873
909
|
self.logger.info("+" * 60)
|
874
910
|
# 检查仓位总规模变化
|
875
|
-
current_position_size = sum(abs(float(position['contracts'])) for position in self.fetch_positions())
|
876
|
-
if current_position_size > previous_position_size:
|
877
|
-
|
878
|
-
|
879
|
-
|
880
|
-
|
881
|
-
|
882
|
-
|
911
|
+
# current_position_size = sum(abs(float(position['contracts'])) for position in self.fetch_positions())
|
912
|
+
# if current_position_size > previous_position_size:
|
913
|
+
# self.send_feishu_notification(f"检测到仓位变化操作,重置最高盈利和档位状态")
|
914
|
+
# self.logger.info("检测到新增仓位操作,重置最高盈利和档位状态")
|
915
|
+
# self.reset_highest_profit_and_tier()
|
916
|
+
# previous_position_size = current_position_size
|
917
|
+
# time.sleep(0.1)
|
918
|
+
# continue # 跳过本次循环
|
883
919
|
|
884
920
|
for position in positions:
|
885
921
|
symbol = position['symbol']
|
922
|
+
cur_entry_price = position['entryPrice']
|
923
|
+
# 检查仓位是否有变化
|
924
|
+
if symbol in self.positions_entry_price and cur_entry_price != self.positions_entry_price[symbol]:
|
925
|
+
# 新开仓
|
926
|
+
self.reset_highest_profit_and_tier(symbol)
|
927
|
+
self.reset_take_profie(symbol)
|
928
|
+
self.positions_entry_price[symbol] = cur_entry_price
|
929
|
+
|
886
930
|
self.check_total_profit(symbol, position)
|
887
931
|
time.sleep(0.1)
|
888
932
|
# 检查仓位和挂单是否有问题
|
@@ -892,8 +936,9 @@ class MultiAssetNewTradingBot:
|
|
892
936
|
time.sleep(self.monitor_interval)
|
893
937
|
|
894
938
|
except Exception as e:
|
895
|
-
print(e)
|
939
|
+
# print(e)
|
896
940
|
error_message = f"程序异常退出: {str(e)}"
|
941
|
+
traceback.print_exc()
|
897
942
|
self.logger.error(error_message)
|
898
943
|
self.send_feishu_notification(error_message)
|
899
944
|
continue
|
taker/main.py
CHANGED
@@ -2,7 +2,8 @@ import logging
|
|
2
2
|
import yaml
|
3
3
|
from logging.handlers import TimedRotatingFileHandler
|
4
4
|
|
5
|
-
from taker.
|
5
|
+
from taker.TrailingSLTaker import TrailingSLTaker
|
6
|
+
from taker.TrailingSLAndTPTaker import TrailingSLAndTPTaker
|
6
7
|
from taker.ThreeLineTradingBot import ThreeLineTradingBot
|
7
8
|
|
8
9
|
def build_logger(log_config) -> logging.Logger:
|
@@ -56,7 +57,7 @@ def main():
|
|
56
57
|
package_name = __package__ or "taker"
|
57
58
|
|
58
59
|
logger.info(f" ++ {package_name}:{version} is doing...")
|
59
|
-
bot =
|
60
|
+
bot = TrailingSLAndTPTaker(config_data, platform_config, feishu_webhook=feishu_webhook_url, monitor_interval=monitor_interval,logger=logger)
|
60
61
|
bot.monitor_total_profit()
|
61
62
|
# bot = ThreeLineTradingBot(platform_config, feishu_webhook=feishu_webhook_url, monitor_interval=monitor_interval)
|
62
63
|
# bot.monitor_klines()
|
File without changes
|
File without changes
|