Add two-day amplitude window to amp-stats.
For each settlement day, also compute H-L over start minus one day through 16:00 (e.g. 25 16:00 to 27 16:00). Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
+8
-5
@@ -46,12 +46,13 @@
|
||||
|------|------|
|
||||
| 开→高 | `H − O`(一边波动) |
|
||||
| 开→低 | `O − L`(另一边波动) |
|
||||
| **振幅** | `H − L`(= 开→高 + 开→低) |
|
||||
| 涨跌值 | `C − O` |
|
||||
| **振幅** | `H − L`(= 开→高 + 开→低),窗为起点整点 → 当日 16:00 |
|
||||
| **两日振幅** | 同上口径,但起点再往前推 1 天;例起点 16:00、结算 27 日 → **25日16:00 → 27日16:00** |
|
||||
| 涨跌值 | `C − O`(单日窗) |
|
||||
|
||||
例:O=2000,H=2500,L=1800 → 开→高 500,开→低 200,振幅 **700**。
|
||||
|
||||
汇总必含:最大振幅(及日期)、开→高/开→低的最大与均值等。
|
||||
汇总必含:最大振幅(及日期)、两日振幅最大/均值/中位、开→高/开→低的最大与均值等。
|
||||
|
||||
K 线粒度:**1H**(与整点对齐);价源优先 OKX 指数(ETH-USD / BTC-USD),失败再降级永续标记。
|
||||
近期 K 线接口约仅 **1440** 根(1H≈60 天);更长周期自动续拉 `history-index-candles` / `history-candles`。
|
||||
@@ -65,12 +66,13 @@ K 线粒度:**1H**(与整点对齐);价源优先 OKX 指数(ETH-USD /
|
||||
|
||||
| 汇总项 | 口径 |
|
||||
|--------|------|
|
||||
| 振幅≥点数 | `H−L ≥ 点数` 的天数与**占比**(主指标) |
|
||||
| 振幅≥点数 | 单日窗 `H−L ≥ 点数` 的天数与**占比**(主指标) |
|
||||
| 两日振幅≥点数 | 两日窗振幅 ≥ 点数 的天数与占比 |
|
||||
| 开→高≥点数 | `H−O ≥ 点数` 天数与占比 |
|
||||
| 开→低≥点数 | `O−L ≥ 点数` 天数与占比 |
|
||||
| \|涨跌\|≥点数 | `\|C−O\| ≥ 点数` 天数与占比 |
|
||||
|
||||
日表保留 **开→高 / 开→低**(两边波动点数),并标 **振幅达标**;达标行振幅会高亮。
|
||||
日表保留 **开→高 / 开→低**、**振幅**、**两日振幅**(悬停可见两日窗起止),并标 **振幅达标**。
|
||||
改点数 / 周末筛选会在已有日表上**本地重算**(不重拉 K 线)。
|
||||
|
||||
### 周末
|
||||
@@ -108,3 +110,4 @@ K 线粒度:**1H**(与整点对齐);价源优先 OKX 指数(ETH-USD /
|
||||
| 2026-07-23 | 买跨对照、周末筛选、止盈点 |
|
||||
| 2026-07-28 | 永期对冲对照(后已移除) |
|
||||
| 2026-07-28 | 去掉买跨/永期;改为波动点数→振幅占比 |
|
||||
| 2026-07-28 | 增加两日振幅(例 25日16:00→27日16:00) |
|
||||
|
||||
+118
-16
@@ -68,10 +68,20 @@ def resolve_sample_days(period: str, custom_days: Any = None) -> int:
|
||||
return PERIOD_DAYS[p]
|
||||
|
||||
|
||||
def window_bounds_for_settlement(settlement: date, start_hour: int) -> tuple[datetime, datetime]:
|
||||
"""返回 [start, end) 的本地时刻;end 为结算日 16:00."""
|
||||
def window_bounds_for_settlement(
|
||||
settlement: date,
|
||||
start_hour: int,
|
||||
*,
|
||||
span_days: int = 1,
|
||||
) -> tuple[datetime, datetime]:
|
||||
"""返回 [start, end) 的本地时刻;end 为结算日 16:00.
|
||||
|
||||
span_days=1: 与现口径相同(如 26日16:00→27日16:00)
|
||||
span_days=2: 再往前推 1 天(如 25日16:00→27日16:00)
|
||||
"""
|
||||
if not (0 <= int(start_hour) <= 23):
|
||||
raise ValueError("起点须为 0-23 整点")
|
||||
span = max(1, int(span_days or 1))
|
||||
end = datetime(settlement.year, settlement.month, settlement.day, END_HOUR, 0, 0, tzinfo=APP_TZ)
|
||||
sh = int(start_hour)
|
||||
if sh >= END_HOUR:
|
||||
@@ -79,6 +89,8 @@ def window_bounds_for_settlement(settlement: date, start_hour: int) -> tuple[dat
|
||||
start = datetime(prev.year, prev.month, prev.day, sh, 0, 0, tzinfo=APP_TZ)
|
||||
else:
|
||||
start = datetime(settlement.year, settlement.month, settlement.day, sh, 0, 0, tzinfo=APP_TZ)
|
||||
if span > 1:
|
||||
start = start - timedelta(days=span - 1)
|
||||
return start, end
|
||||
|
||||
|
||||
@@ -127,12 +139,12 @@ def bars_to_map(bars: list[dict[str, Any]]) -> dict[int, dict[str, float]]:
|
||||
return m
|
||||
|
||||
|
||||
def compute_day_row(
|
||||
settlement: date,
|
||||
start_hour: int,
|
||||
def _ohlc_window_metrics(
|
||||
start: datetime,
|
||||
end: datetime,
|
||||
bar_map: dict[int, dict[str, float]],
|
||||
) -> Optional[dict[str, Any]]:
|
||||
start, end = window_bounds_for_settlement(settlement, start_hour)
|
||||
"""在 [start, end) 上算开高低收与开→高/开→低/振幅/涨跌."""
|
||||
start_ms = int(start.timestamp() * 1000)
|
||||
# 1H 棒覆盖 [T, T+1h);窗终点 16:00 用 15:00 棒的 close
|
||||
last_bar_ms = int((end - timedelta(hours=1)).timestamp() * 1000)
|
||||
@@ -153,15 +165,9 @@ def compute_day_row(
|
||||
down = opens - lo
|
||||
amp = hi - lo
|
||||
change = close - opens
|
||||
wd = settlement.weekday() # Mon=0 … Sun=6
|
||||
is_we = wd >= 5
|
||||
return {
|
||||
"settlement_day": settlement.isoformat(),
|
||||
"window_start": start.strftime("%Y-%m-%d %H:%M"),
|
||||
"window_end": end.strftime("%Y-%m-%d %H:%M"),
|
||||
"weekday": wd,
|
||||
"weekday_label": "六" if wd == 5 else ("日" if wd == 6 else ""),
|
||||
"is_weekend": is_we,
|
||||
"open": round(opens, 4),
|
||||
"high": round(hi, 4),
|
||||
"low": round(lo, 4),
|
||||
@@ -173,6 +179,59 @@ def compute_day_row(
|
||||
}
|
||||
|
||||
|
||||
def compute_day_row(
|
||||
settlement: date,
|
||||
start_hour: int,
|
||||
bar_map: dict[int, dict[str, float]],
|
||||
) -> Optional[dict[str, Any]]:
|
||||
start, end = window_bounds_for_settlement(settlement, start_hour, span_days=1)
|
||||
m1 = _ohlc_window_metrics(start, end, bar_map)
|
||||
if m1 is None:
|
||||
return None
|
||||
start2, end2 = window_bounds_for_settlement(settlement, start_hour, span_days=2)
|
||||
m2 = _ohlc_window_metrics(start2, end2, bar_map)
|
||||
wd = settlement.weekday() # Mon=0 … Sun=6
|
||||
is_we = wd >= 5
|
||||
row: dict[str, Any] = {
|
||||
"settlement_day": settlement.isoformat(),
|
||||
"weekday": wd,
|
||||
"weekday_label": "六" if wd == 5 else ("日" if wd == 6 else ""),
|
||||
"is_weekend": is_we,
|
||||
**m1,
|
||||
}
|
||||
if m2 is None:
|
||||
row.update(
|
||||
{
|
||||
"window2_start": start2.strftime("%Y-%m-%d %H:%M"),
|
||||
"window2_end": end2.strftime("%Y-%m-%d %H:%M"),
|
||||
"open_2d": None,
|
||||
"high_2d": None,
|
||||
"low_2d": None,
|
||||
"close_2d": None,
|
||||
"up_points_2d": None,
|
||||
"down_points_2d": None,
|
||||
"amplitude_2d": None,
|
||||
"change_2d": None,
|
||||
}
|
||||
)
|
||||
else:
|
||||
row.update(
|
||||
{
|
||||
"window2_start": m2["window_start"],
|
||||
"window2_end": m2["window_end"],
|
||||
"open_2d": m2["open"],
|
||||
"high_2d": m2["high"],
|
||||
"low_2d": m2["low"],
|
||||
"close_2d": m2["close"],
|
||||
"up_points_2d": m2["up_points"],
|
||||
"down_points_2d": m2["down_points"],
|
||||
"amplitude_2d": m2["amplitude"],
|
||||
"change_2d": m2["change"],
|
||||
}
|
||||
)
|
||||
return row
|
||||
|
||||
|
||||
def normalize_move_points(raw: Any) -> Optional[float]:
|
||||
"""对照波动点数.空/≤0 表示不做点数达标对照."""
|
||||
if raw is None or raw == "":
|
||||
@@ -252,12 +311,16 @@ def enrich_rows(
|
||||
hit_up = bool(mp is not None and up >= mp)
|
||||
hit_down = bool(mp is not None and down >= mp)
|
||||
amp_hit = bool(mp is not None and amp >= mp)
|
||||
amp2 = item.get("amplitude_2d")
|
||||
amp2_v = float(amp2) if amp2 is not None and amp2 != "" else None
|
||||
amp_hit_2d = bool(mp is not None and amp2_v is not None and amp2_v >= mp)
|
||||
item["move_points"] = mp
|
||||
item["hit_up"] = hit_up
|
||||
item["hit_down"] = hit_down
|
||||
item["hit_either"] = hit_up or hit_down
|
||||
item["hit_both"] = hit_up and hit_down
|
||||
item["amp_hit"] = amp_hit
|
||||
item["amp_hit_2d"] = amp_hit_2d
|
||||
out.append(item)
|
||||
return out
|
||||
|
||||
@@ -287,6 +350,8 @@ def move_points_stats(rows: list[dict[str, Any]], move_points: float) -> dict[st
|
||||
"both_hit_ratio": None,
|
||||
"amp_hit_days": 0,
|
||||
"amp_hit_ratio": None,
|
||||
"amp_2d_hit_days": 0,
|
||||
"amp_2d_hit_ratio": None,
|
||||
"abs_change_hit_days": 0,
|
||||
"abs_change_hit_ratio": None,
|
||||
}
|
||||
@@ -296,7 +361,10 @@ def move_points_stats(rows: list[dict[str, Any]], move_points: float) -> dict[st
|
||||
down_hit = sum(1 for r in work if r.get("hit_down"))
|
||||
either = sum(1 for r in work if r.get("hit_either"))
|
||||
both = sum(1 for r in work if r.get("hit_both"))
|
||||
amp_hit = sum(1 for r in work if float(r.get("amplitude") or 0) >= mp)
|
||||
amp_hit = sum(1 for r in work if r.get("amp_hit"))
|
||||
amp2_rows = [r for r in work if r.get("amplitude_2d") is not None]
|
||||
amp2_hit = sum(1 for r in work if r.get("amp_hit_2d"))
|
||||
n2 = len(amp2_rows)
|
||||
abs_hit = sum(1 for r in work if abs(float(r.get("change") or 0)) >= mp)
|
||||
empty.update(
|
||||
{
|
||||
@@ -310,6 +378,8 @@ def move_points_stats(rows: list[dict[str, Any]], move_points: float) -> dict[st
|
||||
"both_hit_ratio": round(both / n, 4),
|
||||
"amp_hit_days": amp_hit,
|
||||
"amp_hit_ratio": round(amp_hit / n, 4),
|
||||
"amp_2d_hit_days": amp2_hit,
|
||||
"amp_2d_hit_ratio": round(amp2_hit / n2, 4) if n2 else None,
|
||||
"abs_change_hit_days": abs_hit,
|
||||
"abs_change_hit_ratio": round(abs_hit / n, 4),
|
||||
}
|
||||
@@ -323,6 +393,12 @@ def summarize_rows(
|
||||
move_points: Any = None,
|
||||
) -> dict[str, Any]:
|
||||
mp = normalize_move_points(move_points)
|
||||
empty_2d = {
|
||||
"max_amplitude_2d": None,
|
||||
"max_amplitude_2d_day": None,
|
||||
"avg_amplitude_2d": None,
|
||||
"median_amplitude_2d": None,
|
||||
}
|
||||
if not rows:
|
||||
out = {
|
||||
"sample_count": 0,
|
||||
@@ -336,6 +412,7 @@ def summarize_rows(
|
||||
"avg_down_points": None,
|
||||
"up_day_ratio": None,
|
||||
"down_day_ratio": None,
|
||||
**empty_2d,
|
||||
"move_points_stats": None,
|
||||
}
|
||||
if mp is not None:
|
||||
@@ -349,6 +426,7 @@ def summarize_rows(
|
||||
up_days = sum(1 for r in rows if float(r["change"]) > 0)
|
||||
down_days = sum(1 for r in rows if float(r["change"]) < 0)
|
||||
n = len(rows)
|
||||
amps2 = [float(r["amplitude_2d"]) for r in rows if r.get("amplitude_2d") is not None]
|
||||
out: dict[str, Any] = {
|
||||
"sample_count": n,
|
||||
"max_amplitude": round(max_amp, 4),
|
||||
@@ -361,8 +439,17 @@ def summarize_rows(
|
||||
"avg_down_points": round(statistics.fmean(downs), 4),
|
||||
"up_day_ratio": round(up_days / n, 4),
|
||||
"down_day_ratio": round(down_days / n, 4),
|
||||
**empty_2d,
|
||||
"move_points_stats": None,
|
||||
}
|
||||
if amps2:
|
||||
max_a2 = max(amps2)
|
||||
out["max_amplitude_2d"] = round(max_a2, 4)
|
||||
out["max_amplitude_2d_day"] = next(
|
||||
r["settlement_day"] for r in rows if r.get("amplitude_2d") is not None and float(r["amplitude_2d"]) == max_a2
|
||||
)
|
||||
out["avg_amplitude_2d"] = round(statistics.fmean(amps2), 4)
|
||||
out["median_amplitude_2d"] = round(statistics.median(amps2), 4)
|
||||
if mp is not None:
|
||||
out["move_points_stats"] = move_points_stats(rows, mp)
|
||||
return out
|
||||
@@ -583,11 +670,11 @@ def compute_amp_stats(
|
||||
settlements = list_settlement_dates(sample_days=sample_days, now=now)
|
||||
if not settlements:
|
||||
raise RuntimeError("无可用结算日")
|
||||
# 最远窗起点
|
||||
# 最远窗起点(含两日振幅,多拉 1 天)
|
||||
oldest = settlements[-1]
|
||||
newest = settlements[0]
|
||||
start0, _ = window_bounds_for_settlement(oldest, sh)
|
||||
_, end1 = window_bounds_for_settlement(newest, sh)
|
||||
start0, _ = window_bounds_for_settlement(oldest, sh, span_days=2)
|
||||
_, end1 = window_bounds_for_settlement(newest, sh, span_days=1)
|
||||
since_ms = int(start0.timestamp() * 1000)
|
||||
until_ms = int(end1.timestamp() * 1000)
|
||||
bars, price_source, inst_id = fetch_symbol_bars(
|
||||
@@ -723,6 +810,8 @@ def build_export_csv(payload: dict[str, Any]) -> str:
|
||||
w.writerow(["样本数", s.get("sample_count")])
|
||||
w.writerow(["最大振幅", s.get("max_amplitude"), "日期", s.get("max_amplitude_day")])
|
||||
w.writerow(["振幅均值", s.get("avg_amplitude"), "中位数", s.get("median_amplitude")])
|
||||
w.writerow(["两日最大振幅", s.get("max_amplitude_2d"), "日期", s.get("max_amplitude_2d_day")])
|
||||
w.writerow(["两日振幅均值", s.get("avg_amplitude_2d"), "中位数", s.get("median_amplitude_2d")])
|
||||
w.writerow(["开→高最大", s.get("max_up_points"), "均值", s.get("avg_up_points")])
|
||||
w.writerow(["开→低最大", s.get("max_down_points"), "均值", s.get("avg_down_points")])
|
||||
w.writerow(["上涨窗占比", s.get("up_day_ratio"), "下跌窗占比", s.get("down_day_ratio")])
|
||||
@@ -731,6 +820,7 @@ def build_export_csv(payload: dict[str, Any]) -> str:
|
||||
w.writerow([])
|
||||
w.writerow(["【波动点数·振幅占比】", mp.get("move_points")])
|
||||
w.writerow(["振幅≥点数天数", mp.get("amp_hit_days"), "占比", mp.get("amp_hit_ratio")])
|
||||
w.writerow(["两日振幅≥点数天数", mp.get("amp_2d_hit_days"), "占比", mp.get("amp_2d_hit_ratio")])
|
||||
w.writerow(["开→高≥点数天数", mp.get("up_hit_days"), "占比", mp.get("up_hit_ratio")])
|
||||
w.writerow(["开→低≥点数天数", mp.get("down_hit_days"), "占比", mp.get("down_hit_ratio")])
|
||||
w.writerow(["|涨跌|≥点数天数", mp.get("abs_change_hit_days"), "占比", mp.get("abs_change_hit_ratio")])
|
||||
@@ -751,8 +841,14 @@ def build_export_csv(payload: dict[str, Any]) -> str:
|
||||
"开→低",
|
||||
"振幅",
|
||||
"涨跌值",
|
||||
"两日窗起点",
|
||||
"两日窗终点",
|
||||
"两日振幅",
|
||||
"两日开→高",
|
||||
"两日开→低",
|
||||
"对照点数",
|
||||
"振幅达标",
|
||||
"两日振幅达标",
|
||||
]
|
||||
)
|
||||
for r in payload.get("rows") or []:
|
||||
@@ -771,8 +867,14 @@ def build_export_csv(payload: dict[str, Any]) -> str:
|
||||
r.get("down_points"),
|
||||
r.get("amplitude"),
|
||||
r.get("change"),
|
||||
r.get("window2_start"),
|
||||
r.get("window2_end"),
|
||||
r.get("amplitude_2d"),
|
||||
r.get("up_points_2d"),
|
||||
r.get("down_points_2d"),
|
||||
r.get("move_points") if r.get("move_points") is not None else "",
|
||||
"是" if r.get("amp_hit") else ("否" if r.get("move_points") is not None else ""),
|
||||
"是" if r.get("amp_hit_2d") else ("否" if r.get("move_points") is not None and r.get("amplitude_2d") is not None else ""),
|
||||
]
|
||||
)
|
||||
return buf.getvalue()
|
||||
|
||||
@@ -102,6 +102,8 @@
|
||||
`<div><span class="amp-sum-k">最大振幅</span><span class="amp-sum-v">${esc(s.max_amplitude)} <small>(${esc(s.max_amplitude_day)})</small></span></div>` +
|
||||
`<div><span class="amp-sum-k">振幅均值</span><span class="amp-sum-v">${esc(s.avg_amplitude)}</span></div>` +
|
||||
`<div><span class="amp-sum-k">振幅中位</span><span class="amp-sum-v">${esc(s.median_amplitude)}</span></div>` +
|
||||
`<div><span class="amp-sum-k">两日最大振幅</span><span class="amp-sum-v">${esc(s.max_amplitude_2d)} <small>(${esc(s.max_amplitude_2d_day)})</small></span></div>` +
|
||||
`<div><span class="amp-sum-k">两日振幅均值/中位</span><span class="amp-sum-v">${esc(s.avg_amplitude_2d)} / ${esc(s.median_amplitude_2d)}</span></div>` +
|
||||
`<div><span class="amp-sum-k">开→高最大/均</span><span class="amp-sum-v">${esc(s.max_up_points)} / ${esc(s.avg_up_points)}</span></div>` +
|
||||
`<div><span class="amp-sum-k">开→低最大/均</span><span class="amp-sum-v">${esc(s.max_down_points)} / ${esc(s.avg_down_points)}</span></div>` +
|
||||
`<div><span class="amp-sum-k">涨/跌窗占比</span><span class="amp-sum-v">${esc(s.up_day_ratio)} / ${esc(s.down_day_ratio)}</span></div>` +
|
||||
@@ -121,6 +123,7 @@
|
||||
`<div class="amp-sum-grid">` +
|
||||
`<div><span class="amp-sum-k">对照点数</span><span class="amp-sum-v">${esc(ms.move_points)}</span></div>` +
|
||||
`<div><span class="amp-sum-k">振幅≥点数</span><span class="amp-sum-v">${esc(ms.amp_hit_days)} 天 · <strong>${esc(pct(ms.amp_hit_ratio))}</strong></span></div>` +
|
||||
`<div><span class="amp-sum-k">两日振幅≥点数</span><span class="amp-sum-v">${esc(ms.amp_2d_hit_days)} 天 · <strong>${esc(pct(ms.amp_2d_hit_ratio))}</strong></span></div>` +
|
||||
`<div><span class="amp-sum-k">开→高≥点数</span><span class="amp-sum-v">${esc(ms.up_hit_days)} 天 · ${esc(pct(ms.up_hit_ratio))}</span></div>` +
|
||||
`<div><span class="amp-sum-k">开→低≥点数</span><span class="amp-sum-v">${esc(ms.down_hit_days)} 天 · ${esc(pct(ms.down_hit_ratio))}</span></div>` +
|
||||
`<div><span class="amp-sum-k">|涨跌|≥点数</span><span class="amp-sum-v">${esc(ms.abs_change_hit_days)} 天 · ${esc(pct(ms.abs_change_hit_ratio))}</span></div>` +
|
||||
@@ -147,7 +150,7 @@
|
||||
if (!body) return;
|
||||
const rows = (pagePayload && pagePayload.rows) || [];
|
||||
if (!rows.length) {
|
||||
body.innerHTML = '<tr><td colspan="11" class="amp-empty">暂无数据</td></tr>';
|
||||
body.innerHTML = '<tr><td colspan="12" class="amp-empty">暂无数据</td></tr>';
|
||||
} else {
|
||||
body.innerHTML = rows
|
||||
.map((r) => {
|
||||
@@ -155,6 +158,11 @@
|
||||
const upCls = r.hit_up ? ' class="amp-pnl is-pos"' : "";
|
||||
const downCls = r.hit_down ? ' class="amp-pnl is-pos"' : "";
|
||||
const ampCls = r.amp_hit ? ' class="amp-pnl is-pos"' : "";
|
||||
const amp2Cls = r.amp_hit_2d ? ' class="amp-pnl is-pos"' : "";
|
||||
const amp2 =
|
||||
r.amplitude_2d == null || r.amplitude_2d === ""
|
||||
? "—"
|
||||
: `<span title="${esc(r.window2_start || "")} → ${esc(r.window2_end || "")}">${esc(r.amplitude_2d)}</span>`;
|
||||
return (
|
||||
`<tr${trClass}>` +
|
||||
`<td>${dayLabel(r)}</td>` +
|
||||
@@ -166,6 +174,7 @@
|
||||
`<td${upCls}>${esc(r.up_points)}</td>` +
|
||||
`<td${downCls}>${esc(r.down_points)}</td>` +
|
||||
`<td${ampCls}><strong>${esc(r.amplitude)}</strong></td>` +
|
||||
`<td${amp2Cls}><strong>${amp2}</strong></td>` +
|
||||
`<td>${esc(r.change)}</td>` +
|
||||
`<td>${hitCell(r)}</td>` +
|
||||
`</tr>`
|
||||
|
||||
@@ -1275,7 +1275,7 @@
|
||||
</div>
|
||||
</div>
|
||||
<p id="amp-status" class="toolbar-meta amp-status"></p>
|
||||
<p class="amp-hint">口径:开→高=最高−开盘;开→低=开盘−最低;振幅=最高−最低.填写波动点数后看振幅≥该点数的天数占比;日表显示两边波动(开→高/开→低)与振幅是否达标.周末按结算日标注/筛选.</p>
|
||||
<p class="amp-hint">口径:开→高=最高−开盘;开→低=开盘−最低;振幅=最高−最低.两日振幅=起点再往前推1天到当日16:00(例:25日16:00→27日16:00).填写波动点数后看振幅/两日振幅≥该点数的天数占比.周末按结算日标注/筛选.</p>
|
||||
<h3 class="amp-block-title">汇总</h3>
|
||||
<div id="amp-summary" class="amp-summary"></div>
|
||||
<h3 class="amp-block-title">振幅占比</h3>
|
||||
@@ -1286,11 +1286,11 @@
|
||||
<thead>
|
||||
<tr>
|
||||
<th>结算日</th><th>窗起点</th><th>开</th><th>高</th><th>低</th><th>收</th>
|
||||
<th>开→高</th><th>开→低</th><th>振幅</th><th>涨跌</th><th>振幅达标</th>
|
||||
<th>开→高</th><th>开→低</th><th>振幅</th><th>两日振幅</th><th>涨跌</th><th>振幅达标</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody id="amp-table-body">
|
||||
<tr><td colspan="11" class="amp-empty">点击「计算」加载</td></tr>
|
||||
<tr><td colspan="12" class="amp-empty">点击「计算」加载</td></tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
@@ -34,6 +34,14 @@ class AmpStatsLibTests(unittest.TestCase):
|
||||
self.assertEqual(start.strftime("%Y-%m-%d %H:%M"), "2026-07-22 08:00")
|
||||
self.assertEqual(end.strftime("%Y-%m-%d %H:%M"), "2026-07-22 16:00")
|
||||
|
||||
def test_window_two_day_16_to_16(self):
|
||||
# 结算 27 日 → 两日窗 25日16:00 → 27日16:00
|
||||
start, end = window_bounds_for_settlement(date(2026, 7, 27), 16, span_days=2)
|
||||
self.assertEqual(start.strftime("%Y-%m-%d %H:%M"), "2026-07-25 16:00")
|
||||
self.assertEqual(end.strftime("%Y-%m-%d %H:%M"), "2026-07-27 16:00")
|
||||
one_start, _ = window_bounds_for_settlement(date(2026, 7, 27), 16, span_days=1)
|
||||
self.assertEqual(one_start.strftime("%Y-%m-%d %H:%M"), "2026-07-26 16:00")
|
||||
|
||||
def test_settlement_excludes_incomplete_today(self):
|
||||
now = datetime(2026, 7, 22, 10, 0, tzinfo=TZ)
|
||||
days = list_settlement_dates(sample_days=3, now=now)
|
||||
|
||||
Reference in New Issue
Block a user