fix: UTC+8 market chart times and archive full history K-line load
Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -29,10 +29,10 @@ ARCHIVE_DEFAULT_TIMEFRAME = "15m"
|
||||
ARCHIVE_SEED_LOOKBACK_DAYS = 30
|
||||
ARCHIVE_VISIBLE_BARS_DEFAULT = 200
|
||||
ARCHIVE_MAX_CANDLES: dict[str, int] = {
|
||||
"5m": 4000,
|
||||
"15m": 2500,
|
||||
"1h": 1200,
|
||||
"4h": 600,
|
||||
"5m": 9000,
|
||||
"15m": 15000,
|
||||
"1h": 4000,
|
||||
"4h": 2000,
|
||||
}
|
||||
ARCHIVE_SYNC_INTERVAL_SEC = int(os.getenv("HUB_ARCHIVE_SYNC_INTERVAL_SEC", str(4 * 3600)))
|
||||
ARCHIVE_TRADE_DAYS = int(os.getenv("HUB_ARCHIVE_TRADE_DAYS", "365"))
|
||||
@@ -159,6 +159,9 @@ def parse_wall_clock_ms(raw: Any, *, tz: ZoneInfo = CHART_DISPLAY_TZ) -> int | N
|
||||
s = str(raw).strip().replace("Z", "").replace("T", " ")
|
||||
if not s:
|
||||
return None
|
||||
if s.isdigit():
|
||||
v = int(s)
|
||||
return v if v > 1_000_000_000_000 else v * 1000
|
||||
for fmt, ln in (("%Y-%m-%d %H:%M:%S", 19), ("%Y-%m-%d %H:%M", 16), ("%Y-%m-%d", 10)):
|
||||
try:
|
||||
dt = datetime.strptime(s[:ln], fmt)
|
||||
@@ -873,8 +876,8 @@ def resolve_archive_chart(
|
||||
merged = [b for b in agg if start_ms <= int(b["open_time_ms"]) <= end_ms]
|
||||
|
||||
max_n = ARCHIVE_MAX_CANDLES.get(tf, 2000)
|
||||
if rm == "history" and merged:
|
||||
merged = _trim_bars_for_cap(merged, end_ms=end_ms, max_n=max_n)
|
||||
if rm == "history" and merged and len(merged) > max_n:
|
||||
merged = merged[:max_n]
|
||||
|
||||
candles = _to_candles(merged)
|
||||
if not candles:
|
||||
|
||||
@@ -605,7 +605,10 @@
|
||||
chart.timeScale().setVisibleLogicalRange({ from: candles.length - 120, to: candles.length + 5 });
|
||||
}
|
||||
const markHint = markAuto && trades.length > 1 ? " · 自动标注 " + trades.length + " 笔" : tr ? " · 已标注开/平" : "";
|
||||
const histHint = openMs && closeMs ? " · 可拖动/滚轮缩放查看建仓前走势" : "";
|
||||
const histHint =
|
||||
openMs && closeMs
|
||||
? " · 建档30天历史 · 可拖动/滚轮缩放查看建仓前走势"
|
||||
: "";
|
||||
setStatus("K 线 " + candles.length + " 根 · " + timeframe + markHint + histHint);
|
||||
}
|
||||
|
||||
|
||||
@@ -79,6 +79,69 @@
|
||||
"1w": "周线",
|
||||
};
|
||||
const TF_DIGIT_TIMEOUT_MS = 650;
|
||||
const CHART_TZ_OFFSET_SEC = 8 * 60 * 60;
|
||||
|
||||
function pad2(n) {
|
||||
return n < 10 ? "0" + n : String(n);
|
||||
}
|
||||
|
||||
function utcSecToBjDate(utcSec) {
|
||||
return new Date((Number(utcSec) + CHART_TZ_OFFSET_SEC) * 1000);
|
||||
}
|
||||
|
||||
function formatChartTimeBj(utcSec, withDate) {
|
||||
const d = utcSecToBjDate(utcSec);
|
||||
const h = pad2(d.getUTCHours());
|
||||
const mi = pad2(d.getUTCMinutes());
|
||||
if (!withDate) return h + ":" + mi;
|
||||
return (
|
||||
d.getUTCFullYear() +
|
||||
"-" +
|
||||
pad2(d.getUTCMonth() + 1) +
|
||||
"-" +
|
||||
pad2(d.getUTCDate()) +
|
||||
" " +
|
||||
h +
|
||||
":" +
|
||||
mi
|
||||
);
|
||||
}
|
||||
|
||||
function chartLocalizationBj() {
|
||||
return {
|
||||
locale: "zh-CN",
|
||||
dateFormat: "yyyy-MM-dd",
|
||||
timeFormatter: function (time) {
|
||||
if (typeof time === "number") return formatChartTimeBj(time, true);
|
||||
if (time && typeof time === "object" && time.year) {
|
||||
return time.year + "-" + pad2(time.month) + "-" + pad2(time.day);
|
||||
}
|
||||
return "";
|
||||
},
|
||||
tickMarkFormatter: function (time, tickMarkType) {
|
||||
if (typeof time !== "number") {
|
||||
if (time && typeof time === "object" && time.year) {
|
||||
return time.year + "-" + pad2(time.month) + "-" + pad2(time.day);
|
||||
}
|
||||
return "";
|
||||
}
|
||||
const d = utcSecToBjDate(time);
|
||||
if (tickMarkType === 0) return String(d.getUTCFullYear());
|
||||
if (tickMarkType === 1) return pad2(d.getUTCMonth() + 1);
|
||||
if (tickMarkType === 2) return pad2(d.getUTCDate());
|
||||
return formatChartTimeBj(time, false);
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
function buildChartLocalization() {
|
||||
const loc = chartLocalizationBj();
|
||||
loc.priceFormatter = function (p) {
|
||||
return fmtPrice(p);
|
||||
};
|
||||
return loc;
|
||||
}
|
||||
|
||||
const chartHost = document.getElementById("market-chart");
|
||||
if (!chartHost) return;
|
||||
|
||||
@@ -1649,11 +1712,7 @@
|
||||
applyPriceFormatToSeries(indSeries.ema55, pf);
|
||||
if (chart) {
|
||||
chart.applyOptions({
|
||||
localization: {
|
||||
priceFormatter: function (p) {
|
||||
return fmtPrice(p);
|
||||
},
|
||||
},
|
||||
localization: buildChartLocalization(),
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -1933,6 +1992,7 @@
|
||||
horzLines: { visible: false },
|
||||
},
|
||||
rightPriceScale: { borderColor: tp.border, autoScale: true },
|
||||
localization: buildChartLocalization(),
|
||||
timeScale: {
|
||||
borderColor: tp.border,
|
||||
timeVisible: true,
|
||||
|
||||
@@ -417,8 +417,8 @@
|
||||
<div id="toast"></div>
|
||||
<script src="https://unpkg.com/lightweight-charts@4.2.0/dist/lightweight-charts.standalone.production.js"></script>
|
||||
<script src="/assets/chart_draw.js?v=20260608-market-vol-rank"></script>
|
||||
<script src="/assets/chart.js?v=20260608-market-vol-rank-v5"></script>
|
||||
<script src="/assets/archive.js?v=20260608-hub-archive-tz8"></script>
|
||||
<script src="/assets/chart.js?v=20260608-market-tz8"></script>
|
||||
<script src="/assets/archive.js?v=20260608-hub-archive-history"></script>
|
||||
<script src="/assets/ai_review_render.js?v=2"></script>
|
||||
<script src="/assets/app.js?v=20260607-hub-archive-v1"></script>
|
||||
</body>
|
||||
|
||||
@@ -216,3 +216,32 @@ def test_parse_wall_clock_ms_uses_utc_plus_8():
|
||||
assert dt_bj.strftime("%Y-%m-%d %H:%M:%S") == "2026-06-07 20:30:00"
|
||||
assert ms_to_wall_clock_str(ms) == "2026-06-07 20:30:00"
|
||||
assert parse_wall_clock_ms("2026-06-07 20:30") == ms
|
||||
|
||||
|
||||
def test_parse_wall_clock_ms_accepts_epoch_strings():
|
||||
ms = 1_700_000_000_000
|
||||
assert parse_wall_clock_ms(str(ms)) == ms
|
||||
assert parse_wall_clock_ms(str(ms // 1000)) == ms
|
||||
|
||||
|
||||
def test_resolve_archive_chart_history_uses_trade_span_not_200_bars():
|
||||
with tempfile.TemporaryDirectory() as td:
|
||||
db = Path(td) / "archive.db"
|
||||
init_db(db)
|
||||
opened = 1_700_000_000_000
|
||||
closed = opened + 20 * 24 * 3600_000
|
||||
_seed_5m_bars(db, opened - 35 * 24 * 3600_000, 40 * 24 * 12)
|
||||
out = resolve_archive_chart(
|
||||
"gate",
|
||||
"ONDO",
|
||||
"15m",
|
||||
opened_ms=opened,
|
||||
closed_ms=closed,
|
||||
mode="hold",
|
||||
bars=200,
|
||||
range_mode="history",
|
||||
db_path=db,
|
||||
)
|
||||
assert out["ok"] is True
|
||||
assert out["range_mode"] == "history"
|
||||
assert out["bar_count"] > 200
|
||||
|
||||
Reference in New Issue
Block a user