transmite full 1m data

This commit is contained in:
Chris Amow 2026-08-15 04:22:40 -05:00
parent 777ad4af91
commit 5ee32b327c
8 changed files with 46 additions and 5 deletions

View file

@ -169,3 +169,7 @@ markers.
price is sitting on. There is no durable state yet.
- Times are epoch seconds, UTC, everywhere. Only the display is localised —
never shift the stored values.
- **Do not hardcode how many bars a client gets.** A leftover `1000` on the
snapshot made 1m look empty past ~1am while the store held 5,000. The
store cap is `max_bars_per_tf`. The next step is a visible-window fetch,
not another silent number.

View file

@ -12,6 +12,7 @@ from fastapi import APIRouter, Depends, HTTPException, Query, Request, Response
from pydantic import BaseModel, Field
from app.bars.models import Timeframe
from app.config import DEFAULT_MAX_BARS_PER_TF
from app.analysis.levels import Side
from app.analysis.manual_lines import ManualLine
from app.api.deps import require_token
@ -141,7 +142,11 @@ def status(request: Request):
@router.get("/bars")
def bars(request: Request, tf: str = "1m", limit: int = Query(500, ge=1, le=5000)):
def bars(
request: Request,
tf: str = "1m",
limit: int | None = Query(None, ge=1, le=DEFAULT_MAX_BARS_PER_TF),
):
try:
timeframe = Timeframe(tf)
except ValueError as exc:

View file

@ -86,7 +86,7 @@ def snapshot(runtime, tf: Timeframe, prefs: dict | None = None) -> dict:
return {
"type": "snapshot",
"tf": tf.value,
"bars": [bar.to_dict() for bar in runtime.store.get(tf, 1000)],
"bars": [bar.to_dict() for bar in runtime.store.get(tf)],
"levels": [level.to_dict() for level in runtime.levels],
"clusters": [cluster.to_dict() for cluster in connection_clusters(runtime, prefs)],
"price": runtime.store.get(Timeframe.M1, 1)[-1].c

View file

@ -2,6 +2,7 @@ from collections import defaultdict, deque
from typing import Protocol
from app.bars.models import Bar, Timeframe
from app.config import DEFAULT_MAX_BARS_PER_TF
class BarStore(Protocol):
@ -11,7 +12,7 @@ class BarStore(Protocol):
class InMemoryBarStore:
def __init__(self, max_bars_per_tf: int = 5000):
def __init__(self, max_bars_per_tf: int = DEFAULT_MAX_BARS_PER_TF):
self._bars: dict[Timeframe, deque[Bar]] = defaultdict(
lambda: deque(maxlen=max_bars_per_tf)
)

View file

@ -5,6 +5,8 @@ from pydantic_settings import BaseSettings, SettingsConfigDict
from app.bars.models import Timeframe
DEFAULT_MAX_BARS_PER_TF = 5000
TIMEFRAME_WEIGHT = {
Timeframe.M1: 1,
Timeframe.M2: 1,
@ -29,7 +31,7 @@ class Settings(BaseSettings):
seed_1m_range: str = "8d"
timeframes: str = "1m,5m,15m,30m,1h,1d"
base_timeframes: str = "1m,30m,1d"
max_bars_per_tf: int = 5000
max_bars_per_tf: int = DEFAULT_MAX_BARS_PER_TF
# Rollback gate for source-timeframe trendline geometry. When false, both
# server pricing and browser rendering use the previous 1m/displayed grid.
trendline_source_geometry: bool = True

View file

@ -1,6 +1,6 @@
# Current recommendations
Last reviewed: 2026-08-11 05:58 CDT.
Last reviewed: 2026-08-15 00:00 CDT.
This file is the short list of work worth considering next. Verified history,
measurements and completed work remain in `docs/implementation.md`, and the
@ -26,6 +26,18 @@ missed a real overlay offset. Capture the asset commit, final three bar times,
their logical/x coordinates, the first two future coordinates, canonical line
prices, and page-pixel samples from the rendered line before changing code.
### Load bars from the visible window
The 1m chart stopping at ~1am was a leftover `get(tf, 1000)` on the
WebSocket snapshot. The store already held 5,000. The socket now sends
everything in the store (`MAX_BARS_PER_TF`). That is the immediate fix,
not the destination.
Next: send the visible window and fetch older bars when the time scale
hits the left edge. Do not put another silent numeric cap on a payload.
How much the client gets is either “what is on screen” or the named
store limit — never a bare `1000` in a socket handler.
### Deepen freshness telemetry
The status bar now reports when the browser received its latest snapshot or bar.

View file

@ -1074,3 +1074,12 @@ Production then showed that whitespace-only points could still disappear from th
shared scale. The time-scale owner now uses transparent zero-valued points with an
`autoscaleInfoProvider` that returns null. They remain invisible and cannot affect
price range, but Lightweight Charts retains their timestamps as real series data.
### 2026-08-15 — 1m zoom-out died at ~1am because the socket sent 1,000 bars
Compressing the 1m time scale stopped around 01:00, then looked empty.
`HELD` was 5,000; `window.__chart.bars.length` was 1,000. 5m looked deep
because the same 1,000 bars are 3.5 days. The cap was a literal from the
first live-chart commit, never a setting. Snapshot now sends the store
(`max_bars_per_tf`). Viewport fetch is the follow-up — do not replace
one silent number with another.

View file

@ -52,6 +52,14 @@ def test_websocket_layer_preferences_are_isolated_per_connection(tmp_path):
assert runtime.clusters == original_clusters
def test_a_1m_snapshot_is_not_capped_shorter_than_the_store(tmp_path):
runtime = Runtime(Settings(manual_lines_path=tmp_path / "manual_lines.json"))
for minute in range(1001):
runtime.store.put(Bar(Timeframe.M1, 60 * minute, 1, 1, 1, 1, 1, True, "/ES", "test"))
assert len(snapshot(runtime, Timeframe.M1)["bars"]) == 1001
def test_quote_change_always_uses_the_daily_session_open(tmp_path):
runtime = Runtime(Settings(manual_lines_path=tmp_path / "manual_lines.json"))
runtime.store.put(Bar(Timeframe.D1, 1000, 6123.25, 6140, 6100, 6130, 1, False, "/ES", "test"))