mirror of
https://github.com/QuantEngines/fx_quant_engine.git
synced 2026-07-28 10:57:44 +00:00
Initial commit: fx_quant_engine and options_quant_engine scaffold
This commit is contained in:
@@ -0,0 +1,11 @@
|
||||
from pathlib import Path
|
||||
|
||||
from options_quant_engine.utils.config import load_all_configs
|
||||
|
||||
|
||||
def test_load_all_configs() -> None:
|
||||
root = Path(__file__).resolve().parents[1]
|
||||
cfg = load_all_configs(root / "config")
|
||||
assert "universe" in cfg
|
||||
assert "data_sources" in cfg
|
||||
assert cfg["data_sources"]["enabled_sources"]["nse"] is True
|
||||
@@ -0,0 +1,20 @@
|
||||
import os
|
||||
|
||||
import pytest
|
||||
|
||||
from options_quant_engine.ingestion.credentials import load_credentials
|
||||
|
||||
|
||||
def test_load_credentials_success(monkeypatch: pytest.MonkeyPatch) -> None:
|
||||
monkeypatch.setenv("BREEZE_API_KEY", "k")
|
||||
monkeypatch.setenv("BREEZE_API_SECRET", "s")
|
||||
creds = load_credentials("BREEZE", required=["API_KEY", "API_SECRET"])
|
||||
assert creds.get("api_key") == "k"
|
||||
assert creds.get("api_secret") == "s"
|
||||
|
||||
|
||||
def test_load_credentials_missing_required(monkeypatch: pytest.MonkeyPatch) -> None:
|
||||
monkeypatch.delenv("ZERODHA_API_KEY", raising=False)
|
||||
monkeypatch.delenv("ZERODHA_ACCESS_TOKEN", raising=False)
|
||||
with pytest.raises(RuntimeError):
|
||||
load_credentials("ZERODHA", required=["API_KEY", "ACCESS_TOKEN"])
|
||||
@@ -0,0 +1,46 @@
|
||||
from datetime import datetime, timedelta, timezone
|
||||
from pathlib import Path
|
||||
|
||||
from options_quant_engine import OptionsQuantEngine
|
||||
|
||||
|
||||
def test_engine_signal_schema() -> None:
|
||||
root = Path(__file__).resolve().parents[1]
|
||||
engine = OptionsQuantEngine(config_dir=root / "config")
|
||||
end = datetime.now(timezone.utc)
|
||||
start = end - timedelta(days=200)
|
||||
|
||||
out = engine.run_asset("USDINR", start, end)
|
||||
signal = out["signal"]
|
||||
|
||||
required = {
|
||||
"engine",
|
||||
"timestamp",
|
||||
"asset",
|
||||
"signal_type",
|
||||
"signal_direction",
|
||||
"signal_strength",
|
||||
"confidence",
|
||||
"regime",
|
||||
"expected_volatility",
|
||||
"risk_flags",
|
||||
"drivers",
|
||||
"recommended_action",
|
||||
"position_sizing_multiplier",
|
||||
}
|
||||
assert required.issubset(signal.keys())
|
||||
assert 0.0 <= signal["signal_strength"] <= 1.0
|
||||
assert 0.0 <= signal["confidence"] <= 1.0
|
||||
|
||||
|
||||
def test_engine_relative_value_schema() -> None:
|
||||
root = Path(__file__).resolve().parents[1]
|
||||
engine = OptionsQuantEngine(config_dir=root / "config")
|
||||
end = datetime.now(timezone.utc)
|
||||
start = end - timedelta(days=200)
|
||||
|
||||
out = engine.run_relative_value("USDINR", "EURINR", start, end)
|
||||
assert "hedge_ratio" in out
|
||||
assert "spread_zscore" in out
|
||||
assert "signal" in out
|
||||
assert 0.0 <= out["signal"]["signal_strength"] <= 1.0
|
||||
@@ -0,0 +1,23 @@
|
||||
import numpy as np
|
||||
import pandas as pd
|
||||
|
||||
from options_quant_engine.models.relative_value import RelativeValueModel
|
||||
|
||||
|
||||
def test_relative_value_model_outputs_hedge_ratio_and_score() -> None:
|
||||
idx = pd.date_range("2025-01-01", periods=120, freq="B")
|
||||
base = np.linspace(100, 110, len(idx))
|
||||
a = pd.Series(base + np.sin(np.arange(len(idx)) * 0.2), index=idx)
|
||||
b = pd.Series(base * 0.8 + np.cos(np.arange(len(idx)) * 0.2), index=idx)
|
||||
|
||||
fa = pd.DataFrame({"momentum_multi_horizon": 0.2, "carry_proxy": 0.01}, index=idx)
|
||||
fb = pd.DataFrame({"momentum_multi_horizon": -0.1, "carry_proxy": -0.02}, index=idx)
|
||||
|
||||
model = RelativeValueModel(lookback=60)
|
||||
rv = model.generate("USDINR", "EURINR", a, b, fa, fb)
|
||||
|
||||
assert 0.0 <= rv.score <= 1.0
|
||||
assert abs(rv.hedge_ratio) > 0.0
|
||||
assert rv.long_asset in {"USDINR", "EURINR"}
|
||||
assert rv.short_asset in {"USDINR", "EURINR"}
|
||||
assert "hedge_ratio" in rv.drivers
|
||||
@@ -0,0 +1,35 @@
|
||||
from datetime import datetime, timedelta, timezone
|
||||
|
||||
from options_quant_engine.ingestion.adapters import MockAdapter
|
||||
from options_quant_engine.ingestion.base import BaseDataAdapter
|
||||
from options_quant_engine.ingestion.router import DataSourceRouter
|
||||
|
||||
|
||||
class FailingAdapter(BaseDataAdapter):
|
||||
def fetch_price_data(self, asset, start, end):
|
||||
raise RuntimeError("fail")
|
||||
|
||||
def fetch_macro_data(self, key, start, end):
|
||||
raise RuntimeError("fail")
|
||||
|
||||
def fetch_rate_data(self, asset, start, end):
|
||||
raise RuntimeError("fail")
|
||||
|
||||
def health_check(self):
|
||||
return False
|
||||
|
||||
|
||||
def test_fallback_to_mock_when_primary_fails() -> None:
|
||||
cfg = {
|
||||
"enabled_sources": {"nse": True, "mock": True},
|
||||
"source_priority": {"default": ["nse", "mock"]},
|
||||
"asset_source_map": {"USDINR": ["nse", "mock"]},
|
||||
"reliability_tags": {"nse": "high", "mock": "low"},
|
||||
"latency_tags_ms": {"nse": 500, "mock": 1},
|
||||
}
|
||||
router = DataSourceRouter({"nse": FailingAdapter(), "mock": MockAdapter()}, cfg)
|
||||
end = datetime.now(timezone.utc)
|
||||
start = end - timedelta(days=30)
|
||||
out = router.fetch_price_data("USDINR", start, end)
|
||||
assert out.success is True
|
||||
assert out.source == "mock"
|
||||
Reference in New Issue
Block a user