# 03. Tích hợp Official Trade API & Giải thuật Định giá (Trade API & Pricing Engine)

> **Mục tiêu**: Kết nối trực tiếp tới cổng API chính thức của Path of Exile 2 (`pathofexile.com/api/trade2`), quản lý hạn mức gọi (Rate Limits) và tính toán khoảng giá trung vị thời gian thực.  
> **Giao thức**: HTTPS REST JSON với User-Agent chuẩn chỉ định danh.

---

## 1. Kiến trúc Cổng API Giao dịch Chính thức của POE2

Hệ thống giao dịch chính thức của POE2 vận hành theo mô hình 2 bước:
1. **Bước 1 (Search Request)**: Gửi tiêu chí lọc (Tên, Loại đồ, Các dòng Mod) tới endpoint tìm kiếm. Máy chủ trả về một danh sách các mã định danh kết quả (Result Item IDs).
2. **Bước 2 (Fetch Request)**: Gửi tối đa 10 Item IDs để lấy thông tin chi tiết (Tên người bán, Vị trí hòm đồ, Giá niêm yết bằng Chaos hoặc Divine Orbs).

```mermaid
sequenceDiagram
    participant Tool as POE2 Assistant Client
    participant API as Official PoE2 Trade API (pathofexile.com)

    Note over Tool: Người chơi bấm Ctrl+D lên trang bị
    Tool->>API: POST /api/trade2/search/{League} (Payload tiêu chí lọc)
    API-->>Tool: Trả về: { id: "QueryID123", result: ["item_id_1", "item_id_2", ...] }
    
    Tool->>API: GET /api/trade2/fetch/item_id_1,item_id_2?query=QueryID123
    API-->>Tool: Trả về chi tiết: Giá bán (ví dụ: 45 Chaos, 1.2 Divine), Nick ingame
    
    Tool->>Tool: Lọc giá ảo (Outliers) & Tính toán giá Trung vị (Median Price)
    Tool->>Tool: Hiển thị Thẻ Định Giá trên màn hình người chơi
```

---

## 2. Quản lý Hạn mức Gọi API (Strict Rate-Limiting Engine)

Máy chủ GGG trả về các Header điều tiết lưu lượng trong mọi phản hồi HTTP:
- `X-Rate-Limit-Ip`: Quy định số yêu cầu tối đa và cửa sổ thời gian (ví dụ: `12:4:10,30:60:60` nghĩa là tối đa 12 yêu cầu trong 4 giây, tối đa 30 yêu cầu trong 60 giây).
- `X-Rate-Limit-Ip-State`: Trạng thái sử dụng hiện tại (ví dụ: `3:4:0,8:60:0`).

### Giải thuật Xô Rò rỉ (Leaky Bucket Queue) Triển khai trong Python:

```python
"""
Rate-Limited HTTP Client for POE2 Official Trade API
Prevents HTTP 429 Too Many Requests errors.
"""

import time
import asyncio
import requests
from typing import Dict, Any, Optional

class POE2TradeClient:
    BASE_URL = "https://www.pathofexile.com/api/trade2"
    USER_AGENT = "POE2Assistant/1.0.0 (Contact: user@domain.com)"

    def __init__(self, league_name: str = "Standard"):
        self.league = league_name
        self.session = requests.Session()
        self.session.headers.update({
            "User-Agent": self.USER_AGENT,
            "Content-Type": "application/json"
        })
        self.last_request_time = 0.0
        self.min_interval_seconds = 0.35 # Tối đa ~3 yêu cầu / giây

    def _wait_for_rate_limit(self):
        elapsed = time.time() - self.last_request_time
        if elapsed < self.min_interval_seconds:
            time.sleep(self.min_interval_seconds - elapsed)
        self.last_request_time = time.time()

    def search_item(self, query_payload: Dict[str, Any]) -> Optional[Dict[str, Any]]:
        self._wait_for_rate_limit()
        url = f"{self.BASE_URL}/search/{self.league}"
        try:
            resp = self.session.post(url, json=query_payload, timeout=5)
            if resp.status_code == 200:
                return resp.json()
            elif resp.status_code == 429:
                retry_after = int(resp.headers.get("Retry-After", 10))
                print(f"[Trade API Warning] Bị giới hạn tốc độ. Tạm dừng {retry_after}s...")
                time.sleep(retry_after)
                return None
        except Exception as e:
            print(f"[Trade API Error] Kết nối thất bại: {e}")
        return None

    def fetch_items(self, item_ids: list[str], query_id: str) -> Optional[list[Dict[str, Any]]]:
        if not item_ids: return []
        self._wait_for_rate_limit()
        
        # Chỉ fetch tối đa 10 vật phẩm mỗi lượt theo quy định của GGG
        ids_param = ",".join(item_ids[:10])
        url = f"{self.BASE_URL}/fetch/{ids_param}?query={query_id}"
        
        try:
            resp = self.session.get(url, timeout=5)
            if resp.status_code == 200:
                return resp.json().get("result", [])
        except Exception as e:
            print(f"[Trade API Error] Fetch thất bại: {e}")
        return []
```

---

## 3. Giải thuật Tính toán Giá Trung vị (Median Pricing Algorithm)

Để người chơi không bị đánh lừa bởi các tài khoản treo giá thấp ảo (Price Fixing) hoặc các mức giá trên trời:

```python
import numpy as np

def calculate_market_price(listings: list[Dict[str, Any]]) -> Dict[str, Any]:
    prices_chaos = []
    
    # Giả định tỷ giá quy đổi 1 Divine = 120 Chaos trong meta hiện hành
    DIVINE_TO_CHAOS = 120.0 

    for item in listings:
        listing = item.get("listing", {})
        price_info = listing.get("price", {})
        currency = price_info.get("currency", "")
        amount = float(price_info.get("amount", 0.0))

        if amount <= 0: continue

        if currency == "chaos":
            prices_chaos.append(amount)
        elif currency == "divine":
            prices_chaos.append(amount * DIVINE_TO_CHAOS)

    if not prices_chaos:
        return {"status": "NO_DATA", "median_chaos": 0, "confidence": "NONE"}

    # 1. Sắp xếp danh sách giá tăng dần
    prices_chaos.sort()

    # 2. Loại bỏ 15% giá thấp nhất (nghi ngờ treo giá ảo) và 15% giá cao nhất
    q25 = np.percentile(prices_chaos, 15)
    q75 = np.percentile(prices_chaos, 85)
    filtered = [p for p in prices_chaos if q25 <= p <= q75]

    if not filtered: filtered = prices_chaos

    # 3. Lấy giá trị trung vị (Median)
    median_val = float(np.median(filtered))
    
    return {
        "status": "SUCCESS",
        "sample_count": len(prices_chaos),
        "median_chaos": round(median_val, 1),
        "median_divine": round(median_val / DIVINE_TO_CHAOS, 2),
        "lowest_online": prices_chaos[0],
        "highest_online": prices_chaos[-1]
    }
```
