|
14 | 14 | from time import time |
15 | 15 | from typing import Any, Callable |
16 | 16 |
|
| 17 | +from application.account_payload_utils import flatten_values, float_or_none |
17 | 18 | from application.state_persistence import GcsStateStore |
18 | 19 |
|
19 | 20 |
|
@@ -447,6 +448,68 @@ def get_positions(self, account: str) -> dict[str, Any]: |
447 | 448 | _, account_data = self.require_connected() |
448 | 449 | return dict(account_data.get_positions(account)) |
449 | 450 |
|
| 451 | + def get_orders(self, account: str, *, per_page: int = 0) -> list[dict[str, Any]]: |
| 452 | + _, account_data = self.require_connected() |
| 453 | + payload = account_data.get_orders(account, per_page=per_page) |
| 454 | + if isinstance(payload, list): |
| 455 | + return [dict(row) for row in payload if isinstance(row, dict)] |
| 456 | + if isinstance(payload, dict): |
| 457 | + for key in ("items", "orders", "data", "result"): |
| 458 | + value = payload.get(key) |
| 459 | + if isinstance(value, list): |
| 460 | + return [dict(row) for row in value if isinstance(row, dict)] |
| 461 | + return [] |
| 462 | + |
| 463 | + def get_order_status(self, account: str, order_id: str) -> dict[str, Any] | None: |
| 464 | + normalized_order_id = str(order_id or "").strip() |
| 465 | + if not normalized_order_id: |
| 466 | + return None |
| 467 | + for row in self.get_orders(account): |
| 468 | + if not _payload_contains_order_id(row, normalized_order_id): |
| 469 | + continue |
| 470 | + status = _first_text_from_payload( |
| 471 | + row, |
| 472 | + "status", |
| 473 | + "order_status", |
| 474 | + "state", |
| 475 | + "status_description", |
| 476 | + "description", |
| 477 | + ) |
| 478 | + executed_qty = _first_numeric_from_payload( |
| 479 | + row, |
| 480 | + "executed_qty", |
| 481 | + "executed_quantity", |
| 482 | + "filled_quantity", |
| 483 | + "filled_qty", |
| 484 | + "filled", |
| 485 | + "filled_shares", |
| 486 | + "executed_shares", |
| 487 | + "quantity_filled", |
| 488 | + "quantity", |
| 489 | + "shares", |
| 490 | + "qty", |
| 491 | + ) |
| 492 | + executed_price = _first_numeric_from_payload( |
| 493 | + row, |
| 494 | + "executed_price", |
| 495 | + "average_fill_price", |
| 496 | + "avg_fill_price", |
| 497 | + "avg_price", |
| 498 | + "average_price", |
| 499 | + "fill_price", |
| 500 | + "filled_price", |
| 501 | + "price", |
| 502 | + "limit_price", |
| 503 | + ) |
| 504 | + return { |
| 505 | + "status": status or "", |
| 506 | + "executed_qty": max(0.0, float(executed_qty or 0.0)), |
| 507 | + "executed_price": max(0.0, float(executed_price or 0.0)), |
| 508 | + "broker_order_id": normalized_order_id, |
| 509 | + "raw_payload": dict(row), |
| 510 | + } |
| 511 | + return None |
| 512 | + |
450 | 513 | def get_quote(self, account: str, symbol: str) -> dict[str, Any]: |
451 | 514 | session, _ = self.require_connected() |
452 | 515 | quote_factory = self._quote_factory |
@@ -533,3 +596,41 @@ def place_stock_order( |
533 | 596 | notional=notional, |
534 | 597 | ) |
535 | 598 | ) |
| 599 | + |
| 600 | + |
| 601 | +def _sanitize_payload_key(value: Any) -> str: |
| 602 | + return "".join(ch for ch in str(value or "").lower() if ch.isalnum()) |
| 603 | + |
| 604 | + |
| 605 | +def _first_payload_value(payload: Any, *candidate_keys: str) -> Any: |
| 606 | + flattened = flatten_values(payload) |
| 607 | + candidates = {_sanitize_payload_key(key) for key in candidate_keys} |
| 608 | + for key, value in flattened.items(): |
| 609 | + if _sanitize_payload_key(key.rsplit(".", 1)[-1]) in candidates: |
| 610 | + return value |
| 611 | + return None |
| 612 | + |
| 613 | + |
| 614 | +def _first_text_from_payload(payload: Any, *candidate_keys: str) -> str | None: |
| 615 | + value = _first_payload_value(payload, *candidate_keys) |
| 616 | + text = str(value or "").strip() |
| 617 | + return text or None |
| 618 | + |
| 619 | + |
| 620 | +def _first_numeric_from_payload(payload: Any, *candidate_keys: str) -> float | None: |
| 621 | + return float_or_none(_first_payload_value(payload, *candidate_keys)) |
| 622 | + |
| 623 | + |
| 624 | +def _payload_contains_order_id(payload: Any, order_id: str) -> bool: |
| 625 | + normalized_order_id = str(order_id or "").strip() |
| 626 | + if not normalized_order_id: |
| 627 | + return False |
| 628 | + for key, value in flatten_values(payload).items(): |
| 629 | + key_normalized = _sanitize_payload_key(key) |
| 630 | + if "order" not in key_normalized: |
| 631 | + continue |
| 632 | + if not any(token in key_normalized for token in ("id", "number", "orderno")): |
| 633 | + continue |
| 634 | + if str(value or "").strip() == normalized_order_id: |
| 635 | + return True |
| 636 | + return False |
0 commit comments