Changes: - Added a new API endpoint for managing permanent subscriptions, allowing users to enable or disable subscriptions dynamically. - Implemented a function to fill candle data from Kiwoom, ensuring that only relevant data is inserted into the database. - Introduced a mechanism to handle master subscription states, improving the management of subscription statuses. - Updated the database schema to include new fields for managing subscription states and order book filtering. Impact: - These enhancements improve the flexibility and reliability of the trading system, allowing for better management of subscriptions and order book data, while reducing the risk of data inconsistencies. 히스토리 align 제거 븅신같은 초기설계 아예 제거 진입모드에 구멍메움 호가진입을 켜도 호가가 안들어올때 호가 안보고 그냥 사버림
124 lines
3.8 KiB
Python
124 lines
3.8 KiB
Python
"""
|
|
ws_candles 시리즈 — source=증권사, channel=ws|rest|rollup.
|
|
|
|
저장은 섞지 않음: 키움 REST 갭보정은 항상 source=kiwoom, channel=rest.
|
|
읽기(실매=Optuna): 틱 메인 WS 우선, 구멍만 kiwoom+rest.
|
|
"""
|
|
from __future__ import annotations
|
|
|
|
import os
|
|
from typing import Any, Dict, List, Optional, Sequence, Tuple
|
|
|
|
from kis_trader.utils.env import get_env_from_db
|
|
|
|
CHANNEL_WS = "ws"
|
|
CHANNEL_REST = "rest"
|
|
CHANNEL_ROLLUP = "rollup"
|
|
|
|
# 읽기 우선순위 쌍 (source, channel)
|
|
ReadPair = Tuple[str, str]
|
|
|
|
|
|
def normalize_source_channel(
|
|
source: str = "",
|
|
channel: str = "",
|
|
) -> Tuple[str, str]:
|
|
"""레거시 source=rest|kw_rest|rollup_1m 을 (증권사, channel)로 정규화."""
|
|
s = str(source or "").strip().lower()
|
|
ch = str(channel or "").strip().lower()
|
|
if s in ("rest", "kw_rest"):
|
|
return "kiwoom", CHANNEL_REST
|
|
if s in ("rollup_1m", "rollup"):
|
|
src = "kiwoom"
|
|
if ch in ("kis",):
|
|
src = "kis"
|
|
return src, CHANNEL_ROLLUP
|
|
if s in ("kis", "kis_us", "ws", ""):
|
|
return "kis", (ch or CHANNEL_WS)
|
|
if s == "kiwoom":
|
|
return "kiwoom", (ch or CHANNEL_WS)
|
|
if s == "ls":
|
|
return "ls", (ch or CHANNEL_WS)
|
|
return (s or "kis")[:16], (ch or CHANNEL_WS)[:10]
|
|
|
|
|
|
def _provider_and_override() -> Tuple[str, str]:
|
|
override = ""
|
|
raw = os.environ.get("CANDLE_SOURCE")
|
|
if raw is None:
|
|
try:
|
|
raw = get_env_from_db("CANDLE_SOURCE", "")
|
|
except Exception:
|
|
raw = ""
|
|
override = str(raw or "").strip().lower()
|
|
if override not in ("kis", "kiwoom"):
|
|
override = ""
|
|
try:
|
|
provider = (
|
|
get_env_from_db("LIVE_TICK_PROVIDER", "kiwoom") or "kiwoom"
|
|
).strip().lower()
|
|
except Exception:
|
|
provider = "kiwoom"
|
|
if provider not in ("kis", "kiwoom"):
|
|
provider = "kiwoom"
|
|
return provider, override
|
|
|
|
|
|
def live_read_pairs() -> Tuple[ReadPair, ...]:
|
|
"""실매·Optuna 기본 읽기 순서.
|
|
|
|
틱 메인 WS 한 칸, 없으면 키움 REST 구멍.
|
|
CANDLE_SOURCE=kis|kiwoom 이면 그 메인을 씀. 빈값이면 LIVE_TICK_PROVIDER.
|
|
"""
|
|
provider, override = _provider_and_override()
|
|
main = override or provider
|
|
# 메인 WS → 구멍 키움 REST → 1M 롤업(상위 TF 웜업). 증권사 행은 섞지 않음.
|
|
if main == "kis":
|
|
return (
|
|
("kis", CHANNEL_WS),
|
|
("kiwoom", CHANNEL_REST),
|
|
("kiwoom", CHANNEL_ROLLUP),
|
|
)
|
|
return (
|
|
("kiwoom", CHANNEL_WS),
|
|
("kiwoom", CHANNEL_REST),
|
|
("kiwoom", CHANNEL_ROLLUP),
|
|
)
|
|
|
|
|
|
def live_read_label() -> str:
|
|
provider, override = _provider_and_override()
|
|
main = override or provider
|
|
if override:
|
|
return f"{override.upper()}+kiwoom_rest"
|
|
return f"LIVE({main})+kiwoom_rest"
|
|
|
|
|
|
def dedupe_by_read_pairs(
|
|
rows: Sequence[Dict[str, Any]],
|
|
pairs: Optional[Sequence[ReadPair]] = None,
|
|
) -> List[Dict[str, Any]]:
|
|
"""candle_time 1행 — pairs 앞쪽이 이김. source/channel 은 결과에 남기지 않음(레거시 호환)."""
|
|
order = tuple(pairs or live_read_pairs())
|
|
rank = {p: i for i, p in enumerate(order)}
|
|
seen: Dict[str, Tuple[int, Dict[str, Any]]] = {}
|
|
for row in rows:
|
|
ct = str(row.get("candle_time") or "")[:12]
|
|
if not ct:
|
|
continue
|
|
src, ch = normalize_source_channel(
|
|
str(row.get("source") or ""),
|
|
str(row.get("channel") or ""),
|
|
)
|
|
pri = rank.get((src, ch), 999)
|
|
prev = seen.get(ct)
|
|
if prev is None or pri < prev[0]:
|
|
seen[ct] = (pri, dict(row))
|
|
out: List[Dict[str, Any]] = []
|
|
for ct in sorted(seen.keys()):
|
|
d = seen[ct][1]
|
|
d.pop("source", None)
|
|
d.pop("channel", None)
|
|
out.append(d)
|
|
return out
|