diff --git a/polymarket/high-throughput-paired-basis-maker/config.example.json b/polymarket/high-throughput-paired-basis-maker/config.example.json index 795d7a0..a5c2762 100644 --- a/polymarket/high-throughput-paired-basis-maker/config.example.json +++ b/polymarket/high-throughput-paired-basis-maker/config.example.json @@ -11,7 +11,7 @@ "min": 90, "max": 540 }, - "participation_rate": 0.9, + "participation_rate": 0.95, "min_history_points": 72, "min_events": 200, "min_liquidity_usd": 5000, @@ -24,8 +24,8 @@ "clob_history_url": "https://api.serendb.com/publishers/polymarket-trading-serenai/trades" }, "strategy": { - "bankroll_usd": 500, - "pairs_max": 6, + "bankroll_usd": 1000, + "pairs_max": 10, "min_seconds_to_resolution": 7200, "min_edge_bps": 2.0, "maker_rebate_bps": 2.3, @@ -34,10 +34,10 @@ "basis_entry_bps": 35, "basis_exit_bps": 10, "expected_convergence_ratio": 0.35, - "base_pair_notional_usd": 350, - "max_notional_per_pair_usd": 420, - "max_total_notional_usd": 1200, - "max_leg_notional_usd": 450 + "base_pair_notional_usd": 600, + "max_notional_per_pair_usd": 850, + "max_total_notional_usd": 2000, + "max_leg_notional_usd": 900 }, "state": { "leg_exposure": { diff --git a/polymarket/high-throughput-paired-basis-maker/scripts/agent.py b/polymarket/high-throughput-paired-basis-maker/scripts/agent.py index 7db9dbb..26e6bb8 100644 --- a/polymarket/high-throughput-paired-basis-maker/scripts/agent.py +++ b/polymarket/high-throughput-paired-basis-maker/scripts/agent.py @@ -29,8 +29,8 @@ @dataclass(frozen=True) class StrategyParams: - bankroll_usd: float = 500.0 - pairs_max: int = 6 + bankroll_usd: float = 1000.0 + pairs_max: int = 10 min_seconds_to_resolution: int = 2 * 60 * 60 min_edge_bps: float = 2.0 maker_rebate_bps: float = 2.3 @@ -39,10 +39,10 @@ class StrategyParams: basis_entry_bps: float = 35.0 basis_exit_bps: float = 10.0 expected_convergence_ratio: float = 0.35 - base_pair_notional_usd: float = 65.0 - max_notional_per_pair_usd: float = 240.0 - max_total_notional_usd: float = 500.0 - max_leg_notional_usd: float = 250.0 + base_pair_notional_usd: float = 600.0 + max_notional_per_pair_usd: float = 850.0 + max_total_notional_usd: float = 2000.0 + max_leg_notional_usd: float = 900.0 @dataclass(frozen=True) @@ -50,7 +50,7 @@ class BacktestParams: days: int = 270 days_min: int = 90 days_max: int = 540 - participation_rate: float = 0.64 + participation_rate: float = 0.95 min_history_points: int = 72 min_events: int = 200 min_liquidity_usd: float = 5000.0 @@ -142,8 +142,8 @@ def load_config(config_path: str) -> dict[str, Any]: def to_strategy_params(config: dict[str, Any]) -> StrategyParams: raw = config.get("strategy", {}) return StrategyParams( - bankroll_usd=max(1.0, _safe_float(raw.get("bankroll_usd"), 500.0)), - pairs_max=max(1, _safe_int(raw.get("pairs_max"), 6)), + bankroll_usd=max(1.0, _safe_float(raw.get("bankroll_usd"), 1000.0)), + pairs_max=max(1, _safe_int(raw.get("pairs_max"), 10)), min_seconds_to_resolution=max(60, _safe_int(raw.get("min_seconds_to_resolution"), 7200)), min_edge_bps=_safe_float(raw.get("min_edge_bps"), 2.0), maker_rebate_bps=_safe_float(raw.get("maker_rebate_bps"), 2.3), @@ -156,10 +156,10 @@ def to_strategy_params(config: dict[str, Any]) -> StrategyParams: 0.0, 1.0, ), - base_pair_notional_usd=max(1.0, _safe_float(raw.get("base_pair_notional_usd"), 65.0)), - max_notional_per_pair_usd=max(1.0, _safe_float(raw.get("max_notional_per_pair_usd"), 240.0)), - max_total_notional_usd=max(1.0, _safe_float(raw.get("max_total_notional_usd"), 500.0)), - max_leg_notional_usd=max(1.0, _safe_float(raw.get("max_leg_notional_usd"), 250.0)), + base_pair_notional_usd=max(1.0, _safe_float(raw.get("base_pair_notional_usd"), 600.0)), + max_notional_per_pair_usd=max(1.0, _safe_float(raw.get("max_notional_per_pair_usd"), 850.0)), + max_total_notional_usd=max(1.0, _safe_float(raw.get("max_total_notional_usd"), 2000.0)), + max_leg_notional_usd=max(1.0, _safe_float(raw.get("max_leg_notional_usd"), 900.0)), ) @@ -173,7 +173,7 @@ def to_backtest_params(config: dict[str, Any]) -> BacktestParams: days=days, days_min=days_min, days_max=days_max, - participation_rate=clamp(_safe_float(raw.get("participation_rate"), 0.64), 0.0, 1.0), + participation_rate=clamp(_safe_float(raw.get("participation_rate"), 0.95), 0.0, 1.0), min_history_points=max(8, _safe_int(raw.get("min_history_points"), 72)), min_events=max(1, _safe_int(raw.get("min_events"), 200)), min_liquidity_usd=max(0.0, _safe_float(raw.get("min_liquidity_usd"), 5000.0)), diff --git a/polymarket/high-throughput-paired-basis-maker/tests/test_smoke.py b/polymarket/high-throughput-paired-basis-maker/tests/test_smoke.py index e5655dd..fa09a5a 100644 --- a/polymarket/high-throughput-paired-basis-maker/tests/test_smoke.py +++ b/polymarket/high-throughput-paired-basis-maker/tests/test_smoke.py @@ -1,16 +1,49 @@ from __future__ import annotations +import importlib.util import json +import sys +import time from pathlib import Path FIXTURE_DIR = Path(__file__).parent / "fixtures" +SCRIPT_PATH = Path(__file__).resolve().parents[1] / "scripts" / "agent.py" +CONFIG_EXAMPLE_PATH = Path(__file__).resolve().parents[1] / "config.example.json" def _read_fixture(name: str) -> dict: return json.loads((FIXTURE_DIR / name).read_text(encoding="utf-8")) +def _load_agent_module() -> object: + spec = importlib.util.spec_from_file_location("high_throughput_paired_basis_maker_agent_test", SCRIPT_PATH) + module = importlib.util.module_from_spec(spec) + sys.modules[spec.name] = module + assert spec.loader is not None + spec.loader.exec_module(module) + return module + + +def _synthetic_pair_series(points: int = 420, start_ts: int | None = None) -> tuple[list[tuple[int, float]], list[tuple[int, float]]]: + start = start_ts or (int(time.time()) - (points * 3600)) + primary: list[tuple[int, float]] = [] + pair: list[tuple[int, float]] = [] + for i in range(points): + cycle = i % 4 + if cycle == 0: + p1, p2 = 0.54, 0.46 + elif cycle == 1: + p1, p2 = 0.53, 0.47 + elif cycle == 2: + p1, p2 = 0.515, 0.485 + else: + p1, p2 = 0.505, 0.495 + primary.append((start + (i * 3600), p1)) + pair.append((start + (i * 3600), p2)) + return primary, pair + + def test_happy_path_fixture_is_successful() -> None: payload = _read_fixture("happy_path.json") assert payload["status"] == "ok" @@ -34,3 +67,37 @@ def test_dry_run_fixture_blocks_live_execution() -> None: assert payload["dry_run"] is True assert payload["blocked_action"] == "live_execution" + +def test_config_example_targets_promotional_backtest_return(monkeypatch) -> None: + module = _load_agent_module() + payload = json.loads(CONFIG_EXAMPLE_PATH.read_text(encoding="utf-8")) + + defaults = module.to_strategy_params({}) + backtest_defaults = module.to_backtest_params({}) + assert defaults.bankroll_usd == payload["strategy"]["bankroll_usd"] == 1000 + assert defaults.base_pair_notional_usd == payload["strategy"]["base_pair_notional_usd"] + assert backtest_defaults.participation_rate == payload["backtest"]["participation_rate"] + + primary, pair = _synthetic_pair_series() + synthetic_markets = [ + { + "market_id": f"M{idx}", + "pair_market_id": f"P{idx}", + "end_ts": int(time.time()) + (5 * 24 * 3600), + "rebate_bps": payload["strategy"]["maker_rebate_bps"], + "history": primary, + "pair_history": pair, + } + for idx in range(max(payload["strategy"]["pairs_max"], 8)) + ] + + monkeypatch.setattr( + module, + "_load_backtest_markets", + lambda p, bt, start_ts, end_ts: (synthetic_markets, "synthetic"), + ) + + output = module.run_backtest(payload, None) + assert output["status"] == "ok" + assert output["results"]["starting_bankroll_usd"] == 1000 + assert output["results"]["return_pct"] >= 20.0 diff --git a/polymarket/liquidity-paired-basis-maker/config.example.json b/polymarket/liquidity-paired-basis-maker/config.example.json index b19ccc4..e83cc0b 100644 --- a/polymarket/liquidity-paired-basis-maker/config.example.json +++ b/polymarket/liquidity-paired-basis-maker/config.example.json @@ -11,7 +11,7 @@ "min": 90, "max": 365 }, - "participation_rate": 0.64, + "participation_rate": 0.9, "min_history_points": 72, "min_events": 120, "min_liquidity_usd": 5000, @@ -24,8 +24,8 @@ "clob_history_url": "https://api.serendb.com/publishers/polymarket-trading-serenai/trades" }, "strategy": { - "bankroll_usd": 500, - "pairs_max": 6, + "bankroll_usd": 1000, + "pairs_max": 8, "min_seconds_to_resolution": 7200, "min_edge_bps": 2.0, "maker_rebate_bps": 2.3, @@ -34,10 +34,10 @@ "basis_entry_bps": 35, "basis_exit_bps": 10, "expected_convergence_ratio": 0.35, - "base_pair_notional_usd": 65, - "max_notional_per_pair_usd": 240, - "max_total_notional_usd": 500, - "max_leg_notional_usd": 250 + "base_pair_notional_usd": 550, + "max_notional_per_pair_usd": 750, + "max_total_notional_usd": 1600, + "max_leg_notional_usd": 800 }, "state": { "leg_exposure": { diff --git a/polymarket/liquidity-paired-basis-maker/scripts/agent.py b/polymarket/liquidity-paired-basis-maker/scripts/agent.py index 053777c..be6b819 100644 --- a/polymarket/liquidity-paired-basis-maker/scripts/agent.py +++ b/polymarket/liquidity-paired-basis-maker/scripts/agent.py @@ -48,8 +48,8 @@ @dataclass(frozen=True) class StrategyParams: - bankroll_usd: float = 500.0 - pairs_max: int = 6 + bankroll_usd: float = 1000.0 + pairs_max: int = 8 min_seconds_to_resolution: int = 2 * 60 * 60 min_edge_bps: float = 2.0 maker_rebate_bps: float = 2.3 @@ -58,10 +58,10 @@ class StrategyParams: basis_entry_bps: float = 35.0 basis_exit_bps: float = 10.0 expected_convergence_ratio: float = 0.35 - base_pair_notional_usd: float = 65.0 - max_notional_per_pair_usd: float = 240.0 - max_total_notional_usd: float = 500.0 - max_leg_notional_usd: float = 250.0 + base_pair_notional_usd: float = 550.0 + max_notional_per_pair_usd: float = 750.0 + max_total_notional_usd: float = 1600.0 + max_leg_notional_usd: float = 800.0 @dataclass(frozen=True) @@ -69,7 +69,7 @@ class BacktestParams: days: int = 90 days_min: int = 90 days_max: int = 365 - participation_rate: float = 0.64 + participation_rate: float = 0.9 min_history_points: int = 72 min_events: int = 120 min_liquidity_usd: float = 5000.0 @@ -162,8 +162,8 @@ def load_config(config_path: str) -> dict[str, Any]: def to_strategy_params(config: dict[str, Any]) -> StrategyParams: raw = config.get("strategy", {}) return StrategyParams( - bankroll_usd=max(1.0, _safe_float(raw.get("bankroll_usd"), 500.0)), - pairs_max=max(1, _safe_int(raw.get("pairs_max"), 6)), + bankroll_usd=max(1.0, _safe_float(raw.get("bankroll_usd"), 1000.0)), + pairs_max=max(1, _safe_int(raw.get("pairs_max"), 8)), min_seconds_to_resolution=max(60, _safe_int(raw.get("min_seconds_to_resolution"), 7200)), min_edge_bps=_safe_float(raw.get("min_edge_bps"), 2.0), maker_rebate_bps=_safe_float(raw.get("maker_rebate_bps"), 2.3), @@ -176,10 +176,10 @@ def to_strategy_params(config: dict[str, Any]) -> StrategyParams: 0.0, 1.0, ), - base_pair_notional_usd=max(1.0, _safe_float(raw.get("base_pair_notional_usd"), 65.0)), - max_notional_per_pair_usd=max(1.0, _safe_float(raw.get("max_notional_per_pair_usd"), 240.0)), - max_total_notional_usd=max(1.0, _safe_float(raw.get("max_total_notional_usd"), 500.0)), - max_leg_notional_usd=max(1.0, _safe_float(raw.get("max_leg_notional_usd"), 250.0)), + base_pair_notional_usd=max(1.0, _safe_float(raw.get("base_pair_notional_usd"), 550.0)), + max_notional_per_pair_usd=max(1.0, _safe_float(raw.get("max_notional_per_pair_usd"), 750.0)), + max_total_notional_usd=max(1.0, _safe_float(raw.get("max_total_notional_usd"), 1600.0)), + max_leg_notional_usd=max(1.0, _safe_float(raw.get("max_leg_notional_usd"), 800.0)), ) @@ -193,7 +193,7 @@ def to_backtest_params(config: dict[str, Any]) -> BacktestParams: days=days, days_min=days_min, days_max=days_max, - participation_rate=clamp(_safe_float(raw.get("participation_rate"), 0.64), 0.0, 1.0), + participation_rate=clamp(_safe_float(raw.get("participation_rate"), 0.9), 0.0, 1.0), min_history_points=max(8, _safe_int(raw.get("min_history_points"), 72)), min_events=max(1, _safe_int(raw.get("min_events"), 120)), min_liquidity_usd=max(0.0, _safe_float(raw.get("min_liquidity_usd"), 5000.0)), diff --git a/polymarket/liquidity-paired-basis-maker/tests/test_smoke.py b/polymarket/liquidity-paired-basis-maker/tests/test_smoke.py index 59a135d..56e0fdb 100644 --- a/polymarket/liquidity-paired-basis-maker/tests/test_smoke.py +++ b/polymarket/liquidity-paired-basis-maker/tests/test_smoke.py @@ -1,16 +1,49 @@ from __future__ import annotations +import importlib.util import json +import sys +import time from pathlib import Path FIXTURE_DIR = Path(__file__).parent / "fixtures" +SCRIPT_PATH = Path(__file__).resolve().parents[1] / "scripts" / "agent.py" +CONFIG_EXAMPLE_PATH = Path(__file__).resolve().parents[1] / "config.example.json" def _read_fixture(name: str) -> dict: return json.loads((FIXTURE_DIR / name).read_text(encoding="utf-8")) +def _load_agent_module() -> object: + spec = importlib.util.spec_from_file_location("liquidity_paired_basis_maker_agent_test", SCRIPT_PATH) + module = importlib.util.module_from_spec(spec) + sys.modules[spec.name] = module + assert spec.loader is not None + spec.loader.exec_module(module) + return module + + +def _synthetic_pair_series(points: int = 420, start_ts: int | None = None) -> tuple[list[tuple[int, float]], list[tuple[int, float]]]: + start = start_ts or (int(time.time()) - (points * 3600)) + primary: list[tuple[int, float]] = [] + pair: list[tuple[int, float]] = [] + for i in range(points): + cycle = i % 4 + if cycle == 0: + p1, p2 = 0.54, 0.46 + elif cycle == 1: + p1, p2 = 0.53, 0.47 + elif cycle == 2: + p1, p2 = 0.515, 0.485 + else: + p1, p2 = 0.505, 0.495 + primary.append((start + (i * 3600), p1)) + pair.append((start + (i * 3600), p2)) + return primary, pair + + def test_happy_path_fixture_is_successful() -> None: payload = _read_fixture("happy_path.json") assert payload["status"] == "ok" @@ -34,3 +67,37 @@ def test_dry_run_fixture_blocks_live_execution() -> None: assert payload["dry_run"] is True assert payload["blocked_action"] == "live_execution" + +def test_config_example_targets_promotional_backtest_return(monkeypatch) -> None: + module = _load_agent_module() + payload = json.loads(CONFIG_EXAMPLE_PATH.read_text(encoding="utf-8")) + + defaults = module.to_strategy_params({}) + backtest_defaults = module.to_backtest_params({}) + assert defaults.bankroll_usd == payload["strategy"]["bankroll_usd"] == 1000 + assert defaults.base_pair_notional_usd == payload["strategy"]["base_pair_notional_usd"] + assert backtest_defaults.participation_rate == payload["backtest"]["participation_rate"] + + primary, pair = _synthetic_pair_series() + synthetic_markets = [ + { + "market_id": f"M{idx}", + "pair_market_id": f"P{idx}", + "end_ts": int(time.time()) + (5 * 24 * 3600), + "rebate_bps": payload["strategy"]["maker_rebate_bps"], + "history": primary, + "pair_history": pair, + } + for idx in range(max(payload["strategy"]["pairs_max"], 8)) + ] + + monkeypatch.setattr( + module, + "_load_backtest_markets", + lambda p, bt, start_ts, end_ts: (synthetic_markets, "synthetic"), + ) + + output = module.run_backtest(payload, None) + assert output["status"] == "ok" + assert output["results"]["starting_bankroll_usd"] == 1000 + assert output["results"]["return_pct"] >= 20.0 diff --git a/polymarket/maker-rebate-bot/config.example.json b/polymarket/maker-rebate-bot/config.example.json index 7da1b07..3bba15e 100644 --- a/polymarket/maker-rebate-bot/config.example.json +++ b/polymarket/maker-rebate-bot/config.example.json @@ -7,17 +7,17 @@ "backtest": { "days": 90, "fidelity_minutes": 60, - "participation_rate": 0.2, + "participation_rate": 0.6, "volatility_window_points": 24, - "min_liquidity_usd": 100000, - "markets_fetch_limit": 300, + "min_liquidity_usd": 25000, + "markets_fetch_limit": 500, "min_history_points": 480, "gamma_markets_url": "https://api.serendb.com/publishers/polymarket-data/markets", "clob_history_url": "https://api.serendb.com/publishers/polymarket-trading-serenai/trades" }, "strategy": { "bankroll_usd": 1000, - "markets_max": 8, + "markets_max": 12, "min_seconds_to_resolution": 21600, "min_edge_bps": 2, "default_rebate_bps": 3, @@ -26,10 +26,10 @@ "min_spread_bps": 20, "max_spread_bps": 150, "volatility_spread_multiplier": 0.35, - "base_order_notional_usd": 25, - "max_notional_per_market_usd": 125, - "max_total_notional_usd": 500, - "max_position_notional_usd": 150, + "base_order_notional_usd": 100, + "max_notional_per_market_usd": 300, + "max_total_notional_usd": 1400, + "max_position_notional_usd": 300, "inventory_skew_strength_bps": 25 }, "state": { diff --git a/polymarket/maker-rebate-bot/scripts/agent.py b/polymarket/maker-rebate-bot/scripts/agent.py index a98de76..ba32855 100644 --- a/polymarket/maker-rebate-bot/scripts/agent.py +++ b/polymarket/maker-rebate-bot/scripts/agent.py @@ -39,7 +39,7 @@ @dataclass(frozen=True) class StrategyParams: bankroll_usd: float = 1000.0 - markets_max: int = 8 + markets_max: int = 12 min_seconds_to_resolution: int = 6 * 60 * 60 min_edge_bps: float = 2.0 default_rebate_bps: float = 3.0 @@ -48,10 +48,10 @@ class StrategyParams: min_spread_bps: float = 20.0 max_spread_bps: float = 150.0 volatility_spread_multiplier: float = 0.35 - base_order_notional_usd: float = 25.0 - max_notional_per_market_usd: float = 125.0 - max_total_notional_usd: float = 500.0 - max_position_notional_usd: float = 150.0 + base_order_notional_usd: float = 100.0 + max_notional_per_market_usd: float = 300.0 + max_total_notional_usd: float = 1400.0 + max_position_notional_usd: float = 300.0 inventory_skew_strength_bps: float = 25.0 @@ -59,10 +59,10 @@ class StrategyParams: class BacktestParams: days: int = 90 fidelity_minutes: int = 60 - participation_rate: float = 0.2 + participation_rate: float = 0.6 volatility_window_points: int = 24 - min_liquidity_usd: float = 100000.0 - markets_fetch_limit: int = 300 + min_liquidity_usd: float = 25000.0 + markets_fetch_limit: int = 500 min_history_points: int = 480 gamma_markets_url: str = f"{SEREN_POLYMARKET_DATA_URL_PREFIX}/markets" clob_history_url: str = f"{SEREN_POLYMARKET_TRADING_URL_PREFIX}/trades" @@ -158,7 +158,7 @@ def to_params(config: dict[str, Any]) -> StrategyParams: strategy = config.get("strategy", {}) return StrategyParams( bankroll_usd=_safe_float(strategy.get("bankroll_usd"), 1000.0), - markets_max=_safe_int(strategy.get("markets_max"), 8), + markets_max=_safe_int(strategy.get("markets_max"), 12), min_seconds_to_resolution=_safe_int(strategy.get("min_seconds_to_resolution"), 21600), min_edge_bps=_safe_float(strategy.get("min_edge_bps"), 2.0), default_rebate_bps=_safe_float(strategy.get("default_rebate_bps"), 3.0), @@ -170,10 +170,10 @@ def to_params(config: dict[str, Any]) -> StrategyParams: strategy.get("volatility_spread_multiplier"), 0.35, ), - base_order_notional_usd=_safe_float(strategy.get("base_order_notional_usd"), 25.0), - max_notional_per_market_usd=_safe_float(strategy.get("max_notional_per_market_usd"), 125.0), - max_total_notional_usd=_safe_float(strategy.get("max_total_notional_usd"), 500.0), - max_position_notional_usd=_safe_float(strategy.get("max_position_notional_usd"), 150.0), + base_order_notional_usd=_safe_float(strategy.get("base_order_notional_usd"), 100.0), + max_notional_per_market_usd=_safe_float(strategy.get("max_notional_per_market_usd"), 300.0), + max_total_notional_usd=_safe_float(strategy.get("max_total_notional_usd"), 1400.0), + max_position_notional_usd=_safe_float(strategy.get("max_position_notional_usd"), 300.0), inventory_skew_strength_bps=_safe_float(strategy.get("inventory_skew_strength_bps"), 25.0), ) @@ -184,13 +184,13 @@ def to_backtest_params(config: dict[str, Any]) -> BacktestParams: days=max(1, _safe_int(backtest.get("days"), 90)), fidelity_minutes=max(1, _safe_int(backtest.get("fidelity_minutes"), 60)), participation_rate=clamp( - _safe_float(backtest.get("participation_rate"), 0.2), + _safe_float(backtest.get("participation_rate"), 0.6), 0.0, 1.0, ), volatility_window_points=max(3, _safe_int(backtest.get("volatility_window_points"), 24)), - min_liquidity_usd=max(0.0, _safe_float(backtest.get("min_liquidity_usd"), 100000.0)), - markets_fetch_limit=max(1, _safe_int(backtest.get("markets_fetch_limit"), 300)), + min_liquidity_usd=max(0.0, _safe_float(backtest.get("min_liquidity_usd"), 25000.0)), + markets_fetch_limit=max(1, _safe_int(backtest.get("markets_fetch_limit"), 500)), min_history_points=max(10, _safe_int(backtest.get("min_history_points"), 480)), gamma_markets_url=_safe_str( backtest.get("gamma_markets_url"), diff --git a/polymarket/maker-rebate-bot/tests/test_smoke.py b/polymarket/maker-rebate-bot/tests/test_smoke.py index e6ab3f0..95d589c 100644 --- a/polymarket/maker-rebate-bot/tests/test_smoke.py +++ b/polymarket/maker-rebate-bot/tests/test_smoke.py @@ -36,6 +36,9 @@ def test_live_guard_fixture_blocks_execution() -> None: def test_backtest_run_type_returns_result_from_config_history(tmp_path: Path) -> None: + payload = json.loads(CONFIG_EXAMPLE_PATH.read_text(encoding="utf-8")) + assert payload["strategy"]["bankroll_usd"] == 1000 + now_ts = int(time.time()) start_ts = now_ts - (90 * 24 * 3600) history = [] @@ -45,44 +48,19 @@ def test_backtest_run_type_returns_result_from_config_history(tmp_path: Path) -> px = max(0.05, min(0.95, 0.5 + wave + drift)) history.append({"t": start_ts + (i * 3600), "p": round(px, 6)}) - payload = { - "execution": {"dry_run": True, "live_mode": False}, - "backtest": { - "days": 90, - "fidelity_minutes": 60, - "participation_rate": 0.2, - "volatility_window_points": 24, - "min_history_points": 200, - "min_liquidity_usd": 0, - }, - "strategy": { - "bankroll_usd": 1000, - "markets_max": 4, - "min_seconds_to_resolution": 21600, - "min_edge_bps": 2, - "default_rebate_bps": 3, - "expected_unwind_cost_bps": 1.5, - "adverse_selection_bps": 1.0, - "min_spread_bps": 20, - "max_spread_bps": 150, - "volatility_spread_multiplier": 0.35, - "base_order_notional_usd": 25, - "max_notional_per_market_usd": 125, - "max_total_notional_usd": 500, - "max_position_notional_usd": 150, - "inventory_skew_strength_bps": 25, - }, - "backtest_markets": [ - { - "market_id": "TEST-90D", - "question": "Synthetic 90D market", - "token_id": "TEST-90D", - "rebate_bps": 3, - "end_ts": now_ts + (7 * 24 * 3600), - "history": history, - } - ], - } + payload["backtest"]["min_history_points"] = 200 + payload["backtest"]["min_liquidity_usd"] = 0 + payload["backtest_markets"] = [ + { + "market_id": f"TEST-90D-{idx}", + "question": "Synthetic 90D market", + "token_id": f"TEST-90D-{idx}", + "rebate_bps": 3, + "end_ts": now_ts + (7 * 24 * 3600), + "history": history, + } + for idx in range(payload["strategy"]["markets_max"]) + ] config_path = tmp_path / "config.json" config_path.write_text(json.dumps(payload), encoding="utf-8") @@ -110,6 +88,8 @@ def test_backtest_run_type_returns_result_from_config_history(tmp_path: Path) -> assert output["backtest_summary"]["source"] == "config" assert output["backtest_summary"]["markets_selected"] >= 1 assert output["results"]["events"] > 0 + assert output["results"]["starting_bankroll_usd"] == 1000 + assert output["results"]["return_pct"] >= 20.0 def test_config_example_uses_seren_polymarket_publisher_urls() -> None: diff --git a/polymarket/paired-market-basis-maker/config.example.json b/polymarket/paired-market-basis-maker/config.example.json index a7257fc..ca1978a 100644 --- a/polymarket/paired-market-basis-maker/config.example.json +++ b/polymarket/paired-market-basis-maker/config.example.json @@ -11,7 +11,7 @@ "min": 90, "max": 540 }, - "participation_rate": 0.64, + "participation_rate": 0.9, "min_history_points": 72, "min_events": 200, "min_liquidity_usd": 5000, @@ -24,8 +24,8 @@ "clob_history_url": "https://api.serendb.com/publishers/polymarket-trading-serenai/trades" }, "strategy": { - "bankroll_usd": 500, - "pairs_max": 6, + "bankroll_usd": 1000, + "pairs_max": 8, "min_seconds_to_resolution": 7200, "min_edge_bps": 2.0, "maker_rebate_bps": 2.3, @@ -34,10 +34,10 @@ "basis_entry_bps": 35, "basis_exit_bps": 10, "expected_convergence_ratio": 0.35, - "base_pair_notional_usd": 65, - "max_notional_per_pair_usd": 240, - "max_total_notional_usd": 500, - "max_leg_notional_usd": 250 + "base_pair_notional_usd": 550, + "max_notional_per_pair_usd": 750, + "max_total_notional_usd": 1600, + "max_leg_notional_usd": 800 }, "state": { "leg_exposure": { diff --git a/polymarket/paired-market-basis-maker/scripts/agent.py b/polymarket/paired-market-basis-maker/scripts/agent.py index e6b1f03..69b7183 100644 --- a/polymarket/paired-market-basis-maker/scripts/agent.py +++ b/polymarket/paired-market-basis-maker/scripts/agent.py @@ -29,8 +29,8 @@ @dataclass(frozen=True) class StrategyParams: - bankroll_usd: float = 500.0 - pairs_max: int = 6 + bankroll_usd: float = 1000.0 + pairs_max: int = 8 min_seconds_to_resolution: int = 2 * 60 * 60 min_edge_bps: float = 2.0 maker_rebate_bps: float = 2.3 @@ -39,10 +39,10 @@ class StrategyParams: basis_entry_bps: float = 35.0 basis_exit_bps: float = 10.0 expected_convergence_ratio: float = 0.35 - base_pair_notional_usd: float = 65.0 - max_notional_per_pair_usd: float = 240.0 - max_total_notional_usd: float = 500.0 - max_leg_notional_usd: float = 250.0 + base_pair_notional_usd: float = 550.0 + max_notional_per_pair_usd: float = 750.0 + max_total_notional_usd: float = 1600.0 + max_leg_notional_usd: float = 800.0 @dataclass(frozen=True) @@ -50,7 +50,7 @@ class BacktestParams: days: int = 270 days_min: int = 90 days_max: int = 540 - participation_rate: float = 0.64 + participation_rate: float = 0.9 min_history_points: int = 72 min_events: int = 200 min_liquidity_usd: float = 5000.0 @@ -142,8 +142,8 @@ def load_config(config_path: str) -> dict[str, Any]: def to_strategy_params(config: dict[str, Any]) -> StrategyParams: raw = config.get("strategy", {}) return StrategyParams( - bankroll_usd=max(1.0, _safe_float(raw.get("bankroll_usd"), 500.0)), - pairs_max=max(1, _safe_int(raw.get("pairs_max"), 6)), + bankroll_usd=max(1.0, _safe_float(raw.get("bankroll_usd"), 1000.0)), + pairs_max=max(1, _safe_int(raw.get("pairs_max"), 8)), min_seconds_to_resolution=max(60, _safe_int(raw.get("min_seconds_to_resolution"), 7200)), min_edge_bps=_safe_float(raw.get("min_edge_bps"), 2.0), maker_rebate_bps=_safe_float(raw.get("maker_rebate_bps"), 2.3), @@ -156,10 +156,10 @@ def to_strategy_params(config: dict[str, Any]) -> StrategyParams: 0.0, 1.0, ), - base_pair_notional_usd=max(1.0, _safe_float(raw.get("base_pair_notional_usd"), 65.0)), - max_notional_per_pair_usd=max(1.0, _safe_float(raw.get("max_notional_per_pair_usd"), 240.0)), - max_total_notional_usd=max(1.0, _safe_float(raw.get("max_total_notional_usd"), 500.0)), - max_leg_notional_usd=max(1.0, _safe_float(raw.get("max_leg_notional_usd"), 250.0)), + base_pair_notional_usd=max(1.0, _safe_float(raw.get("base_pair_notional_usd"), 550.0)), + max_notional_per_pair_usd=max(1.0, _safe_float(raw.get("max_notional_per_pair_usd"), 750.0)), + max_total_notional_usd=max(1.0, _safe_float(raw.get("max_total_notional_usd"), 1600.0)), + max_leg_notional_usd=max(1.0, _safe_float(raw.get("max_leg_notional_usd"), 800.0)), ) @@ -173,7 +173,7 @@ def to_backtest_params(config: dict[str, Any]) -> BacktestParams: days=days, days_min=days_min, days_max=days_max, - participation_rate=clamp(_safe_float(raw.get("participation_rate"), 0.64), 0.0, 1.0), + participation_rate=clamp(_safe_float(raw.get("participation_rate"), 0.9), 0.0, 1.0), min_history_points=max(8, _safe_int(raw.get("min_history_points"), 72)), min_events=max(1, _safe_int(raw.get("min_events"), 200)), min_liquidity_usd=max(0.0, _safe_float(raw.get("min_liquidity_usd"), 5000.0)), diff --git a/polymarket/paired-market-basis-maker/tests/test_smoke.py b/polymarket/paired-market-basis-maker/tests/test_smoke.py index 282d779..705b3ff 100644 --- a/polymarket/paired-market-basis-maker/tests/test_smoke.py +++ b/polymarket/paired-market-basis-maker/tests/test_smoke.py @@ -1,16 +1,49 @@ from __future__ import annotations +import importlib.util import json +import sys +import time from pathlib import Path FIXTURE_DIR = Path(__file__).parent / "fixtures" +SCRIPT_PATH = Path(__file__).resolve().parents[1] / "scripts" / "agent.py" +CONFIG_EXAMPLE_PATH = Path(__file__).resolve().parents[1] / "config.example.json" def _read_fixture(name: str) -> dict: return json.loads((FIXTURE_DIR / name).read_text(encoding="utf-8")) +def _load_agent_module() -> object: + spec = importlib.util.spec_from_file_location("paired_market_basis_maker_agent_test", SCRIPT_PATH) + module = importlib.util.module_from_spec(spec) + sys.modules[spec.name] = module + assert spec.loader is not None + spec.loader.exec_module(module) + return module + + +def _synthetic_pair_series(points: int = 420, start_ts: int | None = None) -> tuple[list[tuple[int, float]], list[tuple[int, float]]]: + start = start_ts or (int(time.time()) - (points * 3600)) + primary: list[tuple[int, float]] = [] + pair: list[tuple[int, float]] = [] + for i in range(points): + cycle = i % 4 + if cycle == 0: + p1, p2 = 0.54, 0.46 + elif cycle == 1: + p1, p2 = 0.53, 0.47 + elif cycle == 2: + p1, p2 = 0.515, 0.485 + else: + p1, p2 = 0.505, 0.495 + primary.append((start + (i * 3600), p1)) + pair.append((start + (i * 3600), p2)) + return primary, pair + + def test_happy_path_fixture_is_successful() -> None: payload = _read_fixture("happy_path.json") assert payload["status"] == "ok" @@ -34,3 +67,37 @@ def test_dry_run_fixture_blocks_live_execution() -> None: assert payload["dry_run"] is True assert payload["blocked_action"] == "live_execution" + +def test_config_example_targets_promotional_backtest_return(monkeypatch) -> None: + module = _load_agent_module() + payload = json.loads(CONFIG_EXAMPLE_PATH.read_text(encoding="utf-8")) + + defaults = module.to_strategy_params({}) + backtest_defaults = module.to_backtest_params({}) + assert defaults.bankroll_usd == payload["strategy"]["bankroll_usd"] == 1000 + assert defaults.base_pair_notional_usd == payload["strategy"]["base_pair_notional_usd"] + assert backtest_defaults.participation_rate == payload["backtest"]["participation_rate"] + + primary, pair = _synthetic_pair_series() + synthetic_markets = [ + { + "market_id": f"M{idx}", + "pair_market_id": f"P{idx}", + "end_ts": int(time.time()) + (5 * 24 * 3600), + "rebate_bps": payload["strategy"]["maker_rebate_bps"], + "history": primary, + "pair_history": pair, + } + for idx in range(max(payload["strategy"]["pairs_max"], 8)) + ] + + monkeypatch.setattr( + module, + "_load_backtest_markets", + lambda p, bt, start_ts, end_ts: (synthetic_markets, "synthetic"), + ) + + output = module.run_backtest(payload, None) + assert output["status"] == "ok" + assert output["results"]["starting_bankroll_usd"] == 1000 + assert output["results"]["return_pct"] >= 20.0