|
27 | 27 |
|
28 | 28 | strategy_bp = Blueprint('strategy', __name__) |
29 | 29 |
|
| 30 | + |
| 31 | +def _normalize_trade_row_for_api(trade: dict) -> dict: |
| 32 | + """Ensure numeric fields are JSON-friendly floats (PostgreSQL DECIMAL → float).""" |
| 33 | + try: |
| 34 | + from decimal import Decimal |
| 35 | + except Exception: # pragma: no cover |
| 36 | + Decimal = () # type: ignore |
| 37 | + out = dict(trade) |
| 38 | + for k in ("price", "amount", "value", "commission", "profit"): |
| 39 | + v = out.get(k) |
| 40 | + if isinstance(v, Decimal): |
| 41 | + out[k] = float(v) |
| 42 | + return out |
| 43 | + |
| 44 | + |
30 | 45 | # --------------------------------------------------------------------------- |
31 | 46 | # Strategy templates (loaded once from JSON file) |
32 | 47 | # --------------------------------------------------------------------------- |
@@ -542,25 +557,30 @@ def get_trades(): |
542 | 557 | rows = cur.fetchall() or [] |
543 | 558 | cur.close() |
544 | 559 |
|
545 | | - # Convert created_at to UTC timestamp (seconds) for frontend |
546 | | - # This ensures consistent timezone handling |
| 560 | + # Convert created_at to Unix seconds (UTC instant). |
| 561 | + # qd_strategy_trades.created_at is TIMESTAMP WITHOUT TIME ZONE; with PostgreSQL session UTC |
| 562 | + # the stored wall clock is UTC. Naive datetime must not use .timestamp() alone — that would |
| 563 | + # interpret it in the Python process local TZ and shift the instant (e.g. +8h on CN laptops). |
| 564 | + from datetime import datetime as _dt, timezone as _tz |
547 | 565 | processed_rows = [] |
548 | 566 | for row in rows: |
549 | 567 | trade = dict(row) |
550 | 568 | created_at = trade.get('created_at') |
551 | 569 | if created_at: |
552 | 570 | if hasattr(created_at, 'timestamp'): |
553 | | - # datetime object - convert to UTC timestamp |
554 | | - trade['created_at'] = int(created_at.timestamp()) |
| 571 | + dt = created_at |
| 572 | + if getattr(dt, 'tzinfo', None) is None: |
| 573 | + dt = dt.replace(tzinfo=_tz.utc) |
| 574 | + trade['created_at'] = int(dt.timestamp()) |
555 | 575 | elif isinstance(created_at, str): |
556 | | - # ISO string - parse and convert |
557 | 576 | try: |
558 | | - from datetime import datetime |
559 | | - dt = datetime.fromisoformat(created_at.replace('Z', '+00:00')) |
| 577 | + dt = _dt.fromisoformat(created_at.replace('Z', '+00:00')) |
| 578 | + if getattr(dt, 'tzinfo', None) is None: |
| 579 | + dt = dt.replace(tzinfo=_tz.utc) |
560 | 580 | trade['created_at'] = int(dt.timestamp()) |
561 | 581 | except Exception: |
562 | 582 | pass |
563 | | - processed_rows.append(trade) |
| 583 | + processed_rows.append(_normalize_trade_row_for_api(trade)) |
564 | 584 |
|
565 | 585 | # Frontend expects data.trades; keep data.items for compatibility with list-style components. |
566 | 586 | return jsonify({'code': 1, 'msg': 'success', 'data': {'trades': processed_rows, 'items': processed_rows}}) |
@@ -1491,6 +1511,9 @@ def get_strategy_logs(): |
1491 | 1511 | if not isinstance(r, dict): |
1492 | 1512 | continue |
1493 | 1513 | rr = dict(r) |
| 1514 | + msg = str(rr.get('message') or '') |
| 1515 | + if msg.startswith('tick price=') or msg.startswith('tick price '): |
| 1516 | + continue |
1494 | 1517 | ts = rr.get('timestamp') |
1495 | 1518 | if ts is not None and hasattr(ts, 'isoformat'): |
1496 | 1519 | rr['timestamp'] = ts.isoformat() |
|
0 commit comments