移除行情区 3m/10m/20m/6h/8h 周期
保留 30m、2h、12h;12h 仍支持从 1h 聚合 Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
+1
-18
@@ -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,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": "周线",
|
||||||
|
|||||||
@@ -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
@@ -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)
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user