PEFT
Safetensors
echoforest's picture
Upload naver-psy classifier (adapter, heads, calibration)
210f9af verified
Raw History Blame Contribute Delete
3.71 kB
"""KST buckets and session tags (pure Python).
Session tags (sentiment-labelling §6.2 hour bands; rollup column ``session``, classifier-plan §7.3):
``holiday`` on weekends and KRX holidays, else ``pre_open`` 06:00-09:00, ``regular`` 09:00-15:30,
``after_hours`` 15:30-20:00, ``night`` 20:00-06:00 (KST). An hourly bucket takes the tag of its start time, so
the 15:00 bucket (which holds the 15:30 close) is ``regular``. Holidays come from ``config/krx_holidays.yaml``
(a list of dates, or ``{"holidays": [...]}``); without it only weekends count.
"""
from __future__ import annotations
from datetime import date, datetime, time, timedelta, timezone
from pathlib import Path
from typing import Iterable
KST = timezone(timedelta(hours=9))
SESSIONS = ("pre_open", "regular", "after_hours", "night", "holiday")
def to_kst(dt: datetime) -> datetime:
if dt.tzinfo is None:
raise ValueError("naive datetime: written_at must carry a zone")
return dt.astimezone(KST)
def parse(text: str) -> datetime:
"""ISO time with an offset (``Z`` accepted) -> aware datetime."""
dt = datetime.fromisoformat(text.replace("Z", "+00:00"))
if dt.tzinfo is None:
raise ValueError(f"time without offset: {text!r}")
return dt
def hour_bucket(dt: datetime) -> datetime:
"""KST hour start as a naive local timestamp (``bucket_kst timestamp``)."""
k = to_kst(dt)
return k.replace(minute=0, second=0, microsecond=0, tzinfo=None)
def day_bucket(dt: datetime) -> date:
return to_kst(dt).date()
def is_trading_day(d: date, holidays: Iterable[date] = ()) -> bool:
return d.weekday() < 5 and d not in set(holidays)
def session_tag(dt: datetime, holidays: Iterable[date] = ()) -> str:
"""Session of an aware time, or of a naive KST timestamp (a bucket start)."""
k = dt.replace(tzinfo=None) if dt.tzinfo is None else to_kst(dt).replace(tzinfo=None)
if not is_trading_day(k.date(), holidays):
return "holiday"
t = k.time()
if time(6) <= t < time(9):
return "pre_open"
if time(9) <= t < time(15, 30):
return "regular"
if time(15, 30) <= t < time(20):
return "after_hours"
return "night"
def load_holidays(path: str | Path | None) -> set[date]:
if path is None or not Path(path).exists():
return set()
import yaml
value = yaml.safe_load(Path(path).read_text(encoding="utf-8")) or []
if isinstance(value, dict):
value = value.get("holidays", [])
out = set()
for v in value:
if isinstance(v, dict):
v = v.get("date")
out.add(v if isinstance(v, date) else date.fromisoformat(str(v)))
return out
def iso_week(dt: datetime) -> str:
"""ISO week of the KST date, e.g. ``2026-W42``."""
y, w, _ = to_kst(dt).isocalendar()
return f"{y}-W{w:02d}"
def load_groups(path: str | Path | None, codes: Iterable[str] = ()) -> dict[str, str]:
"""Code -> stock group/sector from ``config/stocks.yaml`` (classifier-plan §5.2).
Accepted shapes: ``{"stocks": [{"code", "group"|"sector"}]}``, a list of such entries, or ``{code: group}``.
Codes without an entry (or without the file) are their own group."""
out = {c: c for c in codes}
if path is None or not Path(path).exists():
return out
import yaml
value = yaml.safe_load(Path(path).read_text(encoding="utf-8")) or {}
entries = value.get("stocks", value) if isinstance(value, dict) else value
if isinstance(entries, dict):
out.update({str(k): str(v) for k, v in entries.items()})
return out
for e in entries:
code = str(e["code"])
out[code] = str(e.get("group") or e.get("sector") or code)
return out