- Add agent/src/config/env_schema.py: Pydantic models for ~50 env vars across 6 sub-models (LLMConfig, DataConfig, APIConfig, SwarmConfig, AgentTuningConfig, PathConfig)
- Add agent/src/config/accessor.py: thread-safe lazy singleton get_env_config() + unified _parse_bool() + reset_env_config() for test isolation
- Fix TIMEOUT_SECONDS CLI display default (2400 -> 120, matching runtime)
- Fix MAX_RETRIES CLI display default ('(not set)' -> 2, matching runtime)
- Fix VIBE_TRADING_API_KEY server-side alias (security.py now resolves it via get_env_config().api.api_auth_key)
- Fix VIBE_TRADING_CHANNELS_AUTO_START=on silently ignored (unified _parse_bool accepts 'on')
- Migrate 207 scattered os.getenv/os.environ.get calls across 50+ files to centralized accessor
- Convert 12 module-level import-time fixations to lazy accessor pattern (loop.py, swarm_tool.py, _backend.py, worker.py, security.py)
- Add reset_env_config() calls after settings_routes.py env mutations (13 mutation points)
- Replace print('[WARN]...') with logger.warning() in tushare/okx/yfinance loaders
- Add tools/ci_env_var_gate.py: AST-based CI gate preventing new os.getenv calls outside src/config/
- Add tools/test_ci_env_var_gate.py: 18 test cases for the CI gate
- Update tools/ci_grep_gates.sh to call the new env var gate
- Add agent/tests/test_env_schema.py: 81 test cases covering defaults, type coercion, singleton, thread safety, bool parser
- Update agent/.env.example with schema cross-references
- Update agent/tests/conftest.py with reset_env_config autouse fixture
Closes HKUDS/Vibe-Trading#438
Co-Authored-By: OpenCode <noreply@opencode.ai>
AI-Model: alibaba-cn/glm-5.2
Signed-off-by: shadowinlife <shadowinlife@gmail.com>
Co-Authored-By: opencode <noreply@ai-tool.com>
Co-Authored-By: Claude Code <noreply@anthropic.com>
AI-Contributed/Feature: 1383/1383
AI-Contributed/UT: 735/735
134 lines
3.6 KiB
Python
134 lines
3.6 KiB
Python
"""Performance benchmark: compare old vs new operator/equity paths.
|
||
|
||
Development-only script — not included in the package.
|
||
Run: python agent/scripts/bench_performance.py
|
||
"""
|
||
|
||
from __future__ import annotations
|
||
|
||
import os
|
||
import sys
|
||
import time
|
||
|
||
import numpy as np
|
||
import pandas as pd
|
||
|
||
sys.path.insert(0, os.path.join(os.path.dirname(__file__), ".."))
|
||
|
||
|
||
def bench_operators():
|
||
"""Benchmark factor operators: old pandas vs new fast paths."""
|
||
from src.factors.base import decay_linear, ts_argmax, ts_argmin, ts_rank
|
||
|
||
np.random.seed(42)
|
||
df = pd.DataFrame(np.random.randn(5000, 100))
|
||
n = 20
|
||
|
||
print("=== Operator Benchmarks (5000 rows × 100 cols, window=20) ===\n")
|
||
|
||
ops = [
|
||
("ts_rank", lambda: ts_rank(df, n)),
|
||
("ts_argmax", lambda: ts_argmax(df, n)),
|
||
("ts_argmin", lambda: ts_argmin(df, n)),
|
||
("decay_linear", lambda: decay_linear(df, n)),
|
||
]
|
||
|
||
for name, fn in ops:
|
||
_ = fn()
|
||
t0 = time.perf_counter()
|
||
for _ in range(3):
|
||
fn()
|
||
elapsed = (time.perf_counter() - t0) / 3
|
||
print(f" {name:20s}: {elapsed:.3f}s")
|
||
|
||
print()
|
||
|
||
|
||
def bench_equity():
|
||
"""Benchmark _calc_equity: vectorized vs loop."""
|
||
from backtest.engines.base import BaseEngine
|
||
from backtest.models import Position
|
||
|
||
class _Stub(BaseEngine):
|
||
def can_execute(self, *a):
|
||
return True
|
||
|
||
def round_size(self, s, p):
|
||
return s
|
||
|
||
def calc_commission(self, *a):
|
||
return 0.0
|
||
|
||
def apply_slippage(self, p, d):
|
||
return p
|
||
|
||
np.random.seed(42)
|
||
n_symbols = 50
|
||
n_days = 1000
|
||
symbols = [f"SYM{i:03d}" for i in range(n_symbols)]
|
||
dates = pd.date_range("2020-01-01", periods=n_days, freq="B")
|
||
close_df = pd.DataFrame(
|
||
np.cumsum(np.random.randn(n_days, n_symbols), axis=0) + 100,
|
||
index=dates,
|
||
columns=symbols,
|
||
)
|
||
|
||
engine = _Stub({"initial_cash": 10_000_000})
|
||
engine.capital = 5_000_000
|
||
for i, sym in enumerate(symbols):
|
||
engine.positions[sym] = Position(
|
||
symbol=sym,
|
||
direction=1 if i % 2 == 0 else -1,
|
||
size=100.0 + i * 10,
|
||
entry_price=95.0 + i,
|
||
leverage=1.0,
|
||
entry_time=dates[0],
|
||
)
|
||
|
||
ts = dates[500]
|
||
|
||
_ = engine._calc_equity(close_df, ts)
|
||
t0 = time.perf_counter()
|
||
for _ in range(1000):
|
||
engine._calc_equity(close_df, ts)
|
||
vec_time = (time.perf_counter() - t0) / 1000
|
||
|
||
# Force loop path by monkey-patching
|
||
original_pnl = type(engine)._calc_pnl
|
||
|
||
def _loop_pnl(self, *a):
|
||
return original_pnl(self, *a)
|
||
|
||
type(engine)._calc_pnl = _loop_pnl
|
||
_ = engine._calc_equity(close_df, ts)
|
||
t0 = time.perf_counter()
|
||
for _ in range(1000):
|
||
engine._calc_equity(close_df, ts)
|
||
loop_time = (time.perf_counter() - t0) / 1000
|
||
type(engine)._calc_pnl = original_pnl
|
||
|
||
speedup = loop_time / vec_time if vec_time > 0 else float("inf")
|
||
|
||
print("=== Equity Calculation (50 positions, 1000 iterations) ===\n")
|
||
print(f" Vectorized: {vec_time * 1e6:.1f} µs/call")
|
||
print(f" Loop: {loop_time * 1e6:.1f} µs/call")
|
||
print(f" Speedup: {speedup:.1f}x")
|
||
print()
|
||
|
||
|
||
if __name__ == "__main__":
|
||
print("Vibe-Trading Performance Benchmark")
|
||
print("=" * 50)
|
||
print()
|
||
|
||
from src.factors._backend import HAS_BOTTLENECK
|
||
|
||
print(f"Bottleneck available: {HAS_BOTTLENECK}")
|
||
from src.config.accessor import get_env_config
|
||
|
||
print(f"VIBE_TRADING_DISABLE_BOTTLENECK: {get_env_config().agent_tuning.vibe_trading_disable_bottleneck}")
|
||
print()
|
||
|
||
bench_operators()
|
||
bench_equity()
|