test_search_cross_symbol_high_frequency_portfolios.py 3.0 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485
  1. import importlib.util
  2. import sys
  3. from pathlib import Path
  4. import pytest
  5. def load_module():
  6. path = Path(__file__).resolve().parents[1] / "scripts" / "search_cross_symbol_high_frequency_portfolios.py"
  7. spec = importlib.util.spec_from_file_location("search_cross_symbol_high_frequency_portfolios", path)
  8. assert spec is not None
  9. module = importlib.util.module_from_spec(spec)
  10. assert spec.loader is not None
  11. sys.modules[spec.name] = module
  12. spec.loader.exec_module(module)
  13. return module
  14. def test_add_cost_columns_records_net_metrics():
  15. module = load_module()
  16. row = {
  17. "total_return": 0.12,
  18. "annualized_return": 0.04,
  19. "max_drawdown": 0.20,
  20. "calmar": 0.20,
  21. }
  22. module.add_cost_columns(row, "maker_taker", 0.0021)
  23. assert row["cost_model"] == "maker_taker"
  24. assert row["roundtrip_cost_on_margin"] == pytest.approx(0.0021)
  25. assert row["net_total_return"] == pytest.approx(0.12)
  26. assert row["net_annualized_return"] == pytest.approx(0.04)
  27. assert row["net_max_drawdown"] == pytest.approx(0.20)
  28. assert row["net_calmar"] == pytest.approx(0.20)
  29. def test_risk_qualified_keeps_recent_horizons_per_cost_model():
  30. module = load_module()
  31. pandas = pytest.importorskip("pandas")
  32. rows = []
  33. for cost_model, ret_1y in (("maker_taker", 0.11), ("taker_taker", 0.07)):
  34. for horizon, total_return in (("full", 0.30), ("3y", 0.20), ("1y", ret_1y), ("6m", 0.08), ("3m", 0.06)):
  35. rows.append(
  36. {
  37. "cost_model": cost_model,
  38. "portfolio": "test-portfolio",
  39. "horizon": horizon,
  40. "total_return": total_return,
  41. "annualized_return": 0.05,
  42. "max_drawdown": 0.10,
  43. "calmar": 0.50,
  44. "trades_per_month": 20.0,
  45. }
  46. )
  47. qualified = module.risk_qualified(pandas.DataFrame(rows)).sort_values("cost_model").reset_index(drop=True)
  48. assert qualified["cost_model"].tolist() == ["maker_taker", "taker_taker"]
  49. assert qualified.loc[0, "ret_1y"] == pytest.approx(0.11)
  50. assert qualified.loc[1, "ret_1y"] == pytest.approx(0.07)
  51. def test_robust_survivors_requires_positive_all_horizons_and_calmar():
  52. module = load_module()
  53. pandas = pytest.importorskip("pandas")
  54. rows = []
  55. for portfolio, ret_3m in (("survivor", 0.04), ("reject", -0.01)):
  56. for horizon, total_return in (("full", 0.30), ("3y", 0.20), ("1y", 0.10), ("6m", 0.08), ("3m", ret_3m)):
  57. rows.append(
  58. {
  59. "cost_model": "maker_taker",
  60. "portfolio": portfolio,
  61. "horizon": horizon,
  62. "total_return": total_return,
  63. "annualized_return": 0.05,
  64. "max_drawdown": 0.10,
  65. "calmar": 0.50,
  66. "trades_per_month": 20.0,
  67. }
  68. )
  69. survivors = module.robust_survivors(pandas.DataFrame(rows))
  70. assert survivors["portfolio"].tolist() == ["survivor"]