transmite full 1m data
This commit is contained in:
parent
777ad4af91
commit
5ee32b327c
8 changed files with 46 additions and 5 deletions
|
|
@ -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.
|
||||
|
|
|
|||
|
|
@ -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:
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
)
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
|
|
|
|||
|
|
@ -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"))
|
||||
|
|
|
|||
Loading…
Reference in a new issue