chart/app/bars/models.py
Chris Amow 8c2ef80966 Remove the 4h timeframe
It was never in the enabled timeframes, so it held zero bars and produced no
levels, but it still carried weight 8 in the scoring table and forced
bucket_start to special-case a wall-clock ET anchor whose entire purpose was
surviving DST transitions. That was the most intricate logic in session.py,
maintained for a timeframe nobody used.

Daily is now the only session-anchored bucket, which is a much easier rule to
state and to keep correct. The DST parametrised tests go with it; the Sunday
open and daily boundary cases remain.

Manual-line tests move to 1h, so the weight assertions drop from 8 to 4.

The plan document keeps its 4h examples — rewriting a dozen illustrative
sentences would churn more than it clarifies — but the timeframe-roles section
now records the removal so nothing reads as a spec to build.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-10 00:43:47 -05:00

61 lines
1.3 KiB
Python

from dataclasses import asdict, dataclass
from enum import Enum
from typing import Any
class Timeframe(str, Enum):
M1 = "1m"
M2 = "2m"
M5 = "5m"
M15 = "15m"
M30 = "30m"
H1 = "1h"
D1 = "1d"
@property
def seconds(self) -> int:
seconds = {
self.M1: 60,
self.M2: 120,
self.M5: 300,
self.M15: 900,
self.M30: 1800,
self.H1: 3600,
}
if self is self.D1:
raise ValueError("1d is session-defined, not a fixed number of seconds")
return seconds[self]
@dataclass(slots=True)
class Bar:
tf: Timeframe
t: int
o: float
h: float
l: float
c: float
v: int
closed: bool
symbol: str
source: str
def to_dict(self) -> dict[str, Any]:
value = asdict(self)
value["tf"] = self.tf.value
return value
@classmethod
def from_dict(cls, value: dict[str, Any]) -> "Bar":
return cls(
tf=Timeframe(value["tf"]),
t=int(value["t"]),
o=float(value["o"]),
h=float(value["h"]),
l=float(value["l"]),
c=float(value["c"]),
v=int(value.get("v") or 0),
closed=bool(value["closed"]),
symbol=str(value["symbol"]),
source=str(value["source"]),
)