扩展指标
This commit is contained in:
@@ -0,0 +1,227 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import argparse
|
||||
import json
|
||||
import time
|
||||
from pathlib import Path
|
||||
from typing import Any
|
||||
|
||||
import numpy as np
|
||||
|
||||
import ferro_ta
|
||||
|
||||
try:
|
||||
from benchmarks.metadata import benchmark_metadata
|
||||
except ModuleNotFoundError: # pragma: no cover - script execution fallback
|
||||
from metadata import benchmark_metadata
|
||||
|
||||
|
||||
def _time_fn(fn, *args, rounds: int = 5, **kwargs) -> float:
|
||||
fn(*args, **kwargs)
|
||||
times: list[float] = []
|
||||
for _ in range(rounds):
|
||||
t0 = time.perf_counter()
|
||||
fn(*args, **kwargs)
|
||||
times.append(time.perf_counter() - t0)
|
||||
return min(times)
|
||||
|
||||
|
||||
def run_batch_benchmark(
|
||||
*,
|
||||
n_samples: int = 100_000,
|
||||
n_series: int = 100,
|
||||
seed: int = 42,
|
||||
) -> dict[str, Any]:
|
||||
rng = np.random.default_rng(seed)
|
||||
close2d = rng.uniform(100.0, 200.0, (n_samples, n_series))
|
||||
high2d = close2d + rng.uniform(0.1, 2.0, (n_samples, n_series))
|
||||
low2d = close2d - rng.uniform(0.1, 2.0, (n_samples, n_series))
|
||||
close1d = close2d[:, 0]
|
||||
high1d = high2d[:, 0]
|
||||
low1d = low2d[:, 0]
|
||||
|
||||
batch_rows: list[dict[str, Any]] = []
|
||||
grouped_rows: list[dict[str, Any]] = []
|
||||
|
||||
indicators = [
|
||||
(
|
||||
"SMA",
|
||||
lambda: ferro_ta.batch.batch_sma(close2d, timeperiod=14, parallel=True),
|
||||
lambda: ferro_ta.batch.batch_sma(close2d, timeperiod=14, parallel=False),
|
||||
lambda: [
|
||||
ferro_ta.SMA(close2d[:, j], timeperiod=14) for j in range(n_series)
|
||||
],
|
||||
),
|
||||
(
|
||||
"RSI",
|
||||
lambda: ferro_ta.batch.batch_rsi(close2d, timeperiod=14, parallel=True),
|
||||
lambda: ferro_ta.batch.batch_rsi(close2d, timeperiod=14, parallel=False),
|
||||
lambda: [
|
||||
ferro_ta.RSI(close2d[:, j], timeperiod=14) for j in range(n_series)
|
||||
],
|
||||
),
|
||||
(
|
||||
"ATR",
|
||||
lambda: ferro_ta.batch.batch_atr(
|
||||
high2d, low2d, close2d, timeperiod=14, parallel=True
|
||||
),
|
||||
lambda: ferro_ta.batch.batch_atr(
|
||||
high2d, low2d, close2d, timeperiod=14, parallel=False
|
||||
),
|
||||
lambda: [
|
||||
ferro_ta.ATR(high2d[:, j], low2d[:, j], close2d[:, j], timeperiod=14)
|
||||
for j in range(n_series)
|
||||
],
|
||||
),
|
||||
(
|
||||
"ADX",
|
||||
lambda: ferro_ta.batch.batch_adx(
|
||||
high2d, low2d, close2d, timeperiod=14, parallel=True
|
||||
),
|
||||
lambda: ferro_ta.batch.batch_adx(
|
||||
high2d, low2d, close2d, timeperiod=14, parallel=False
|
||||
),
|
||||
lambda: [
|
||||
ferro_ta.ADX(high2d[:, j], low2d[:, j], close2d[:, j], timeperiod=14)
|
||||
for j in range(n_series)
|
||||
],
|
||||
),
|
||||
]
|
||||
|
||||
for name, parallel_fn, sequential_fn, loop_fn in indicators:
|
||||
batch_parallel_s = _time_fn(parallel_fn)
|
||||
batch_sequential_s = _time_fn(sequential_fn)
|
||||
loop_s = _time_fn(loop_fn)
|
||||
batch_rows.append(
|
||||
{
|
||||
"indicator": name,
|
||||
"parallel_ms": round(batch_parallel_s * 1000, 4),
|
||||
"sequential_ms": round(batch_sequential_s * 1000, 4),
|
||||
"loop_ms": round(loop_s * 1000, 4),
|
||||
"parallel_speedup_vs_loop": round(loop_s / batch_parallel_s, 4),
|
||||
"sequential_speedup_vs_loop": round(loop_s / batch_sequential_s, 4),
|
||||
}
|
||||
)
|
||||
|
||||
grouped_cases = [
|
||||
(
|
||||
"close_bundle_3",
|
||||
lambda: ferro_ta.batch.compute_many(
|
||||
[
|
||||
("SMA", {"timeperiod": 10}),
|
||||
("EMA", {"timeperiod": 12}),
|
||||
("RSI", {"timeperiod": 14}),
|
||||
],
|
||||
close=close1d,
|
||||
),
|
||||
lambda: (
|
||||
ferro_ta.SMA(close1d, timeperiod=10),
|
||||
ferro_ta.EMA(close1d, timeperiod=12),
|
||||
ferro_ta.RSI(close1d, timeperiod=14),
|
||||
),
|
||||
),
|
||||
(
|
||||
"hlc_bundle_3",
|
||||
lambda: ferro_ta.batch.compute_many(
|
||||
[
|
||||
("ATR", {"timeperiod": 14}),
|
||||
("ADX", {"timeperiod": 14}),
|
||||
("CCI", {"timeperiod": 14}),
|
||||
],
|
||||
close=close1d,
|
||||
high=high1d,
|
||||
low=low1d,
|
||||
),
|
||||
lambda: (
|
||||
ferro_ta.ATR(high1d, low1d, close1d, timeperiod=14),
|
||||
ferro_ta.ADX(high1d, low1d, close1d, timeperiod=14),
|
||||
ferro_ta.CCI(high1d, low1d, close1d, timeperiod=14),
|
||||
),
|
||||
),
|
||||
]
|
||||
|
||||
for name, grouped_fn, separate_fn in grouped_cases:
|
||||
grouped_s = _time_fn(grouped_fn)
|
||||
separate_s = _time_fn(separate_fn)
|
||||
grouped_rows.append(
|
||||
{
|
||||
"case": name,
|
||||
"grouped_ms": round(grouped_s * 1000, 4),
|
||||
"separate_ms": round(separate_s * 1000, 4),
|
||||
"speedup_vs_separate": round(separate_s / grouped_s, 4),
|
||||
}
|
||||
)
|
||||
|
||||
return {
|
||||
"metadata": benchmark_metadata(
|
||||
"batch",
|
||||
extra={
|
||||
"dataset": {
|
||||
"n_samples": n_samples,
|
||||
"n_series": n_series,
|
||||
"total_bars": n_samples * n_series,
|
||||
"seed": seed,
|
||||
}
|
||||
},
|
||||
),
|
||||
"results": batch_rows,
|
||||
"grouped_results": grouped_rows,
|
||||
}
|
||||
|
||||
|
||||
def main() -> int:
|
||||
parser = argparse.ArgumentParser(description="Benchmark batch indicator execution.")
|
||||
parser.add_argument("--samples", type=int, default=100_000)
|
||||
parser.add_argument("--series", type=int, default=100)
|
||||
parser.add_argument("--seed", type=int, default=42)
|
||||
parser.add_argument("--json", dest="json_path")
|
||||
args = parser.parse_args()
|
||||
|
||||
payload = run_batch_benchmark(
|
||||
n_samples=args.samples,
|
||||
n_series=args.series,
|
||||
seed=args.seed,
|
||||
)
|
||||
|
||||
dataset = payload["metadata"]["dataset"]
|
||||
print(
|
||||
"Batch Benchmark: "
|
||||
f"{dataset['n_samples']} bars, {dataset['n_series']} series "
|
||||
f"(Total: {dataset['total_bars'] / 1e6:.1f} M bars)"
|
||||
)
|
||||
print("-" * 74)
|
||||
print(
|
||||
f"{'Indicator':<12} {'Parallel (ms)':>14} {'Sequential (ms)':>16} "
|
||||
f"{'Loop (ms)':>12} {'P speedup':>10}"
|
||||
)
|
||||
print("-" * 74)
|
||||
for row in payload["results"]:
|
||||
print(
|
||||
f"{row['indicator']:<12} {row['parallel_ms']:14.1f} "
|
||||
f"{row['sequential_ms']:16.1f} {row['loop_ms']:12.1f} "
|
||||
f"{row['parallel_speedup_vs_loop']:10.2f}x"
|
||||
)
|
||||
|
||||
if payload["grouped_results"]:
|
||||
print("\nGrouped Multi-Indicator Calls")
|
||||
print("-" * 64)
|
||||
print(
|
||||
f"{'Case':<18} {'Grouped (ms)':>14} {'Separate (ms)':>16} {'Speedup':>12}"
|
||||
)
|
||||
print("-" * 64)
|
||||
for row in payload["grouped_results"]:
|
||||
print(
|
||||
f"{row['case']:<18} {row['grouped_ms']:14.1f} "
|
||||
f"{row['separate_ms']:16.1f} {row['speedup_vs_separate']:12.2f}x"
|
||||
)
|
||||
|
||||
if args.json_path:
|
||||
json_path = Path(args.json_path)
|
||||
json_path.write_text(json.dumps(payload, indent=2), encoding="utf-8")
|
||||
print(f"\nWrote JSON results to {json_path}")
|
||||
|
||||
return 0
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
raise SystemExit(main())
|
||||
Reference in New Issue
Block a user