2026-08-30 00:34:27 +08:00
|
|
|
from datetime import datetime
|
2026-08-28 18:52:27 +08:00
|
|
|
from dataclasses import dataclass, field
|
|
|
|
|
from typing import Any
|
2026-09-07 21:22:51 +08:00
|
|
|
from functools import lru_cache
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
_SIDES = {"23": "BUY", "24": "SELL", "48": "BUY", "49": "SELL"}
|
2026-08-28 18:52:27 +08:00
|
|
|
|
|
|
|
|
|
|
|
|
|
def _number(value: Any, kind: type = float) -> Any:
|
|
|
|
|
try:
|
|
|
|
|
return kind(value or 0)
|
|
|
|
|
except (TypeError, ValueError):
|
|
|
|
|
return kind()
|
|
|
|
|
|
|
|
|
|
|
2026-09-06 11:34:23 +08:00
|
|
|
@dataclass(slots=True)
|
2026-08-30 00:34:27 +08:00
|
|
|
class OrderItem:
|
2026-09-07 14:04:26 +08:00
|
|
|
"""Fields match format_orders() exactly."""
|
|
|
|
|
|
|
|
|
|
stock_code: str = ""
|
|
|
|
|
order_sys_id: str = ""
|
|
|
|
|
ref: int = 0
|
|
|
|
|
order_ref: str = ""
|
|
|
|
|
direction: int = 0
|
|
|
|
|
offset_flag: int = 0
|
|
|
|
|
limit_price: float = 0.0
|
|
|
|
|
volume_total_original: int = 0
|
|
|
|
|
volume_traded: int = 0
|
|
|
|
|
volume_total: int = 0
|
|
|
|
|
traded_price: float = 0.0
|
2026-08-30 00:34:27 +08:00
|
|
|
trade_amount: float = 0.0
|
2026-09-07 14:04:26 +08:00
|
|
|
insert_date: str = ""
|
|
|
|
|
insert_time: str = ""
|
|
|
|
|
remark: str = ""
|
|
|
|
|
order_status: int = 0
|
2026-08-30 00:34:27 +08:00
|
|
|
|
2026-09-07 14:04:26 +08:00
|
|
|
@property
|
|
|
|
|
def side(self) -> str:
|
|
|
|
|
return _side(self.offset_flag)
|
|
|
|
|
|
|
|
|
|
@property
|
2026-09-07 18:18:00 +08:00
|
|
|
def get_local_order_id(self) -> str:
|
2026-09-07 14:04:26 +08:00
|
|
|
return self.remark.split("|", 1)[0]
|
|
|
|
|
|
2026-09-07 21:22:51 +08:00
|
|
|
# Keep the existing property name available to SDK callers.
|
|
|
|
|
local_order_id = get_local_order_id
|
|
|
|
|
|
2026-09-07 14:04:26 +08:00
|
|
|
@property
|
|
|
|
|
def created_at(self) -> datetime | None:
|
|
|
|
|
return _parse_datetime(self.insert_date, self.insert_time)
|
2026-08-30 00:34:27 +08:00
|
|
|
|
|
|
|
|
|
2026-09-06 14:49:46 +08:00
|
|
|
@dataclass(slots=True)
|
|
|
|
|
class DealItem:
|
2026-09-07 14:04:26 +08:00
|
|
|
"""Fields match format_deals() exactly."""
|
|
|
|
|
|
|
|
|
|
stock_code: str = ""
|
|
|
|
|
order_sys_id: str = ""
|
|
|
|
|
ref: int = 0
|
|
|
|
|
order_ref: str = ""
|
|
|
|
|
direction: int = 0
|
|
|
|
|
offset_flag: int = 0
|
2026-09-07 00:27:33 +08:00
|
|
|
price: float = 0.0
|
2026-09-07 14:04:26 +08:00
|
|
|
volume: int = 0
|
2026-09-07 00:27:33 +08:00
|
|
|
trade_amount: float = 0.0
|
2026-09-07 14:04:26 +08:00
|
|
|
trade_date: str = ""
|
|
|
|
|
trade_time: str = ""
|
|
|
|
|
remark: str = ""
|
|
|
|
|
close_profit: float = 0.0
|
|
|
|
|
|
|
|
|
|
@property
|
|
|
|
|
def side(self) -> str:
|
|
|
|
|
return _side(self.offset_flag)
|
2026-09-06 14:49:46 +08:00
|
|
|
|
2026-09-07 14:04:26 +08:00
|
|
|
@property
|
2026-09-07 18:18:00 +08:00
|
|
|
def get_local_order_id(self) -> str:
|
2026-09-07 14:04:26 +08:00
|
|
|
return self.remark.split("|", 1)[0]
|
2026-09-06 14:49:46 +08:00
|
|
|
|
2026-09-07 21:22:51 +08:00
|
|
|
local_order_id = get_local_order_id
|
|
|
|
|
|
2026-09-06 14:49:46 +08:00
|
|
|
|
2026-09-06 11:34:23 +08:00
|
|
|
@dataclass(slots=True)
|
2026-08-30 00:34:27 +08:00
|
|
|
class PositionItem:
|
2026-09-07 14:04:26 +08:00
|
|
|
"""Fields match format_holding() exactly."""
|
|
|
|
|
|
2026-08-28 18:52:27 +08:00
|
|
|
stock_code: str = ""
|
|
|
|
|
stock_name: str = ""
|
|
|
|
|
direction: Any = None
|
|
|
|
|
volume: int = 0
|
|
|
|
|
open_price: float = 0.0
|
2026-09-07 14:04:26 +08:00
|
|
|
open_cost: float = 0.0
|
2026-08-28 18:52:27 +08:00
|
|
|
float_profit: float = 0.0
|
|
|
|
|
market_value: float = 0.0
|
|
|
|
|
stock_holder: str = ""
|
|
|
|
|
frozen_volume: int = 0
|
|
|
|
|
can_use_volume: int = 0
|
|
|
|
|
on_road_volume: int = 0
|
|
|
|
|
yesterday_volume: int = 0
|
|
|
|
|
last_price: float = 0.0
|
|
|
|
|
profit_rate: float = 0.0
|
|
|
|
|
future_trade_type: Any = None
|
|
|
|
|
expire_date: str = ""
|
|
|
|
|
|
|
|
|
|
|
2026-09-06 11:34:23 +08:00
|
|
|
@dataclass(slots=True)
|
2026-08-28 18:52:27 +08:00
|
|
|
class Assets:
|
2026-09-07 14:04:26 +08:00
|
|
|
"""Fields match format_assets() exactly."""
|
|
|
|
|
|
2026-08-28 18:52:27 +08:00
|
|
|
total: float = 0.0
|
|
|
|
|
available: float = 0.0
|
|
|
|
|
|
2026-08-30 00:34:27 +08:00
|
|
|
|
2026-09-06 11:34:23 +08:00
|
|
|
@dataclass(slots=True)
|
2026-09-03 11:33:43 +08:00
|
|
|
class Portfolio:
|
|
|
|
|
assets: Assets
|
|
|
|
|
positions: dict[str, PositionItem]
|
|
|
|
|
orders: list[OrderItem]
|
|
|
|
|
|
|
|
|
|
|
2026-09-07 14:04:26 +08:00
|
|
|
def _side(offset_flag: int) -> str:
|
2026-09-07 21:22:51 +08:00
|
|
|
return _SIDES.get(str(offset_flag), "")
|
2026-09-06 14:49:46 +08:00
|
|
|
|
|
|
|
|
|
2026-09-07 21:22:51 +08:00
|
|
|
@lru_cache(maxsize=4096)
|
2026-09-06 14:49:46 +08:00
|
|
|
def _parse_datetime(date: str, clock: str) -> datetime | None:
|
|
|
|
|
clock = clock.replace(":", "").zfill(6)
|
2026-08-30 00:34:27 +08:00
|
|
|
try:
|
2026-09-07 14:04:26 +08:00
|
|
|
return datetime.strptime(date.replace("-", "") + clock, "%Y%m%d%H%M%S")
|
2026-08-30 00:34:27 +08:00
|
|
|
except ValueError:
|
|
|
|
|
return None
|
|
|
|
|
|
2026-08-28 18:52:27 +08:00
|
|
|
|
2026-09-06 11:34:23 +08:00
|
|
|
@dataclass(slots=True)
|
2026-08-28 18:52:27 +08:00
|
|
|
class Tick:
|
|
|
|
|
last_price: float = 0.0
|
|
|
|
|
last_close: float = 0.0
|
|
|
|
|
raw: dict[str, Any] = field(default_factory=dict)
|
|
|
|
|
|
2026-09-03 11:33:43 +08:00
|
|
|
@classmethod
|
2026-09-07 21:22:51 +08:00
|
|
|
def from_dict(cls, data: Any) -> Tick:
|
2026-09-03 11:33:43 +08:00
|
|
|
if not isinstance(data, dict):
|
|
|
|
|
return cls()
|
|
|
|
|
return cls(
|
2026-09-06 14:49:46 +08:00
|
|
|
last_price=_number(
|
|
|
|
|
data.get("lastPrice", data.get("last_price", data.get("LastPrice")))
|
|
|
|
|
),
|
|
|
|
|
last_close=_number(
|
|
|
|
|
data.get("lastClose", data.get("last_close", data.get("LastClose")))
|
|
|
|
|
),
|
2026-09-03 11:33:43 +08:00
|
|
|
raw=data,
|
|
|
|
|
)
|
|
|
|
|
|
2026-08-28 18:52:27 +08:00
|
|
|
|
2026-09-06 11:34:23 +08:00
|
|
|
@dataclass(slots=True)
|
2026-08-28 18:52:27 +08:00
|
|
|
class HistoryDataRequest:
|
|
|
|
|
length: int = 10
|
|
|
|
|
period: str = ""
|
|
|
|
|
field: str = ""
|
|
|
|
|
dividend_type: int = 0
|
|
|
|
|
skip_paused: bool = True
|
|
|
|
|
|
|
|
|
|
|
2026-09-06 11:34:23 +08:00
|
|
|
@dataclass(slots=True)
|
2026-08-28 18:52:27 +08:00
|
|
|
class MarketDataRequest:
|
|
|
|
|
fields: list[str] = field(default_factory=list)
|
|
|
|
|
stocks: list[str] = field(default_factory=list)
|
|
|
|
|
start_time: str = ""
|
|
|
|
|
end_time: str = ""
|
|
|
|
|
period: str = ""
|
|
|
|
|
dividend_type: str = ""
|
|
|
|
|
count: int = 0
|
|
|
|
|
|
|
|
|
|
|
2026-09-06 11:34:23 +08:00
|
|
|
@dataclass(slots=True)
|
2026-08-28 18:52:27 +08:00
|
|
|
class FinancialDataRequest:
|
2026-09-06 14:49:46 +08:00
|
|
|
tabname: str = ""
|
|
|
|
|
colname: str = ""
|
|
|
|
|
market: str = ""
|
|
|
|
|
code: str = ""
|
|
|
|
|
report_type: str = ""
|
|
|
|
|
barpos: int = 0
|
|
|
|
|
field_list: list[str] = field(default_factory=list)
|
|
|
|
|
stock_list: list[str] = field(default_factory=list)
|
|
|
|
|
start_date: str = ""
|
|
|
|
|
end_date: str = ""
|
2026-08-28 18:52:27 +08:00
|
|
|
|
|
|
|
|
|
2026-09-06 11:34:23 +08:00
|
|
|
@dataclass(slots=True)
|
2026-08-28 18:52:27 +08:00
|
|
|
class FactorDataRequest:
|
2026-09-06 14:49:46 +08:00
|
|
|
field_list: list[str] = field(default_factory=list)
|
|
|
|
|
stock_list: list[str] = field(default_factory=list)
|
|
|
|
|
stock_code: str = ""
|
|
|
|
|
start_date: str = ""
|
|
|
|
|
end_date: str = ""
|
2026-08-28 18:52:27 +08:00
|
|
|
|
|
|
|
|
|
2026-09-06 11:34:23 +08:00
|
|
|
@dataclass(slots=True)
|
2026-08-28 18:52:27 +08:00
|
|
|
class BSMPriceRequest:
|
2026-09-06 14:49:46 +08:00
|
|
|
option_type: str
|
|
|
|
|
object_prices: Any
|
|
|
|
|
strike_price: float
|
|
|
|
|
risk_free: float
|
|
|
|
|
sigma: float
|
|
|
|
|
days: int
|
|
|
|
|
dividend: float
|
2026-08-28 18:52:27 +08:00
|
|
|
|
|
|
|
|
|
2026-09-06 11:34:23 +08:00
|
|
|
@dataclass(slots=True)
|
2026-08-28 18:52:27 +08:00
|
|
|
class BSMIVRequest:
|
2026-09-06 14:49:46 +08:00
|
|
|
option_type: str
|
|
|
|
|
object_prices: float
|
|
|
|
|
strike_price: float
|
|
|
|
|
option_price: float
|
|
|
|
|
risk_free: float
|
|
|
|
|
days: int
|
|
|
|
|
dividend: float
|
2026-08-28 18:52:27 +08:00
|
|
|
|
|
|
|
|
|
2026-09-06 11:34:23 +08:00
|
|
|
@dataclass(slots=True)
|
2026-08-28 18:52:27 +08:00
|
|
|
class LocalDataRequest:
|
2026-09-06 14:49:46 +08:00
|
|
|
stock_code: str
|
|
|
|
|
start_time: str = ""
|
|
|
|
|
end_time: str = ""
|
|
|
|
|
period: str = ""
|
|
|
|
|
divid_type: str = ""
|
|
|
|
|
count: int = 0
|