移除行情区 3m/10m/20m/6h/8h 周期

保留 30m、2h、12h;12h 仍支持从 1h 聚合

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
dekun
2026-06-02 15:33:33 +08:00
parent eebc50cffa
commit fdca82ea26
4 changed files with 16 additions and 59 deletions
+1 -18
View File
@@ -9,17 +9,12 @@ from typing import Any, Callable, Optional
CHART_TIMEFRAMES = frozenset( CHART_TIMEFRAMES = frozenset(
{ {
"1m", "1m",
"3m",
"5m", "5m",
"10m",
"15m", "15m",
"20m",
"30m", "30m",
"1h", "1h",
"2h", "2h",
"4h", "4h",
"6h",
"8h",
"12h", "12h",
"1d", "1d",
"1w", "1w",
@@ -27,43 +22,31 @@ CHART_TIMEFRAMES = frozenset(
) )
CHART_TIMEFRAME_ORDER = ( CHART_TIMEFRAME_ORDER = (
"1m", "1m",
"3m",
"5m", "5m",
"10m",
"15m", "15m",
"20m",
"30m", "30m",
"1h", "1h",
"2h", "2h",
"4h", "4h",
"6h",
"8h",
"12h", "12h",
"1d", "1d",
"1w", "1w",
) )
DAILY_PLUS_TIMEFRAMES = frozenset({"1d", "1w"}) DAILY_PLUS_TIMEFRAMES = frozenset({"1d", "1w"})
# 部分交易所 ccxt 无原生周期(如 Gate 无 6h/12h,或原生 K 线间隔异常时从细周期聚合 # 部分交易所 ccxt 无原生 12h,或原生 K 线间隔异常时从 1h 聚合
OHLCV_AGGREGATE_FROM: dict[str, str] = { OHLCV_AGGREGATE_FROM: dict[str, str] = {
"6h": "1h",
"8h": "1h",
"12h": "1h", "12h": "1h",
} }
TIMEFRAME_MS: dict[str, int] = { TIMEFRAME_MS: dict[str, int] = {
"1m": 60_000, "1m": 60_000,
"3m": 3 * 60_000,
"5m": 5 * 60_000, "5m": 5 * 60_000,
"10m": 10 * 60_000,
"15m": 15 * 60_000, "15m": 15 * 60_000,
"20m": 20 * 60_000,
"30m": 30 * 60_000, "30m": 30 * 60_000,
"1h": 60 * 60_000, "1h": 60 * 60_000,
"2h": 2 * 60 * 60_000, "2h": 2 * 60 * 60_000,
"4h": 4 * 60 * 60_000, "4h": 4 * 60 * 60_000,
"6h": 6 * 60 * 60_000,
"8h": 8 * 60 * 60_000,
"12h": 12 * 60 * 60_000, "12h": 12 * 60 * 60_000,
"1d": 24 * 60 * 60_000, "1d": 24 * 60 * 60_000,
"1w": 7 * 24 * 60 * 60_000, "1w": 7 * 24 * 60 * 60_000,
-15
View File
@@ -15,34 +15,24 @@
const MAX_DIV_MARKERS = 4; const MAX_DIV_MARKERS = 4;
const TF_MS = { const TF_MS = {
"1m": 60_000, "1m": 60_000,
"3m": 3 * 60_000,
"5m": 5 * 60_000, "5m": 5 * 60_000,
"10m": 10 * 60_000,
"15m": 15 * 60_000, "15m": 15 * 60_000,
"20m": 20 * 60_000,
"30m": 30 * 60_000, "30m": 30 * 60_000,
"1h": 60 * 60_000, "1h": 60 * 60_000,
"2h": 2 * 60 * 60_000, "2h": 2 * 60 * 60_000,
"4h": 4 * 60 * 60_000, "4h": 4 * 60 * 60_000,
"6h": 6 * 60 * 60_000,
"8h": 8 * 60 * 60_000,
"12h": 12 * 60 * 60_000, "12h": 12 * 60 * 60_000,
"1d": 24 * 60 * 60_000, "1d": 24 * 60 * 60_000,
"1w": 7 * 24 * 60 * 60_000, "1w": 7 * 24 * 60 * 60_000,
}; };
const TF_BY_MINUTES = { const TF_BY_MINUTES = {
"1": "1m", "1": "1m",
"3": "3m",
"5": "5m", "5": "5m",
"10": "10m",
"15": "15m", "15": "15m",
"20": "20m",
"30": "30m", "30": "30m",
"60": "1h", "60": "1h",
"120": "2h", "120": "2h",
"240": "4h", "240": "4h",
"360": "6h",
"480": "8h",
"720": "12h", "720": "12h",
"1440": "1d", "1440": "1d",
"10080": "1w", "10080": "1w",
@@ -52,17 +42,12 @@
}); });
const TF_CN_LABEL = { const TF_CN_LABEL = {
"1m": "1分钟", "1m": "1分钟",
"3m": "3分钟",
"5m": "5分钟", "5m": "5分钟",
"10m": "10分钟",
"15m": "15分钟", "15m": "15分钟",
"20m": "20分钟",
"30m": "30分钟", "30m": "30分钟",
"1h": "1小时", "1h": "1小时",
"2h": "2小时", "2h": "2小时",
"4h": "4小时", "4h": "4小时",
"6h": "6小时",
"8h": "8小时",
"12h": "12小时", "12h": "12小时",
"1d": "日线", "1d": "日线",
"1w": "周线", "1w": "周线",
+2 -12
View File
@@ -60,7 +60,7 @@
<div id="page-market" class="page hidden"> <div id="page-market" class="page hidden">
<div class="page-head"> <div class="page-head">
<h1><span class="head-tag">MKT</span> 行情区</h1> <h1><span class="head-tag">MKT</span> 行情区</h1>
<p class="page-desc">按需拉取 K 线,本地库保留 15 天(无后台自动更新)。快捷键:<kbd>F</kbd> 切换 K 线全屏/退出(全屏时 <kbd>Esc</kbd> 退出);数字键为周期分钟数(如 15→15m、120→2h、720→12h、1440→1d,多键连按后 Enter 或稍停确认)。</p> <p class="page-desc">按需拉取 K 线,本地库保留 15 天(无后台自动更新)。快捷键:<kbd>F</kbd> 切换 K 线全屏/退出(全屏时 <kbd>Esc</kbd> 退出);数字键切换周期:1/5/15/30/60/120/240/720/1440/10080(多键连按,如 1 再 5 为 15m)。</p>
</div> </div>
<details class="hint-box"> <details class="hint-box">
<summary>数据说明</summary> <summary>数据说明</summary>
@@ -82,17 +82,12 @@
<span>周期</span> <span>周期</span>
<select id="market-timeframe"> <select id="market-timeframe">
<option value="1m">1m</option> <option value="1m">1m</option>
<option value="3m">3m</option>
<option value="5m">5m</option> <option value="5m">5m</option>
<option value="10m">10m</option>
<option value="15m">15m</option> <option value="15m">15m</option>
<option value="20m">20m</option>
<option value="30m">30m</option> <option value="30m">30m</option>
<option value="1h">1h</option> <option value="1h">1h</option>
<option value="2h">2h</option> <option value="2h">2h</option>
<option value="4h">4h</option> <option value="4h">4h</option>
<option value="6h">6h</option>
<option value="8h">8h</option>
<option value="12h">12h</option> <option value="12h">12h</option>
<option value="1d" selected>1d</option> <option value="1d" selected>1d</option>
<option value="1w">1w</option> <option value="1w">1w</option>
@@ -145,17 +140,12 @@
<span>周期</span> <span>周期</span>
<select id="market-fs-timeframe"> <select id="market-fs-timeframe">
<option value="1m">1m</option> <option value="1m">1m</option>
<option value="3m">3m</option>
<option value="5m">5m</option> <option value="5m">5m</option>
<option value="10m">10m</option>
<option value="15m">15m</option> <option value="15m">15m</option>
<option value="20m">20m</option>
<option value="30m">30m</option> <option value="30m">30m</option>
<option value="1h">1h</option> <option value="1h">1h</option>
<option value="2h">2h</option> <option value="2h">2h</option>
<option value="4h">4h</option> <option value="4h">4h</option>
<option value="6h">6h</option>
<option value="8h">8h</option>
<option value="12h">12h</option> <option value="12h">12h</option>
<option value="1d">1d</option> <option value="1d">1d</option>
<option value="1w">1w</option> <option value="1w">1w</option>
@@ -256,7 +246,7 @@
<div id="toast"></div> <div id="toast"></div>
<script src="https://unpkg.com/lightweight-charts@4.2.0/dist/lightweight-charts.standalone.production.js"></script> <script src="https://unpkg.com/lightweight-charts@4.2.0/dist/lightweight-charts.standalone.production.js"></script>
<script src="/assets/chart.js?v=20260528-hub-tf-h"></script> <script src="/assets/chart.js?v=20260528-hub-tf-trim"></script>
<script src="/assets/app.js?v=20260528-hub-tpsl-fix"></script> <script src="/assets/app.js?v=20260528-hub-tpsl-fix"></script>
</body> </body>
</html> </html>
+13 -14
View File
@@ -132,25 +132,24 @@ class TestHubOhlcvLib(unittest.TestCase):
self.assertGreaterEqual(len(ex.calls), 3) self.assertGreaterEqual(len(ex.calls), 3)
self.assertAlmostEqual(out["bars"][-1]["close"], 3.05) self.assertAlmostEqual(out["bars"][-1]["close"], 3.05)
def test_aggregate_6h_from_1h_when_exchange_lacks_native(self): def test_aggregate_12h_from_1h_when_exchange_lacks_native(self):
"""Gate 等无 6h 时应从 1h 聚合。""" """无原生 12h 时应从 1h 聚合。"""
from hub_ohlcv_lib import TIMEFRAME_MS from hub_ohlcv_lib import TIMEFRAME_MS
h1 = TIMEFRAME_MS["1h"] h1 = TIMEFRAME_MS["1h"]
h6 = TIMEFRAME_MS["6h"] h12 = TIMEFRAME_MS["12h"]
base = 1_700_000_000_000 base = (1_700_000_000_000 // h12) * h12
base = (base // h6) * h6
one_h = [ one_h = [
[base + i * h1, 100.0 + i, 101.0 + i, 99.0 + i, 100.5 + i, 10.0] [base + i * h1, 100.0 + i, 101.0 + i, 99.0 + i, 100.5 + i, 10.0]
for i in range(24) for i in range(48)
] ]
ex = _FakeExchange( ex = _FakeExchange(
[one_h], [one_h],
timeframes={"1h": "1h", "4h": "4h", "8h": "8h"}, timeframes={"1h": "1h", "4h": "4h"},
) )
out = fetch_ohlcv_for_hub( out = fetch_ohlcv_for_hub(
symbol="BTC/USDT", symbol="BTC/USDT",
timeframe="6h", timeframe="12h",
since_ms=base, since_ms=base,
limit=4, limit=4,
normalize_symbol_input=lambda s: str(s).strip().upper(), normalize_symbol_input=lambda s: str(s).strip().upper(),
@@ -161,15 +160,15 @@ class TestHubOhlcvLib(unittest.TestCase):
self.assertTrue(out.get("ok")) self.assertTrue(out.get("ok"))
bars = out.get("bars") or [] bars = out.get("bars") or []
self.assertEqual(len(bars), 4) self.assertEqual(len(bars), 4)
self.assertTrue(bars_spacing_matches_timeframe(bars, "6h")) self.assertTrue(bars_spacing_matches_timeframe(bars, "12h"))
self.assertEqual(ex.calls[0]["timeframe"], "1h") self.assertEqual(ex.calls[0]["timeframe"], "1h")
def test_aggregate_ohlcv_bars_buckets(self): def test_aggregate_ohlcv_bars_buckets(self):
from hub_ohlcv_lib import TIMEFRAME_MS from hub_ohlcv_lib import TIMEFRAME_MS
h1 = TIMEFRAME_MS["1h"] h1 = TIMEFRAME_MS["1h"]
h6 = TIMEFRAME_MS["6h"] h12 = TIMEFRAME_MS["12h"]
base = (1_700_000_000_000 // h6) * h6 base = (1_700_000_000_000 // h12) * h12
src = [ src = [
{ {
"open_time_ms": base + i * h1, "open_time_ms": base + i * h1,
@@ -179,11 +178,11 @@ class TestHubOhlcvLib(unittest.TestCase):
"close": 1.5, "close": 1.5,
"volume": 1.0, "volume": 1.0,
} }
for i in range(6) for i in range(12)
] ]
out = aggregate_ohlcv_bars(src, "6h") out = aggregate_ohlcv_bars(src, "12h")
self.assertEqual(len(out), 1) self.assertEqual(len(out), 1)
self.assertEqual(out[0]["volume"], 6.0) self.assertEqual(out[0]["volume"], 12.0)
self.assertEqual(out[0]["high"], 2.0) self.assertEqual(out[0]["high"], 2.0)
self.assertEqual(out[0]["low"], 0.5) self.assertEqual(out[0]["low"], 0.5)