Coverage for core/data_source/adapters/trade_adapter.py: 53.20%

359 statements  

« prev     ^ index     » next       coverage.py v7.10.7, created at 2025-10-13 18:58 +0000

1""" 

2交易数据源适配器 

3提供统一的交易接口,屏蔽具体券商SDK差异 

4""" 

5 

6from datetime import date, datetime 

7from decimal import Decimal 

8from typing import Any, Dict, List, Optional 

9 

10from core.data_source.adapters.data_source_adapter import DataSourceAdapter 

11 

12# 市场类型限制配置 

13MARKET_ORDER_TYPES = { 

14 "US": ["LO", "MO", "LIT", "MIT", "TSLPAMT", "TSLPPCT"], 

15 "HK": [ 

16 "LO", 

17 "ELO", 

18 "MO", 

19 "AO", 

20 "ALO", 

21 "ODD", 

22 "LIT", 

23 "MIT", 

24 "TSLPAMT", 

25 "TSLPPCT", 

26 "SLO", 

27 ], 

28 "CN": ["LO", "MO"], 

29 "SG": ["LO", "MO"], 

30 "CRYPTO": ["LO", "MO"], 

31} 

32 

33# 订单类型必填字段配置 

34ORDER_TYPE_REQUIRED_FIELDS = { 

35 "LO": ["submitted_price"], # 限价单 

36 "ELO": ["submitted_price"], # 增强限价单 

37 "ALO": ["submitted_price"], # 竞价限价单 

38 "ODD": ["submitted_price"], # 碎股单 

39 "LIT": ["submitted_price", "trigger_price"], # 触价限价单 

40 "MIT": ["trigger_price"], # 触价市价单 

41 "TSLPAMT": ["trailing_amount"], # 跟踪止损限价单(跟踪金额) 

42 "TSLPPCT": ["trailing_percent"], # 跟踪止损限价单(跟踪涨跌幅) 

43 "MO": [], # 市价单 

44 "AO": [], # 竞价市价单 

45 "SLO": ["submitted_price"], # 特殊限价单 

46} 

47 

48# 美股必填字段 

49US_REQUIRED_FIELDS = ["outside_rth"] 

50 

51# GTD订单类型必填字段 

52GTD_REQUIRED_FIELDS = ["expire_date"] 

53 

54 

55class TradeDataSourceAdapter(DataSourceAdapter): 

56 """交易数据源适配器""" 

57 

58 def is_available(self) -> bool: 

59 """检查是否可用""" 

60 return self._get_client() is not None 

61 

62 def validate_order_params( 

63 self, 

64 symbol: str, 

65 order_type: str, 

66 time_in_force: str, 

67 submitted_price: Optional[Decimal] = None, 

68 trigger_price: Optional[Decimal] = None, 

69 trailing_amount: Optional[Decimal] = None, 

70 trailing_percent: Optional[Decimal] = None, 

71 expire_date: Optional[date] = None, 

72 outside_rth: Optional[str] = None, 

73 ) -> Dict[str, Any]: 

74 """验证订单参数""" 

75 errors = [] 

76 warnings = [] 

77 

78 # 从symbol中提取市场类型 

79 market = self._extract_market_from_symbol(symbol) 

80 

81 # 验证市场类型是否支持该订单类型 

82 if market in MARKET_ORDER_TYPES: 

83 if order_type not in MARKET_ORDER_TYPES[market]: 

84 errors.append(f"市场 {market} 不支持订单类型 {order_type}") 

85 else: 

86 warnings.append(f"未知市场类型: {market}") 

87 

88 # 验证订单类型必填字段 

89 required_fields = ORDER_TYPE_REQUIRED_FIELDS.get(order_type, []) 

90 for field in required_fields: 

91 if field == "submitted_price" and submitted_price is None: 

92 errors.append(f"订单类型 {order_type} 需要填写委托价格") 

93 elif field == "trigger_price" and trigger_price is None: 

94 errors.append(f"订单类型 {order_type} 需要填写触发价格") 

95 elif field == "trailing_amount" and trailing_amount is None: 

96 errors.append(f"订单类型 {order_type} 需要填写跟踪金额") 

97 elif field == "trailing_percent" and trailing_percent is None: 

98 errors.append(f"订单类型 {order_type} 需要填写跟踪涨跌幅") 

99 

100 # 验证美股必填字段 

101 if market == "US" and outside_rth is None: 

102 errors.append("美股订单必须选择盘前盘后交易选项") 

103 

104 # 验证GTD订单类型必填字段 

105 if time_in_force == "GTD" and expire_date is None: 

106 errors.append("GTD订单类型必须填写过期日期") 

107 

108 return { 

109 "valid": len(errors) == 0, 

110 "errors": errors, 

111 "warnings": warnings, 

112 "market": market, 

113 } 

114 

115 def _extract_market_from_symbol(self, symbol: str) -> str: 

116 """从股票代码中提取市场类型""" 

117 if "." in symbol: 

118 market_suffix = symbol.split(".")[-1].upper() 

119 market_mapping = { 

120 "US": "US", 

121 "HK": "HK", 

122 "SZ": "CN", 

123 "SH": "CN", 

124 "SG": "SG", 

125 } 

126 return market_mapping.get(market_suffix, "UNKNOWN") 

127 return "UNKNOWN" 

128 

129 def get_supported_order_types(self, market: str) -> List[str]: 

130 """获取指定市场支持的订单类型""" 

131 return MARKET_ORDER_TYPES.get(market, []) 

132 

133 def get_order_type_required_fields(self, order_type: str) -> List[str]: 

134 """获取订单类型必填字段""" 

135 return ORDER_TYPE_REQUIRED_FIELDS.get(order_type, []) 

136 

137 def submit_order( 

138 self, 

139 symbol: str, 

140 order_type: str, 

141 side: str, 

142 submitted_quantity: Decimal, 

143 time_in_force: str, 

144 submitted_price: Optional[Decimal] = None, 

145 trigger_price: Optional[Decimal] = None, 

146 limit_offset: Optional[Decimal] = None, 

147 trailing_amount: Optional[Decimal] = None, 

148 trailing_percent: Optional[Decimal] = None, 

149 expire_date: Optional[date] = None, 

150 outside_rth: Optional[str] = None, 

151 remark: Optional[str] = None, 

152 ) -> Optional[Dict[str, Any]]: 

153 """委托下单""" 

154 client = self._get_client() 

155 if not client: 

156 return None 

157 

158 # 验证订单参数 

159 validation = self.validate_order_params( 

160 symbol=symbol, 

161 order_type=order_type, 

162 time_in_force=time_in_force, 

163 submitted_price=submitted_price, 

164 trigger_price=trigger_price, 

165 trailing_amount=trailing_amount, 

166 trailing_percent=trailing_percent, 

167 expire_date=expire_date, 

168 outside_rth=outside_rth, 

169 ) 

170 

171 if not validation["valid"]: 

172 return { 

173 "success": False, 

174 "message": "订单参数验证失败", 

175 "errors": validation["errors"], 

176 "warnings": validation["warnings"], 

177 } 

178 

179 try: 

180 trade_ctx = client.get("trade_ctx") 

181 if trade_ctx: 

182 # 转换枚举值 

183 order_type_enum = self._get_order_type_enum(order_type) 

184 side_enum = self._get_order_side_enum(side) 

185 time_in_force_enum = self._get_time_in_force_enum(time_in_force) 

186 outside_rth_enum = ( 

187 self._get_outside_rth_enum(outside_rth) if outside_rth else None 

188 ) 

189 

190 # 处理时区转换 

191 processed_expire_date = self._convert_expire_date_timezone( 

192 symbol, expire_date 

193 ) 

194 

195 # 调用LongPort下单接口 

196 result = trade_ctx.submit_order( 

197 symbol=symbol, 

198 order_type=order_type_enum, 

199 side=side_enum, 

200 submitted_quantity=submitted_quantity, 

201 time_in_force=time_in_force_enum, 

202 submitted_price=submitted_price, 

203 trigger_price=trigger_price, 

204 limit_offset=limit_offset, 

205 trailing_amount=trailing_amount, 

206 trailing_percent=trailing_percent, 

207 expire_date=processed_expire_date, 

208 outside_rth=outside_rth_enum, 

209 remark=remark, 

210 ) 

211 

212 return self._serialize_submit_order_response(result) 

213 except Exception as e: 

214 print(f"❌ 委托下单失败: {e}") 

215 return {"success": False, "message": f"委托下单失败: {e}", "error": str(e)} 

216 

217 return None 

218 

219 def replace_order( 

220 self, 

221 order_id: str, 

222 quantity: Decimal, 

223 price: Optional[Decimal] = None, 

224 trigger_price: Optional[Decimal] = None, 

225 remark: Optional[str] = None, 

226 ) -> bool: 

227 """改单""" 

228 client = self._get_client() 

229 if not client: 

230 return False 

231 

232 try: 

233 trade_ctx = client.get("trade_ctx") 

234 if trade_ctx: 

235 trade_ctx.replace_order( 

236 order_id=order_id, 

237 quantity=quantity, 

238 price=price, 

239 trigger_price=trigger_price, 

240 remark=remark, 

241 ) 

242 return True 

243 except Exception as e: 

244 print(f"❌ 改单失败: {e}") 

245 

246 return False 

247 

248 def cancel_order(self, order_id: str) -> bool: 

249 """撤单""" 

250 client = self._get_client() 

251 if not client: 

252 return False 

253 

254 try: 

255 trade_ctx = client.get("trade_ctx") 

256 if trade_ctx: 

257 trade_ctx.cancel_order(order_id) 

258 return True 

259 except Exception as e: 

260 print(f"❌ 撤单失败: {e}") 

261 

262 return False 

263 

264 def get_today_orders( 

265 self, 

266 symbol: Optional[str] = None, 

267 status: Optional[List[str]] = None, 

268 side: Optional[str] = None, 

269 market: Optional[str] = None, 

270 order_id: Optional[str] = None, 

271 ) -> List[Dict[str, Any]]: 

272 """获取当日订单""" 

273 client = self._get_client() 

274 if not client: 

275 return [] 

276 

277 try: 

278 trade_ctx = client.get("trade_ctx") 

279 if trade_ctx: 

280 # 转换枚举值 

281 status_enums = None 

282 if status: 

283 status_enums = [self._get_order_status_enum(s) for s in status] 

284 

285 side_enum = None 

286 if side: 

287 side_enum = self._get_order_side_enum(side) 

288 

289 market_enum = None 

290 if market: 

291 market_enum = self._get_market_enum(market) 

292 

293 orders = trade_ctx.today_orders( 

294 symbol=symbol, 

295 status=status_enums, 

296 side=side_enum, 

297 market=market_enum, 

298 order_id=order_id, 

299 ) 

300 

301 return [self._serialize_order(order) for order in orders] 

302 except Exception as e: 

303 print(f"❌ 获取当日订单失败: {e}") 

304 

305 return [] 

306 

307 def get_history_orders( 

308 self, 

309 symbol: Optional[str] = None, 

310 status: Optional[List[str]] = None, 

311 side: Optional[str] = None, 

312 market: Optional[str] = None, 

313 start_at: Optional[datetime] = None, 

314 end_at: Optional[datetime] = None, 

315 ) -> List[Dict[str, Any]]: 

316 """获取历史订单""" 

317 client = self._get_client() 

318 if not client: 

319 return [] 

320 

321 try: 

322 trade_ctx = client.get("trade_ctx") 

323 if trade_ctx: 

324 # 设置默认时间范围 

325 processed_start_at, processed_end_at = self._set_default_time_range( 

326 symbol or "", start_at, end_at 

327 ) 

328 

329 # 转换时区 

330 processed_start_at = self._convert_datetime_timezone( 

331 symbol or "", processed_start_at 

332 ) 

333 processed_end_at = self._convert_datetime_timezone( 

334 symbol or "", processed_end_at 

335 ) 

336 

337 # 转换枚举值 

338 status_enums = None 

339 if status: 

340 status_enums = [self._get_order_status_enum(s) for s in status] 

341 

342 side_enum = None 

343 if side: 

344 side_enum = self._get_order_side_enum(side) 

345 

346 market_enum = None 

347 if market: 

348 market_enum = self._get_market_enum(market) 

349 

350 orders = trade_ctx.history_orders( 

351 symbol=symbol, 

352 status=status_enums, 

353 side=side_enum, 

354 market=market_enum, 

355 start_at=processed_start_at, 

356 end_at=processed_end_at, 

357 ) 

358 

359 return [self._serialize_order(order) for order in orders] 

360 except Exception as e: 

361 print(f"❌ 获取历史订单失败: {e}") 

362 

363 return [] 

364 

365 def get_today_executions( 

366 self, symbol: Optional[str] = None, order_id: Optional[str] = None 

367 ) -> List[Dict[str, Any]]: 

368 """获取当日成交明细""" 

369 client = self._get_client() 

370 if not client: 

371 return [] 

372 

373 try: 

374 trade_ctx = client.get("trade_ctx") 

375 if trade_ctx: 

376 executions = trade_ctx.today_executions( 

377 symbol=symbol, order_id=order_id 

378 ) 

379 

380 return [ 

381 self._serialize_execution(execution) for execution in executions 

382 ] 

383 except Exception as e: 

384 print(f"❌ 获取当日成交明细失败: {e}") 

385 

386 return [] 

387 

388 def get_history_executions( 

389 self, 

390 symbol: Optional[str] = None, 

391 start_at: Optional[datetime] = None, 

392 end_at: Optional[datetime] = None, 

393 ) -> List[Dict[str, Any]]: 

394 """获取历史成交明细""" 

395 client = self._get_client() 

396 if not client: 

397 return [] 

398 

399 try: 

400 trade_ctx = client.get("trade_ctx") 

401 if trade_ctx: 

402 # 设置默认时间范围 

403 processed_start_at, processed_end_at = self._set_default_time_range( 

404 symbol or "", start_at, end_at 

405 ) 

406 

407 # 转换时区 

408 processed_start_at = self._convert_datetime_timezone( 

409 symbol or "", processed_start_at 

410 ) 

411 processed_end_at = self._convert_datetime_timezone( 

412 symbol or "", processed_end_at 

413 ) 

414 

415 executions = trade_ctx.history_executions( 

416 symbol=symbol, start_at=processed_start_at, end_at=processed_end_at 

417 ) 

418 

419 return [ 

420 self._serialize_execution(execution) for execution in executions 

421 ] 

422 except Exception as e: 

423 print(f"❌ 获取历史成交明细失败: {e}") 

424 

425 return [] 

426 

427 def get_order_detail(self, order_id: str) -> Optional[Dict[str, Any]]: 

428 """获取订单详情""" 

429 client = self._get_client() 

430 if not client: 

431 return None 

432 

433 try: 

434 trade_ctx = client.get("trade_ctx") 

435 if trade_ctx: 

436 order_detail = trade_ctx.order_detail(order_id) 

437 return self._serialize_order_detail(order_detail) 

438 except Exception as e: 

439 print(f"❌ 获取订单详情失败: {e}") 

440 

441 return None 

442 

443 def get_account_balance( 

444 self, currency: Optional[str] = None 

445 ) -> List[Dict[str, Any]]: 

446 """获取账户余额""" 

447 client = self._get_client() 

448 if not client: 

449 return [] 

450 

451 try: 

452 trade_ctx = client.get("trade_ctx") 

453 if trade_ctx: 

454 balances = trade_ctx.account_balance(currency=currency) 

455 serialized_balances = [] 

456 for balance in balances: 

457 try: 

458 serialized_balance = self._serialize_account_balance(balance) 

459 if serialized_balance: # 确保序列化成功 

460 serialized_balances.append(serialized_balance) 

461 except Exception as e: 

462 print(f"❌ 序列化单个账户余额失败: {e}") 

463 continue 

464 return serialized_balances 

465 except Exception as e: 

466 print(f"❌ 获取账户余额失败: {e}") 

467 

468 return [] 

469 

470 def estimate_max_purchase_quantity( 

471 self, 

472 symbol: str, 

473 order_type: str, 

474 side: str, 

475 price: Optional[Decimal] = None, 

476 currency: Optional[str] = None, 

477 order_id: Optional[str] = None, 

478 fractional_shares: bool = False, 

479 ) -> Optional[Dict[str, Any]]: 

480 """估算最大购买数量""" 

481 client = self._get_client() 

482 if not client: 

483 return None 

484 

485 try: 

486 trade_ctx = client.get("trade_ctx") 

487 if trade_ctx: 

488 # 转换枚举值 

489 order_type_enum = self._get_order_type_enum(order_type) 

490 side_enum = self._get_order_side_enum(side) 

491 

492 result = trade_ctx.estimate_max_purchase_quantity( 

493 symbol=symbol, 

494 order_type=order_type_enum, 

495 side=side_enum, 

496 price=price, 

497 currency=currency, 

498 order_id=order_id, 

499 fractional_shares=fractional_shares, 

500 ) 

501 

502 return self._serialize_max_purchase_quantity_response(result) 

503 except Exception as e: 

504 print(f"❌ 估算最大购买数量失败: {e}") 

505 

506 return None 

507 

508 # 序列化方法 

509 def _serialize_submit_order_response(self, response) -> Dict[str, Any]: 

510 """序列化下单响应""" 

511 if not response: 

512 return {} 

513 

514 try: 

515 return { 

516 "order_id": getattr(response, "order_id", ""), 

517 "status": str(getattr(response, "status", "")), 

518 "message": getattr(response, "message", ""), 

519 } 

520 except Exception as e: 

521 print(f"❌ 序列化下单响应失败: {e}") 

522 return {} 

523 

524 def _serialize_order(self, order) -> Dict[str, Any]: 

525 """序列化订单信息""" 

526 if not order: 

527 return {} 

528 

529 try: 

530 return { 

531 "order_id": getattr(order, "order_id", ""), 

532 "status": str(getattr(order, "status", "")), 

533 "stock_name": getattr(order, "stock_name", ""), 

534 "symbol": getattr(order, "symbol", ""), 

535 "side": str(getattr(order, "side", "")), 

536 "order_type": str(getattr(order, "order_type", "")), 

537 "quantity": float(getattr(order, "quantity", 0)), 

538 "executed_quantity": float(getattr(order, "executed_quantity", 0)), 

539 "price": ( 

540 float(getattr(order, "price", 0)) 

541 if getattr(order, "price", None) is not None 

542 else None 

543 ), 

544 "executed_price": ( 

545 float(getattr(order, "executed_price", 0)) 

546 if getattr(order, "executed_price", None) is not None 

547 else None 

548 ), 

549 "submitted_at": getattr(order, "submitted_at", None), 

550 "updated_at": getattr(order, "updated_at", None), 

551 "last_done": ( 

552 float(getattr(order, "last_done", 0)) 

553 if getattr(order, "last_done", None) is not None 

554 else None 

555 ), 

556 "trigger_price": ( 

557 float(getattr(order, "trigger_price", 0)) 

558 if getattr(order, "trigger_price", None) is not None 

559 else None 

560 ), 

561 "msg": getattr(order, "msg", ""), 

562 "tag": str(getattr(order, "tag", "")), 

563 "time_in_force": str(getattr(order, "time_in_force", "")), 

564 "expire_date": getattr(order, "expire_date", None), 

565 "currency": getattr(order, "currency", ""), 

566 "remark": getattr(order, "remark", ""), 

567 } 

568 except Exception as e: 

569 print(f"❌ 序列化订单信息失败: {e}") 

570 return {} 

571 

572 def _serialize_execution(self, execution) -> Dict[str, Any]: 

573 """序列化成交明细""" 

574 if not execution: 

575 return {} 

576 

577 try: 

578 return { 

579 "order_id": getattr(execution, "order_id", ""), 

580 "trade_id": getattr(execution, "trade_id", ""), 

581 "symbol": getattr(execution, "symbol", ""), 

582 "trade_done_at": getattr(execution, "trade_done_at", None), 

583 "quantity": float(getattr(execution, "quantity", 0)), 

584 "price": float(getattr(execution, "price", 0)), 

585 } 

586 except Exception as e: 

587 print(f"❌ 序列化成交明细失败: {e}") 

588 return {} 

589 

590 def _serialize_order_detail(self, order_detail) -> Dict[str, Any]: 

591 """序列化订单详情""" 

592 if not order_detail: 

593 return {} 

594 

595 try: 

596 # 序列化history字段 

597 history = [] 

598 for hist_item in getattr(order_detail, "history", []): 

599 history.append( 

600 { 

601 "status": str(getattr(hist_item, "status", "")), 

602 "time": getattr(hist_item, "time", None), 

603 "quantity": float(getattr(hist_item, "quantity", 0)), 

604 "price": ( 

605 float(getattr(hist_item, "price", 0)) 

606 if getattr(hist_item, "price", None) is not None 

607 else None 

608 ), 

609 "msg": getattr(hist_item, "msg", ""), 

610 } 

611 ) 

612 

613 # 序列化charge_detail字段 

614 charge_detail = {} 

615 charge_detail_obj = getattr(order_detail, "charge_detail", None) 

616 if charge_detail_obj: 

617 charge_detail = { 

618 "total_charges": float( 

619 getattr(charge_detail_obj, "total_charges", 0) 

620 ), 

621 "currency": getattr(charge_detail_obj, "currency", ""), 

622 "charges": [], 

623 } 

624 

625 # 序列化charges列表 

626 for charge in getattr(charge_detail_obj, "charges", []): 

627 charge_detail["charges"].append( 

628 { 

629 "code": getattr(charge, "code", ""), 

630 "name": getattr(charge, "name", ""), 

631 "fees": [], 

632 } 

633 ) 

634 

635 # 序列化fees列表 

636 for fee in getattr(charge, "fees", []): 

637 charge_detail["charges"][-1]["fees"].append( 

638 { 

639 "code": getattr(fee, "code", ""), 

640 "name": getattr(fee, "name", ""), 

641 "amount": float(getattr(fee, "amount", 0)), 

642 "currency": getattr(fee, "currency", ""), 

643 } 

644 ) 

645 

646 return { 

647 "order_id": getattr(order_detail, "order_id", ""), 

648 "status": str(getattr(order_detail, "status", "")), 

649 "stock_name": getattr(order_detail, "stock_name", ""), 

650 "symbol": getattr(order_detail, "symbol", ""), 

651 "side": str(getattr(order_detail, "side", "")), 

652 "order_type": str(getattr(order_detail, "order_type", "")), 

653 "quantity": float(getattr(order_detail, "quantity", 0)), 

654 "executed_quantity": float( 

655 getattr(order_detail, "executed_quantity", 0) 

656 ), 

657 "price": ( 

658 float(getattr(order_detail, "price", 0)) 

659 if getattr(order_detail, "price", None) is not None 

660 else None 

661 ), 

662 "executed_price": ( 

663 float(getattr(order_detail, "executed_price", 0)) 

664 if getattr(order_detail, "executed_price", None) is not None 

665 else None 

666 ), 

667 "submitted_at": getattr(order_detail, "submitted_at", None), 

668 "updated_at": getattr(order_detail, "updated_at", None), 

669 "last_done": ( 

670 float(getattr(order_detail, "last_done", 0)) 

671 if getattr(order_detail, "last_done", None) is not None 

672 else None 

673 ), 

674 "trigger_price": ( 

675 float(getattr(order_detail, "trigger_price", 0)) 

676 if getattr(order_detail, "trigger_price", None) is not None 

677 else None 

678 ), 

679 "msg": getattr(order_detail, "msg", ""), 

680 "tag": str(getattr(order_detail, "tag", "")), 

681 "time_in_force": str(getattr(order_detail, "time_in_force", "")), 

682 "expire_date": getattr(order_detail, "expire_date", None), 

683 "currency": getattr(order_detail, "currency", ""), 

684 "remark": getattr(order_detail, "remark", ""), 

685 "free_status": str(getattr(order_detail, "free_status", "")), 

686 "free_amount": ( 

687 float(getattr(order_detail, "free_amount", 0)) 

688 if getattr(order_detail, "free_amount", None) is not None 

689 else None 

690 ), 

691 "free_currency": getattr(order_detail, "free_currency", ""), 

692 "deductions_status": str( 

693 getattr(order_detail, "deductions_status", "") 

694 ), 

695 "deductions_amount": ( 

696 float(getattr(order_detail, "deductions_amount", 0)) 

697 if getattr(order_detail, "deductions_amount", None) is not None 

698 else None 

699 ), 

700 "deductions_currency": getattr(order_detail, "deductions_currency", ""), 

701 "platform_deducted_status": str( 

702 getattr(order_detail, "platform_deducted_status", "") 

703 ), 

704 "platform_deducted_amount": ( 

705 float(getattr(order_detail, "platform_deducted_amount", 0)) 

706 if getattr(order_detail, "platform_deducted_amount", None) 

707 is not None 

708 else None 

709 ), 

710 "platform_deducted_currency": getattr( 

711 order_detail, "platform_deducted_currency", "" 

712 ), 

713 "history": history, 

714 "charge_detail": charge_detail, 

715 } 

716 except Exception as e: 

717 print(f"❌ 序列化订单详情失败: {e}") 

718 return {} 

719 

720 def _serialize_account_balance(self, balance) -> Dict[str, Any]: 

721 """序列化账户余额""" 

722 if not balance: 

723 return {} 

724 

725 try: 

726 # 序列化cash_infos 

727 cash_infos = [] 

728 cash_infos_raw = getattr(balance, "cash_infos", []) 

729 if cash_infos_raw: 

730 for cash_info in cash_infos_raw: 

731 try: 

732 cash_infos.append( 

733 { 

734 "currency": str(getattr(cash_info, "currency", "")), 

735 "available_cash": float( 

736 getattr(cash_info, "available_cash", 0) 

737 ), 

738 "frozen_cash": float( 

739 getattr(cash_info, "frozen_cash", 0) 

740 ), 

741 "settling_cash": float( 

742 getattr(cash_info, "settling_cash", 0) 

743 ), 

744 "currency_earning": float( 

745 getattr(cash_info, "currency_earning", 0) 

746 ), 

747 } 

748 ) 

749 except Exception as e: 

750 print(f"❌ 序列化cash_info失败: {e}") 

751 continue 

752 

753 # 序列化frozen_transaction_fees 

754 frozen_fees = [] 

755 frozen_fees_raw = getattr(balance, "frozen_transaction_fees", []) 

756 if frozen_fees_raw: 

757 for fee in frozen_fees_raw: 

758 try: 

759 frozen_fees.append( 

760 { 

761 "currency": str(getattr(fee, "currency", "")), 

762 "frozen_transaction_fee": float( 

763 getattr(fee, "frozen_transaction_fee", 0) 

764 ), 

765 } 

766 ) 

767 except Exception as e: 

768 print(f"❌ 序列化frozen_fee失败: {e}") 

769 continue 

770 

771 return { 

772 "total_cash": float(getattr(balance, "total_cash", 0)), 

773 "net_assets": float(getattr(balance, "net_assets", 0)), 

774 "currency": str(getattr(balance, "currency", "")), 

775 "max_finance_amount": float(getattr(balance, "max_finance_amount", 0)), 

776 "remaining_finance_amount": float( 

777 getattr(balance, "remaining_finance_amount", 0) 

778 ), 

779 "risk_level": int(getattr(balance, "risk_level", 0)), 

780 "margin_call": float(getattr(balance, "margin_call", 0)), 

781 "init_margin": float(getattr(balance, "init_margin", 0)), 

782 "maintenance_margin": float(getattr(balance, "maintenance_margin", 0)), 

783 "buy_power": float(getattr(balance, "buy_power", 0)), 

784 "cash_infos": cash_infos, 

785 "frozen_transaction_fees": frozen_fees, 

786 } 

787 except Exception as e: 

788 print(f"❌ 序列化账户余额失败: {e}") 

789 return {} 

790 

791 def _serialize_max_purchase_quantity_response(self, response) -> Dict[str, Any]: 

792 """序列化最大购买数量响应""" 

793 if not response: 

794 return {} 

795 

796 try: 

797 return { 

798 "cash_max_qty": float(getattr(response, "cash_max_qty", 0)), 

799 "margin_max_qty": float(getattr(response, "margin_max_qty", 0)), 

800 } 

801 except Exception as e: 

802 print(f"❌ 序列化最大购买数量响应失败: {e}") 

803 return {} 

804 

805 # 枚举转换辅助方法 

806 def _get_order_type_enum(self, order_type: str): 

807 """转换订单类型枚举""" 

808 from longport.openapi import OrderType 

809 

810 # 处理常见的命名差异 

811 order_type_mapping = { 

812 "LO": "LO", # Limit Order 

813 "MO": "MO", # Market Order 

814 "ELO": "ELO", # Enhanced Limit Order 

815 "AO": "AO", # At-auction Order 

816 "ALO": "ALO", # At-auction Limit Order 

817 "ODD": "ODD", # Odd Lots 

818 "LIT": "LIT", # Limit If Touched 

819 "MIT": "MIT", # Market If Touched 

820 "TSLPAMT": "TSLPAMT", # Trailing Limit If Touched (Trailing Amount) 

821 "TSLPPCT": "TSLPPCT", # Trailing Limit If Touched (Trailing Percent) 

822 "TSMAMT": "TSMAMT", # Trailing Market If Touched (Trailing Amount) 

823 "TSMPCT": "TSMPCT", # Trailing Market If Touched (Trailing Percent) 

824 "SLO": "SLO", # Special Limit Order 

825 } 

826 

827 mapped_type = order_type_mapping.get(order_type, order_type) 

828 return getattr(OrderType, mapped_type) 

829 

830 def _get_order_side_enum(self, side: str): 

831 """转换订单方向枚举""" 

832 from longport.openapi import OrderSide 

833 

834 side_mapping = {"Buy": "Buy", "Sell": "Sell", "Unknown": "Unknown"} 

835 

836 mapped_side = side_mapping.get(side, side) 

837 return getattr(OrderSide, mapped_side) 

838 

839 def _get_time_in_force_enum(self, time_in_force: str): 

840 """转换订单有效期枚举""" 

841 from longport.openapi import TimeInForceType 

842 

843 time_in_force_mapping = { 

844 "Day": "Day", 

845 "GTC": "GoodTilCanceled", 

846 "GTD": "GoodTilDate", 

847 "GoodTilCanceled": "GoodTilCanceled", 

848 "GoodTilDate": "GoodTilDate", 

849 "Unknown": "Unknown", 

850 } 

851 

852 mapped_tif = time_in_force_mapping.get(time_in_force, time_in_force) 

853 return getattr(TimeInForceType, mapped_tif) 

854 

855 def _get_order_status_enum(self, status: str): 

856 """转换订单状态枚举""" 

857 from longport.openapi import OrderStatus 

858 

859 status_mapping = { 

860 "Unknown": "Unknown", 

861 "NotReported": "NotReported", 

862 "ReplacedNotReported": "ReplacedNotReported", 

863 "ProtectedNotReported": "ProtectedNotReported", 

864 "VarietiesNotReported": "VarietiesNotReported", 

865 "Filled": "Filled", 

866 "WaitToNew": "WaitToNew", 

867 "New": "New", 

868 "WaitToReplace": "WaitToReplace", 

869 "PendingReplace": "PendingReplace", 

870 "Replaced": "Replaced", 

871 "PartialFilled": "PartialFilled", 

872 "WaitToCancel": "WaitToCancel", 

873 "PendingCancel": "PendingCancel", 

874 "Rejected": "Rejected", 

875 "Canceled": "Canceled", 

876 "Expired": "Expired", 

877 "PartialWithdrawal": "PartialWithdrawal", 

878 } 

879 

880 mapped_status = status_mapping.get(status, status) 

881 return getattr(OrderStatus, mapped_status) 

882 

883 def _convert_expire_date_timezone( 

884 self, symbol: str, expire_date: Optional[date] 

885 ) -> Optional[date]: 

886 """根据市场类型转换过期日期的时区""" 

887 if expire_date is None: 

888 return None 

889 

890 market = self._extract_market_from_symbol(symbol) 

891 

892 if market == "US": 

893 # 美股:前端输入的是美东时间日期,LongPort API期望的也是美东时间 

894 # 不需要转换,直接返回 

895 return expire_date 

896 elif market == "HK": 

897 # 港股:前端输入的是北京时间日期,LongPort API期望的是香港时间 

898 # 香港时间和北京时间相同,不需要转换 

899 return expire_date 

900 else: 

901 # 其他市场:直接返回 

902 return expire_date 

903 

904 def _convert_datetime_timezone( 

905 self, symbol: str, dt: Optional[datetime] 

906 ) -> Optional[datetime]: 

907 """根据市场类型转换日期时间的时区""" 

908 if dt is None: 

909 return None 

910 

911 market = self._extract_market_from_symbol(symbol) 

912 

913 if market == "US": 

914 # 美股:前端输入的是美东时间,LongPort API期望的也是美东时间 

915 # 不需要转换,直接返回 

916 return dt 

917 elif market == "HK": 

918 # 港股:前端输入的是北京时间,LongPort API期望的是香港时间 

919 # 香港时间和北京时间相同,不需要转换 

920 return dt 

921 else: 

922 # 其他市场:直接返回 

923 return dt 

924 

925 def _set_default_time_range( 

926 self, symbol: str, start_at: Optional[datetime], end_at: Optional[datetime] 

927 ) -> tuple[Optional[datetime], Optional[datetime]]: 

928 """设置默认时间范围""" 

929 if start_at is None and end_at is None: 

930 # 如果没有指定时间范围,默认查询最近7天 

931 from datetime import timedelta 

932 

933 end_at = datetime.now() 

934 start_at = end_at - timedelta(days=7) 

935 

936 if start_at is not None: 

937 # 开始时间默认设置为00:00:00 

938 start_at = start_at.replace(hour=0, minute=0, second=0, microsecond=0) 

939 

940 if end_at is not None: 

941 # 结束时间默认设置为23:59:00 

942 end_at = end_at.replace(hour=23, minute=59, second=0, microsecond=0) 

943 

944 return start_at, end_at 

945 

946 def _get_outside_rth_enum(self, outside_rth: str): 

947 """转换盘前盘后交易枚举""" 

948 from longport.openapi import OutsideRTH 

949 

950 outside_rth_mapping = { 

951 "RTH_ONLY": "RTHOnly", 

952 "ANY_TIME": "AnyTime", 

953 "OVERNIGHT": "Overnight", 

954 "UnknownOutsideRth": "Unknown", 

955 } 

956 

957 mapped_outside_rth = outside_rth_mapping.get(outside_rth, outside_rth) 

958 return getattr(OutsideRTH, mapped_outside_rth) 

959 

960 def _get_market_enum(self, market: str): 

961 """转换市场枚举""" 

962 from longport.openapi import Market 

963 

964 market_mapping = { 

965 "US": "US", 

966 "HK": "HK", 

967 "CN": "CN", 

968 "SG": "SG", 

969 "Crypto": "Crypto", 

970 "Unknown": "Unknown", 

971 } 

972 

973 mapped_market = market_mapping.get(market, market) 

974 return getattr(Market, mapped_market) 

975 

976 def refresh_access_token(self) -> Optional[Dict[str, Any]]: 

977 """刷新访问令牌""" 

978 try: 

979 client = self._get_client() 

980 if not client: 

981 return {"success": False, "message": "无法获取长桥客户端"} 

982 

983 # 获取Config对象 

984 config = client.get("config") 

985 if not config: 

986 return {"success": False, "message": "无法获取长桥配置对象"} 

987 

988 # 设置过期时间为6个月后 

989 from datetime import datetime, timedelta 

990 

991 expired_at = datetime.now() + timedelta(days=180) # 6个月 = 180天 

992 

993 # 调用LongPort SDK的refresh_access_token方法 

994 new_token = config.refresh_access_token(expired_at) 

995 if new_token: 

996 return { 

997 "success": True, 

998 "message": "Token刷新成功", 

999 "new_token": new_token, 

1000 "expired_at": expired_at.isoformat(), 

1001 } 

1002 else: 

1003 return {"success": False, "message": "Token刷新失败:未返回新token"} 

1004 except Exception as e: 

1005 return {"success": False, "message": f"Token刷新失败: {str(e)}"}