Skip to content
Merged

Dev #40

Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 4 additions & 1 deletion core/results.py
Original file line number Diff line number Diff line change
Expand Up @@ -206,7 +206,10 @@ def initial_margin(self) -> np.ndarray:
def maintenance_margin(self) -> np.ndarray:
return self.accounting.maintenance_margin

def full_report(self, trading_days: int = 365) -> Dict:
def full_report(self, trading_days: int = 365, scope: str = "auto") -> Dict:
if str(scope).lower().strip() not in {"auto", "full"}:
raise ValueError("NativeEventScoreResult supports scope='auto' or scope='full'")

from ..metrics.performance import compute_performance_metrics

return compute_performance_metrics(
Expand Down
113 changes: 113 additions & 0 deletions quantbt_phase34_merge_gate.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,113 @@
from __future__ import annotations

import inspect

import numpy as np
import pandas as pd

import quantbt
from quantbt import EndpointConfig, OrderCommand, PreparedNativeEventStrategyRunner, QuantBTEndpoint
from quantbt.core.schema import OrderSide, OrderType, TimeInForce
from quantbt.optimization import ObjectiveResult, PreparedNativeEventStrategyEvaluator, ReportMetricObjective


class _GateStrategy:
def on_bar_close(self, context):
symbol = context.symbols[0]
if context.bar_index == 0:
return [
OrderCommand(
timestamp=context.timestamp,
symbol=symbol,
side=OrderSide.BUY,
order_type=OrderType.MARKET,
qty=1.0,
tif=TimeInForce.IOC,
order_id="entry",
)
]
if context.bar_index == 4 and context.positions[symbol] > 0.0:
return [
OrderCommand(
timestamp=context.timestamp,
symbol=symbol,
side=OrderSide.SELL,
order_type=OrderType.MARKET,
qty=abs(context.positions[symbol]),
tif=TimeInForce.IOC,
reduce_only=True,
order_id="exit",
)
]
return []


def _bars() -> pd.DataFrame:
idx = pd.date_range("2024-01-01", periods=12, freq="1h", tz="UTC")
close = pd.Series(100.0 + np.sin(np.arange(len(idx)) / 2.0), index=idx)
return pd.DataFrame(
{
"open": close,
"high": close + 2.0,
"low": close - 2.0,
"close": close,
"volume": 1_000.0,
},
index=idx,
)


def _assert(condition: bool, message: str) -> None:
if not condition:
raise AssertionError(message)


def main() -> None:
fields = EndpointConfig.__dataclass_fields__
_assert(
all(name in fields for name in ("reactive_kernel_mode", "audit_sink", "audit_sink_path")),
"EndpointConfig missing Phase 34 fields",
)
print("EndpointConfig fields: PASSED")

_assert(hasattr(QuantBTEndpoint, "prepare_native_event_strategy"), "prepare_native_event_strategy missing")
_assert(hasattr(quantbt, "PreparedNativeEventStrategyRunner"), "PreparedNativeEventStrategyRunner missing")
_assert(quantbt.PreparedNativeEventStrategyRunner is PreparedNativeEventStrategyRunner, "Prepared runner export mismatch")
print("Prepared endpoint API: PASSED")

endpoint = QuantBTEndpoint.native_event_strategy(initial_capital=10_000, leverage=10, use_funding=False)
prepared = endpoint.prepare_native_event_strategy(data=_bars(), symbols=["BTC"])
_assert(isinstance(prepared, PreparedNativeEventStrategyRunner), "prepared runner type mismatch")
score = prepared.score(_GateStrategy())
_assert(score.metadata["reactive_kernel_mode"] == "single_pass", "prepared score did not use single_pass")
print("prepared.score(): PASSED")

signature = inspect.signature(score.full_report)
_assert("scope" in signature.parameters, "NativeEventScoreResult.full_report missing scope")
score.full_report(scope="auto")
print("Score/full_report signature: PASSED")

def strategy_factory(_params):
return _GateStrategy()

def objective_builder(result, params):
objective = ReportMetricObjective(value_metrics=("sharpe",), scope="auto")
return objective(result, params)

evaluator = PreparedNativeEventStrategyEvaluator(
runner=prepared,
strategy_factory=strategy_factory,
objective_builder=objective_builder,
)
objective = evaluator.evaluate({})
_assert(isinstance(objective, ObjectiveResult), "Prepared evaluator did not return ObjectiveResult")
print("Prepared evaluator import: PASSED")

objective = ReportMetricObjective(value_metrics=("sharpe",), scope="auto")(score, {})
_assert(isinstance(objective, ObjectiveResult), "ReportMetricObjective(score) failed")
print("ReportMetricObjective(score): PASSED")
print("PHASE 34 MERGE GATE: PASSED")


if __name__ == "__main__":
main()
33 changes: 31 additions & 2 deletions tests/test_phase34b_native_event_prepared_score.py
Original file line number Diff line number Diff line change
@@ -1,12 +1,15 @@
from __future__ import annotations

import inspect

import numpy as np
import pandas as pd

from quantbt import QuantBTEndpoint
import quantbt
from quantbt import EndpointConfig, PreparedNativeEventStrategyRunner, QuantBTEndpoint
from quantbt.core.orders import OrderCommand
from quantbt.core.schema import OrderSide, OrderType, TimeInForce
from quantbt.optimization import ObjectiveResult, PreparedNativeEventStrategyEvaluator
from quantbt.optimization import ObjectiveResult, PreparedNativeEventStrategyEvaluator, ReportMetricObjective


def _bars(n: int = 16) -> pd.DataFrame:
Expand Down Expand Up @@ -141,3 +144,29 @@ def objective_builder(result, params):
assert isinstance(objective, ObjectiveResult)
assert evaluator.last_result.metadata["engine"] == "event_v2_reactive_score"
assert prepared.metadata["scores"] == 1


def test_public_native_event_phase34_contract_is_available_from_quantbt():
fields = EndpointConfig.__dataclass_fields__

assert hasattr(quantbt.QuantBTEndpoint, "prepare_native_event_strategy")
assert hasattr(quantbt, "PreparedNativeEventStrategyRunner")
assert PreparedNativeEventStrategyRunner is quantbt.PreparedNativeEventStrategyRunner
assert "reactive_kernel_mode" in fields
assert "audit_sink" in fields
assert "audit_sink_path" in fields


def test_report_metric_objective_accepts_native_event_score_result_scope_contract():
df = _bars()
endpoint = QuantBTEndpoint.native_event_strategy(initial_capital=10_000, leverage=10, use_funding=False)
prepared = endpoint.prepare_native_event_strategy(data=df, symbols=["BTC"])
score_result = prepared.score(TwoTradeStrategy(entry_bar=0, exit_bar=5))

assert "scope" in inspect.signature(score_result.full_report).parameters
objective = ReportMetricObjective(value_metrics=("sharpe",), scope="auto")
result = objective(score_result, {"entry_bar": 0, "exit_bar": 5})

assert isinstance(result, ObjectiveResult)
assert result.values == (score_result.metrics["sharpe"],)
assert result.metrics["num_trades"] == score_result.metrics["num_trades"]
40 changes: 40 additions & 0 deletions upgrade/implement.md
Original file line number Diff line number Diff line change
Expand Up @@ -6910,3 +6910,43 @@ Scope note:
- Benchmark report records wall time, CPU time, peak RSS, Python heap peak,
NumPy allocated bytes, object count, ledger bytes, command count, fill count,
report construction time, and stage timings.

Merge regression fix on `dev`:

- Cherry-picked Phase 34A, 34B, and 34C onto `dev` after detecting that the
native-event public endpoint integration was missing from the research
branch.
- Restored public exports and endpoint contracts:
- `PreparedNativeEventStrategyRunner`;
- `QuantBTEndpoint.prepare_native_event_strategy(...)`;
- `EndpointConfig.reactive_kernel_mode`;
- `EndpointConfig.audit_sink`;
- `EndpointConfig.audit_sink_path`.
- Added `scope="auto"` compatibility to
`NativeEventScoreResult.full_report(...)`, matching the public result
contract expected by `ReportMetricObjective`.
- Added `quantbt_phase34_merge_gate.py` so future merges can verify public
Phase 34 integration directly.
- Added regression tests covering:
- public `quantbt` imports;
- endpoint Phase 34 fields;
- prepared native-event runner availability;
- `prepared.score(...)`;
- `PreparedNativeEventStrategyEvaluator`;
- `ReportMetricObjective(score_result)`.

Validation on `dev`:

```bash
MPLCONFIGDIR=/tmp PYTHONPATH=/root/bobby/pool_alpha poetry run python3 quantbt_phase34_merge_gate.py
# PHASE 34 MERGE GATE: PASSED

MPLCONFIGDIR=/tmp PYTHONPATH=/root/bobby/pool_alpha poetry run pytest -q \
quantbt/tests/test_phase34a_native_event_artifacts.py \
quantbt/tests/test_phase34b_native_event_prepared_score.py \
quantbt/tests/test_phase34c_native_event_single_pass.py
# 11 passed

MPLCONFIGDIR=/tmp PYTHONPATH=/root/bobby/pool_alpha poetry run pytest -q quantbt/tests
# 549 passed, 1 skipped
```
Loading