kaq-quant-common 0.2.16__py3-none-any.whl → 0.2.18__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.
Files changed (25) hide show
  1. kaq_quant_common/api/common/__init__.py +1 -1
  2. kaq_quant_common/api/common/api_interface.py +38 -38
  3. kaq_quant_common/api/rest/api_client_base.py +187 -187
  4. kaq_quant_common/api/rest/instruction/helper/commission_helper.py +141 -141
  5. kaq_quant_common/api/rest/instruction/helper/mock_order_helper.py +346 -346
  6. kaq_quant_common/api/rest/instruction/helper/order_helper.py +362 -362
  7. kaq_quant_common/api/rest/instruction/instruction_client.py +40 -2
  8. kaq_quant_common/api/rest/instruction/instruction_server_base.py +62 -53
  9. kaq_quant_common/api/rest/instruction/models/__init__.py +17 -17
  10. kaq_quant_common/api/rest/instruction/models/kline.py +60 -0
  11. kaq_quant_common/api/rest/instruction/models/transfer.py +32 -32
  12. kaq_quant_common/api/ws/exchange/models.py +23 -23
  13. kaq_quant_common/api/ws/exchange/ws_exchange_server.py +440 -440
  14. kaq_quant_common/common/ddb_table_monitor.py +106 -106
  15. kaq_quant_common/common/http_monitor.py +69 -69
  16. kaq_quant_common/common/modules/limit_order_helper.py +12 -0
  17. kaq_quant_common/common/monitor_base.py +84 -84
  18. kaq_quant_common/common/monitor_group.py +97 -97
  19. kaq_quant_common/common/ws_wrapper.py +21 -21
  20. kaq_quant_common/utils/logger_utils.py +4 -4
  21. kaq_quant_common/utils/signal_utils.py +23 -23
  22. kaq_quant_common/utils/uuid_utils.py +5 -5
  23. {kaq_quant_common-0.2.16.dist-info → kaq_quant_common-0.2.18.dist-info}/METADATA +1 -1
  24. {kaq_quant_common-0.2.16.dist-info → kaq_quant_common-0.2.18.dist-info}/RECORD +25 -24
  25. {kaq_quant_common-0.2.16.dist-info → kaq_quant_common-0.2.18.dist-info}/WHEEL +1 -1
@@ -1,362 +1,362 @@
1
- import json
2
- import threading
3
- import time
4
- from typing import Callable
5
-
6
- from kaq_quant_common.api.rest.instruction.models.order import (
7
- OrderInfo,
8
- OrderSide,
9
- OrderStatus,
10
- PositionStatus,
11
- )
12
- from kaq_quant_common.api.rest.instruction.models.position import PositionSide
13
- from kaq_quant_common.utils import logger_utils, uuid_utils
14
-
15
-
16
- class OrderHelper:
17
- def __init__(self, ins_server):
18
- # 必须放在这里 延迟引入,否则会有循环引用问题
19
- from kaq_quant_common.api.rest.instruction.instruction_server_base import (
20
- InstructionServerBase,
21
- )
22
-
23
- self._server: InstructionServerBase = ins_server
24
- self._logger = logger_utils.get_logger(self)
25
-
26
- self._mysql_table_name_order = "kaq_futures_instruction_order"
27
- self._mysql_table_name_position = "kaq_futures_instruction_position"
28
- # 当前持仓
29
- self._redis_key_position = "kaq_futures_instruction_position"
30
- # 持仓历史
31
- self._redis_key_position_history = "kaq_futures_instruction_position_history"
32
-
33
- def _write_position_open_to_redis(
34
- self,
35
- position_id: str,
36
- exchange: str,
37
- symbol: str,
38
- position_side,
39
- lever: int,
40
- coin_quantity: float,
41
- usdt_quantity: float,
42
- open_ins_id: str,
43
- open_price: float,
44
- open_fee: float,
45
- open_fee_rate: float,
46
- open_time: int,
47
- is_spot: bool,
48
- ):
49
- redis = self._server._redis
50
- if redis is None:
51
- return
52
- data = {
53
- "id": position_id,
54
- "exchange": exchange,
55
- "symbol": symbol,
56
- "position_side": position_side.value,
57
- "lever": lever,
58
- "coin_quantity": coin_quantity,
59
- "usdt_quantity": usdt_quantity,
60
- "open_ins_id": open_ins_id,
61
- "open_price": open_price,
62
- "open_fee": open_fee,
63
- "open_fee_rate": open_fee_rate,
64
- "open_time": open_time,
65
- "close_ins_id": None,
66
- "close_price": 0,
67
- "close_time": 0,
68
- "status": PositionStatus.OPEN.value,
69
- "is_spot": is_spot,
70
- }
71
- redis.client.hset(self._redis_key_position, position_id, json.dumps(data))
72
-
73
- def _write_position_close_to_redis(
74
- self,
75
- position_id: str,
76
- exchange: str,
77
- symbol: str,
78
- position_side,
79
- lever: int,
80
- coin_quantity: float,
81
- usdt_quantity: float,
82
- open_ins_id: str,
83
- open_price: float,
84
- open_fee: float,
85
- open_fee_rate: float,
86
- open_time: int,
87
- close_ins_id: str,
88
- close_price: float,
89
- close_fee: float,
90
- close_fee_rate: float,
91
- close_time: int,
92
- is_spot: bool,
93
- ):
94
- redis = self._server._redis
95
- if redis is None:
96
- return
97
-
98
- # 先从 redis 读取现有的 position 数据,获取 funding_rate_records 字段
99
- funding_rate_records = None
100
- try:
101
- existing_position_json = redis.client.hget(self._redis_key_position, position_id)
102
- if existing_position_json:
103
- existing_position = json.loads(existing_position_json)
104
- if existing_position and "funding_rate_records" in existing_position:
105
- funding_rate_records = existing_position.get("funding_rate_records")
106
- except Exception as e:
107
- # 读取失败不影响后续流程,记录日志
108
- self._logger.warning(f"Failed to get funding_rate_records for position {position_id}: {e}")
109
-
110
- data = {
111
- "id": position_id,
112
- "exchange": exchange,
113
- "symbol": symbol,
114
- "position_side": position_side.value,
115
- "lever": lever,
116
- "coin_quantity": coin_quantity,
117
- "usdt_quantity": usdt_quantity,
118
- "open_ins_id": open_ins_id,
119
- "open_price": open_price,
120
- "open_fee": open_fee,
121
- "open_fee_rate": open_fee_rate,
122
- "open_time": open_time,
123
- "close_ins_id": close_ins_id,
124
- "close_price": close_price,
125
- "close_fee": close_fee,
126
- "close_fee_rate": close_fee_rate,
127
- "close_time": close_time,
128
- "status": PositionStatus.CLOSE.value,
129
- "is_spot": is_spot,
130
- }
131
-
132
- # 如果存在 funding_rate_records,添加到 data 中
133
- if funding_rate_records is not None:
134
- data["funding_rate_records"] = funding_rate_records
135
-
136
- redis.client.hdel(self._redis_key_position, position_id)
137
- redis.client.rpush(self._redis_key_position_history, json.dumps(data))
138
-
139
- def process_order(self, order: OrderInfo, get_order_result: Callable):
140
- # 获取交易所
141
- exchange = self._server._exchange
142
-
143
- # 记录时间,统计成交耗时
144
- start_time = time.time()
145
-
146
- #
147
- if not self._do_process_order(exchange, order, get_order_result, True, start_time):
148
- # 马上执行,没有成功,开启线程执行
149
- thread = threading.Thread(
150
- target=self._do_process_order,
151
- args=(exchange, order, get_order_result, False, start_time),
152
- )
153
- thread.name = f"process_order_{order.instruction_id}_{exchange}_{order.symbol}_{order.order_id}"
154
- thread.daemon = True
155
- thread.start()
156
-
157
- def _do_process_order(
158
- self,
159
- exchange: str,
160
- order: OrderInfo,
161
- get_order_result: Callable,
162
- first=True,
163
- start_time: float = 0,
164
- ):
165
- # 获取mysql
166
- mysql = self._server._mysql
167
-
168
- #
169
- ins_id = order.instruction_id
170
- order_id = order.order_id
171
- symbol = order.symbol
172
- side = order.side
173
- position_side = order.position_side
174
- lever = order.level
175
- # TODO
176
- is_spot = False
177
-
178
- is_open = True
179
- side_str = "开仓"
180
- if position_side == PositionSide.LONG:
181
- # 多单是正向理解的
182
- if side == OrderSide.SELL:
183
- side_str = "平仓"
184
- is_open = False
185
- else:
186
- side_str = "开仓"
187
- is_open = True
188
- else:
189
- # 空单是反向理解的
190
- if side == OrderSide.SELL:
191
- side_str = "开仓"
192
- is_open = True
193
- else:
194
- side_str = "平仓"
195
- is_open = False
196
-
197
- if first:
198
- self._logger.info(f"{ins_id}_{exchange}_{symbol} step 1. {"现货" if is_spot else "合约"}{side_str}挂单成功 {order_id}")
199
-
200
- # 步骤1.挂单成功 插入到订单记录
201
- # 获取当前时间-ms
202
- current_time = int(time.time() * 1000)
203
-
204
- if mysql is not None:
205
- status = OrderStatus.CREATE
206
- sql = f"""
207
- INSERT INTO {self._mysql_table_name_order} (ins_id, exchange, symbol, side, position_side, lever, orig_price, orig_coin_quantity, order_id, status, create_time, last_update_time, is_spot)
208
- VALUES ( '{ins_id}', '{exchange}', '{symbol}', '{side.value}', '{order.position_side.value}', '{lever}', {order.current_price or order.target_price}, {order.quantity}, '{order_id}', '{status.value}', {current_time}, {current_time}, '{1 if is_spot else 0}' );
209
- """
210
- execute_ret = mysql.execute_sql(sql, True)
211
-
212
- # 步骤2.查询订单状态 直到订单成交后
213
- # 统计查询次数
214
- query_counter = 0
215
- while True:
216
- query_counter += 1
217
- # 获取订单结果
218
- order_info = None
219
- try:
220
- order_info = get_order_result()
221
- except Exception as e:
222
- self._logger.error(f"{ins_id}_{exchange}_{symbol} step 2. {side_str}订单 查询状态失败 {e}")
223
-
224
- if order_info is not None:
225
- break
226
- # 只查询一次
227
- if first:
228
- return False
229
- # 等待
230
- time.sleep(1)
231
-
232
- if not first:
233
- # 需要加上第一查询
234
- query_counter += 1
235
-
236
- # 记录时间,统计成交耗时
237
- end_time = time.time()
238
- cost_time = end_time - start_time
239
- self._logger.info(
240
- f"{ins_id}_{exchange}_{symbol} step 2. {side_str}订单 {order_id} 成交 耗时 {int(cost_time * 1000)}ms, 查询次数 {query_counter}"
241
- )
242
-
243
- # 步骤3.把最终持仓写进去
244
- # 平均成交价格 转float
245
- avg_price = float(order_info["avg_price"])
246
- # 最终成交数量 转float
247
- executed_qty = float(order_info["executed_qty"])
248
- # 计算出usdt数量
249
- executed_usdt = avg_price * executed_qty
250
- # 手续费,不一定有
251
- fee = float(order_info["fee"]) if "fee" in order_info else 0.0
252
- # 费率
253
- fee_rate = fee / executed_usdt
254
-
255
- current_time = int(time.time() * 1000)
256
-
257
- if mysql is None:
258
- self._logger.warning(f"{ins_id}_{exchange}_{symbol} 仅操作,没有入库,请设置 mysql!!")
259
- return
260
-
261
- status = OrderStatus.FINISH
262
- # 更新写入最终信息
263
- sql = f"""
264
- UPDATE {self._mysql_table_name_order}
265
- SET price = {avg_price}, coin_quantity = {executed_qty}, usdt_quantity = {executed_usdt}, fee = {fee}, fee_rate = {fee_rate}, status = '{status.value}', last_update_time = {current_time}
266
- WHERE ins_id = '{ins_id}' AND exchange = '{exchange}' AND symbol = '{symbol}';
267
- """
268
- execute_ret = mysql.execute_sql(sql, True)
269
-
270
- self._logger.info(
271
- f"{ins_id}_{exchange}_{symbol} step 2. 订单成交 {order_id}, {side_str}价格 {avg_price}, {side_str}数量 {executed_qty}, {side_str}usdt {executed_usdt} 杠杆 {lever}"
272
- )
273
- if is_open:
274
- # 同时插入持仓表
275
- position_id = uuid_utils.generate_uuid()
276
- sql = f"""
277
- INSERT INTO {self._mysql_table_name_position} (id, exchange, symbol, position_side, lever, coin_quantity, usdt_quantity, open_ins_id, open_price, open_fee, open_fee_rate, open_time, status, is_spot)
278
- VALUES ( '{position_id}', '{exchange}', '{symbol}', '{position_side.value}', '{lever}', '{executed_qty}', '{executed_usdt}', '{ins_id}', '{avg_price}', '{fee}', '{fee_rate}', {current_time}, '{PositionStatus.OPEN.value}', '{1 if is_spot else 0}' );
279
- """
280
- execute_ret = mysql.execute_sql(sql, True)
281
-
282
- self._logger.info(f"{ins_id}_{exchange}_{symbol} step 3. 创建持仓记录 {position_id}")
283
- try:
284
- self._write_position_open_to_redis(
285
- position_id=position_id,
286
- exchange=exchange,
287
- symbol=symbol,
288
- position_side=position_side,
289
- lever=lever,
290
- coin_quantity=executed_qty,
291
- usdt_quantity=executed_usdt,
292
- open_ins_id=ins_id,
293
- open_price=avg_price,
294
- open_fee=fee,
295
- open_fee_rate=fee_rate,
296
- open_time=current_time,
297
- is_spot=is_spot,
298
- )
299
- except:
300
- pass
301
- else:
302
- # 需要找到对应的持仓记录
303
- sql = f"""
304
- SELECT * FROM {self._mysql_table_name_position}
305
- WHERE exchange = '{exchange}'
306
- AND symbol = '{symbol}'
307
- AND position_side = '{position_side.value}'
308
- AND status = '{PositionStatus.OPEN.value}'
309
- AND is_spot = '{1 if is_spot else 0}'
310
- AND is_mock = 0
311
- ORDER BY open_time ASC;
312
- """
313
-
314
- # 如果有指定仓位id,就用指定的
315
- if hasattr(order, "position_id") and order.position_id:
316
- sql = f"""
317
- SELECT * FROM {self._mysql_table_name_position}
318
- WHERE id = '{order.position_id}' AND status = '{PositionStatus.OPEN.value}'
319
- """
320
- self._logger.info(f"{ins_id}_{exchange}_{symbol} get position by id {order.position_id}")
321
-
322
- execute_ret = mysql.execute_sql(sql)
323
- try:
324
- row = execute_ret.fetchone()
325
- position_id = row.id
326
- if position_id is not None:
327
- # 更新持仓信息
328
- sql = f"""
329
- UPDATE {self._mysql_table_name_position}
330
- SET close_ins_id = '{ins_id}', close_price = {avg_price}, close_fee = '{fee}', close_fee_rate = '{fee_rate}', close_time = {current_time}, status = '{PositionStatus.CLOSE.value}'
331
- WHERE id = '{position_id}';
332
- """
333
- execute_ret = mysql.execute_sql(sql, True)
334
-
335
- self._logger.info(f"{ins_id}_{exchange}_{symbol} step 3. 更新持仓记录 {position_id}")
336
- try:
337
- self._write_position_close_to_redis(
338
- position_id=position_id,
339
- exchange=exchange,
340
- symbol=symbol,
341
- position_side=position_side,
342
- lever=row.lever,
343
- coin_quantity=float(row.coin_quantity),
344
- usdt_quantity=float(row.usdt_quantity),
345
- open_ins_id=row.open_ins_id,
346
- open_price=float(row.open_price),
347
- open_fee=float(row.open_fee),
348
- open_fee_rate=float(row.open_fee_rate),
349
- open_time=int(row.open_time),
350
- close_ins_id=ins_id,
351
- close_price=avg_price,
352
- close_fee=fee,
353
- close_fee_rate=fee_rate,
354
- close_time=current_time,
355
- is_spot=row.is_spot,
356
- )
357
- except:
358
- pass
359
- except:
360
- pass
361
-
362
- return True
1
+ import json
2
+ import threading
3
+ import time
4
+ from typing import Callable
5
+
6
+ from kaq_quant_common.api.rest.instruction.models.order import (
7
+ OrderInfo,
8
+ OrderSide,
9
+ OrderStatus,
10
+ PositionStatus,
11
+ )
12
+ from kaq_quant_common.api.rest.instruction.models.position import PositionSide
13
+ from kaq_quant_common.utils import logger_utils, uuid_utils
14
+
15
+
16
+ class OrderHelper:
17
+ def __init__(self, ins_server):
18
+ # 必须放在这里 延迟引入,否则会有循环引用问题
19
+ from kaq_quant_common.api.rest.instruction.instruction_server_base import (
20
+ InstructionServerBase,
21
+ )
22
+
23
+ self._server: InstructionServerBase = ins_server
24
+ self._logger = logger_utils.get_logger(self)
25
+
26
+ self._mysql_table_name_order = "kaq_futures_instruction_order"
27
+ self._mysql_table_name_position = "kaq_futures_instruction_position"
28
+ # 当前持仓
29
+ self._redis_key_position = "kaq_futures_instruction_position"
30
+ # 持仓历史
31
+ self._redis_key_position_history = "kaq_futures_instruction_position_history"
32
+
33
+ def _write_position_open_to_redis(
34
+ self,
35
+ position_id: str,
36
+ exchange: str,
37
+ symbol: str,
38
+ position_side,
39
+ lever: int,
40
+ coin_quantity: float,
41
+ usdt_quantity: float,
42
+ open_ins_id: str,
43
+ open_price: float,
44
+ open_fee: float,
45
+ open_fee_rate: float,
46
+ open_time: int,
47
+ is_spot: bool,
48
+ ):
49
+ redis = self._server._redis
50
+ if redis is None:
51
+ return
52
+ data = {
53
+ "id": position_id,
54
+ "exchange": exchange,
55
+ "symbol": symbol,
56
+ "position_side": position_side.value,
57
+ "lever": lever,
58
+ "coin_quantity": coin_quantity,
59
+ "usdt_quantity": usdt_quantity,
60
+ "open_ins_id": open_ins_id,
61
+ "open_price": open_price,
62
+ "open_fee": open_fee,
63
+ "open_fee_rate": open_fee_rate,
64
+ "open_time": open_time,
65
+ "close_ins_id": None,
66
+ "close_price": 0,
67
+ "close_time": 0,
68
+ "status": PositionStatus.OPEN.value,
69
+ "is_spot": is_spot,
70
+ }
71
+ redis.client.hset(self._redis_key_position, position_id, json.dumps(data))
72
+
73
+ def _write_position_close_to_redis(
74
+ self,
75
+ position_id: str,
76
+ exchange: str,
77
+ symbol: str,
78
+ position_side,
79
+ lever: int,
80
+ coin_quantity: float,
81
+ usdt_quantity: float,
82
+ open_ins_id: str,
83
+ open_price: float,
84
+ open_fee: float,
85
+ open_fee_rate: float,
86
+ open_time: int,
87
+ close_ins_id: str,
88
+ close_price: float,
89
+ close_fee: float,
90
+ close_fee_rate: float,
91
+ close_time: int,
92
+ is_spot: bool,
93
+ ):
94
+ redis = self._server._redis
95
+ if redis is None:
96
+ return
97
+
98
+ # 先从 redis 读取现有的 position 数据,获取 funding_rate_records 字段
99
+ funding_rate_records = None
100
+ try:
101
+ existing_position_json = redis.client.hget(self._redis_key_position, position_id)
102
+ if existing_position_json:
103
+ existing_position = json.loads(existing_position_json)
104
+ if existing_position and "funding_rate_records" in existing_position:
105
+ funding_rate_records = existing_position.get("funding_rate_records")
106
+ except Exception as e:
107
+ # 读取失败不影响后续流程,记录日志
108
+ self._logger.warning(f"Failed to get funding_rate_records for position {position_id}: {e}")
109
+
110
+ data = {
111
+ "id": position_id,
112
+ "exchange": exchange,
113
+ "symbol": symbol,
114
+ "position_side": position_side.value,
115
+ "lever": lever,
116
+ "coin_quantity": coin_quantity,
117
+ "usdt_quantity": usdt_quantity,
118
+ "open_ins_id": open_ins_id,
119
+ "open_price": open_price,
120
+ "open_fee": open_fee,
121
+ "open_fee_rate": open_fee_rate,
122
+ "open_time": open_time,
123
+ "close_ins_id": close_ins_id,
124
+ "close_price": close_price,
125
+ "close_fee": close_fee,
126
+ "close_fee_rate": close_fee_rate,
127
+ "close_time": close_time,
128
+ "status": PositionStatus.CLOSE.value,
129
+ "is_spot": is_spot,
130
+ }
131
+
132
+ # 如果存在 funding_rate_records,添加到 data 中
133
+ if funding_rate_records is not None:
134
+ data["funding_rate_records"] = funding_rate_records
135
+
136
+ redis.client.hdel(self._redis_key_position, position_id)
137
+ redis.client.rpush(self._redis_key_position_history, json.dumps(data))
138
+
139
+ def process_order(self, order: OrderInfo, get_order_result: Callable):
140
+ # 获取交易所
141
+ exchange = self._server._exchange
142
+
143
+ # 记录时间,统计成交耗时
144
+ start_time = time.time()
145
+
146
+ #
147
+ if not self._do_process_order(exchange, order, get_order_result, True, start_time):
148
+ # 马上执行,没有成功,开启线程执行
149
+ thread = threading.Thread(
150
+ target=self._do_process_order,
151
+ args=(exchange, order, get_order_result, False, start_time),
152
+ )
153
+ thread.name = f"process_order_{order.instruction_id}_{exchange}_{order.symbol}_{order.order_id}"
154
+ thread.daemon = True
155
+ thread.start()
156
+
157
+ def _do_process_order(
158
+ self,
159
+ exchange: str,
160
+ order: OrderInfo,
161
+ get_order_result: Callable,
162
+ first=True,
163
+ start_time: float = 0,
164
+ ):
165
+ # 获取mysql
166
+ mysql = self._server._mysql
167
+
168
+ #
169
+ ins_id = order.instruction_id
170
+ order_id = order.order_id
171
+ symbol = order.symbol
172
+ side = order.side
173
+ position_side = order.position_side
174
+ lever = order.level
175
+ # TODO
176
+ is_spot = False
177
+
178
+ is_open = True
179
+ side_str = "开仓"
180
+ if position_side == PositionSide.LONG:
181
+ # 多单是正向理解的
182
+ if side == OrderSide.SELL:
183
+ side_str = "平仓"
184
+ is_open = False
185
+ else:
186
+ side_str = "开仓"
187
+ is_open = True
188
+ else:
189
+ # 空单是反向理解的
190
+ if side == OrderSide.SELL:
191
+ side_str = "开仓"
192
+ is_open = True
193
+ else:
194
+ side_str = "平仓"
195
+ is_open = False
196
+
197
+ if first:
198
+ self._logger.info(f"{ins_id}_{exchange}_{symbol} step 1. {"现货" if is_spot else "合约"}{side_str}挂单成功 {order_id}")
199
+
200
+ # 步骤1.挂单成功 插入到订单记录
201
+ # 获取当前时间-ms
202
+ current_time = int(time.time() * 1000)
203
+
204
+ if mysql is not None:
205
+ status = OrderStatus.CREATE
206
+ sql = f"""
207
+ INSERT INTO {self._mysql_table_name_order} (ins_id, exchange, symbol, side, position_side, lever, orig_price, orig_coin_quantity, order_id, status, create_time, last_update_time, is_spot)
208
+ VALUES ( '{ins_id}', '{exchange}', '{symbol}', '{side.value}', '{order.position_side.value}', '{lever}', {order.current_price or order.target_price}, {order.quantity}, '{order_id}', '{status.value}', {current_time}, {current_time}, '{1 if is_spot else 0}' );
209
+ """
210
+ execute_ret = mysql.execute_sql(sql, True)
211
+
212
+ # 步骤2.查询订单状态 直到订单成交后
213
+ # 统计查询次数
214
+ query_counter = 0
215
+ while True:
216
+ query_counter += 1
217
+ # 获取订单结果
218
+ order_info = None
219
+ try:
220
+ order_info = get_order_result()
221
+ except Exception as e:
222
+ self._logger.error(f"{ins_id}_{exchange}_{symbol} step 2. {side_str}订单 查询状态失败 {e}")
223
+
224
+ if order_info is not None:
225
+ break
226
+ # 只查询一次
227
+ if first:
228
+ return False
229
+ # 等待
230
+ time.sleep(1)
231
+
232
+ if not first:
233
+ # 需要加上第一查询
234
+ query_counter += 1
235
+
236
+ # 记录时间,统计成交耗时
237
+ end_time = time.time()
238
+ cost_time = end_time - start_time
239
+ self._logger.info(
240
+ f"{ins_id}_{exchange}_{symbol} step 2. {side_str}订单 {order_id} 成交 耗时 {int(cost_time * 1000)}ms, 查询次数 {query_counter}"
241
+ )
242
+
243
+ # 步骤3.把最终持仓写进去
244
+ # 平均成交价格 转float
245
+ avg_price = float(order_info["avg_price"])
246
+ # 最终成交数量 转float
247
+ executed_qty = float(order_info["executed_qty"])
248
+ # 计算出usdt数量
249
+ executed_usdt = avg_price * executed_qty
250
+ # 手续费,不一定有
251
+ fee = float(order_info["fee"]) if "fee" in order_info else 0.0
252
+ # 费率
253
+ fee_rate = fee / executed_usdt
254
+
255
+ current_time = int(time.time() * 1000)
256
+
257
+ if mysql is None:
258
+ self._logger.warning(f"{ins_id}_{exchange}_{symbol} 仅操作,没有入库,请设置 mysql!!")
259
+ return
260
+
261
+ status = OrderStatus.FINISH
262
+ # 更新写入最终信息
263
+ sql = f"""
264
+ UPDATE {self._mysql_table_name_order}
265
+ SET price = {avg_price}, coin_quantity = {executed_qty}, usdt_quantity = {executed_usdt}, fee = {fee}, fee_rate = {fee_rate}, status = '{status.value}', last_update_time = {current_time}
266
+ WHERE ins_id = '{ins_id}' AND exchange = '{exchange}' AND symbol = '{symbol}';
267
+ """
268
+ execute_ret = mysql.execute_sql(sql, True)
269
+
270
+ self._logger.info(
271
+ f"{ins_id}_{exchange}_{symbol} step 2. 订单成交 {order_id}, {side_str}价格 {avg_price}, {side_str}数量 {executed_qty}, {side_str}usdt {executed_usdt} 杠杆 {lever}"
272
+ )
273
+ if is_open:
274
+ # 同时插入持仓表
275
+ position_id = uuid_utils.generate_uuid()
276
+ sql = f"""
277
+ INSERT INTO {self._mysql_table_name_position} (id, exchange, symbol, position_side, lever, coin_quantity, usdt_quantity, open_ins_id, open_price, open_fee, open_fee_rate, open_time, status, is_spot)
278
+ VALUES ( '{position_id}', '{exchange}', '{symbol}', '{position_side.value}', '{lever}', '{executed_qty}', '{executed_usdt}', '{ins_id}', '{avg_price}', '{fee}', '{fee_rate}', {current_time}, '{PositionStatus.OPEN.value}', '{1 if is_spot else 0}' );
279
+ """
280
+ execute_ret = mysql.execute_sql(sql, True)
281
+
282
+ self._logger.info(f"{ins_id}_{exchange}_{symbol} step 3. 创建持仓记录 {position_id}")
283
+ try:
284
+ self._write_position_open_to_redis(
285
+ position_id=position_id,
286
+ exchange=exchange,
287
+ symbol=symbol,
288
+ position_side=position_side,
289
+ lever=lever,
290
+ coin_quantity=executed_qty,
291
+ usdt_quantity=executed_usdt,
292
+ open_ins_id=ins_id,
293
+ open_price=avg_price,
294
+ open_fee=fee,
295
+ open_fee_rate=fee_rate,
296
+ open_time=current_time,
297
+ is_spot=is_spot,
298
+ )
299
+ except:
300
+ pass
301
+ else:
302
+ # 需要找到对应的持仓记录
303
+ sql = f"""
304
+ SELECT * FROM {self._mysql_table_name_position}
305
+ WHERE exchange = '{exchange}'
306
+ AND symbol = '{symbol}'
307
+ AND position_side = '{position_side.value}'
308
+ AND status = '{PositionStatus.OPEN.value}'
309
+ AND is_spot = '{1 if is_spot else 0}'
310
+ AND is_mock = 0
311
+ ORDER BY open_time ASC;
312
+ """
313
+
314
+ # 如果有指定仓位id,就用指定的
315
+ if hasattr(order, "position_id") and order.position_id:
316
+ sql = f"""
317
+ SELECT * FROM {self._mysql_table_name_position}
318
+ WHERE id = '{order.position_id}' AND status = '{PositionStatus.OPEN.value}'
319
+ """
320
+ self._logger.info(f"{ins_id}_{exchange}_{symbol} get position by id {order.position_id}")
321
+
322
+ execute_ret = mysql.execute_sql(sql)
323
+ try:
324
+ row = execute_ret.fetchone()
325
+ position_id = row.id
326
+ if position_id is not None:
327
+ # 更新持仓信息
328
+ sql = f"""
329
+ UPDATE {self._mysql_table_name_position}
330
+ SET close_ins_id = '{ins_id}', close_price = {avg_price}, close_fee = '{fee}', close_fee_rate = '{fee_rate}', close_time = {current_time}, status = '{PositionStatus.CLOSE.value}'
331
+ WHERE id = '{position_id}';
332
+ """
333
+ execute_ret = mysql.execute_sql(sql, True)
334
+
335
+ self._logger.info(f"{ins_id}_{exchange}_{symbol} step 3. 更新持仓记录 {position_id}")
336
+ try:
337
+ self._write_position_close_to_redis(
338
+ position_id=position_id,
339
+ exchange=exchange,
340
+ symbol=symbol,
341
+ position_side=position_side,
342
+ lever=row.lever,
343
+ coin_quantity=float(row.coin_quantity),
344
+ usdt_quantity=float(row.usdt_quantity),
345
+ open_ins_id=row.open_ins_id,
346
+ open_price=float(row.open_price),
347
+ open_fee=float(row.open_fee),
348
+ open_fee_rate=float(row.open_fee_rate),
349
+ open_time=int(row.open_time),
350
+ close_ins_id=ins_id,
351
+ close_price=avg_price,
352
+ close_fee=fee,
353
+ close_fee_rate=fee_rate,
354
+ close_time=current_time,
355
+ is_spot=row.is_spot,
356
+ )
357
+ except:
358
+ pass
359
+ except:
360
+ pass
361
+
362
+ return True