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