diff --git a/.github/ISSUE_TEMPLATE/bug.yml b/.github/ISSUE_TEMPLATE/bug.yml index 3351944..4a3ef88 100644 --- a/.github/ISSUE_TEMPLATE/bug.yml +++ b/.github/ISSUE_TEMPLATE/bug.yml @@ -27,7 +27,7 @@ body: id: version attributes: label: Engine revision or version - placeholder: v1.1.0 or a full commit SHA + placeholder: v1.1.1 or a full commit SHA validations: required: true - type: input diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 97e275a..dac2c39 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -30,7 +30,7 @@ jobs: - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 with: persist-credentials: false - - uses: ocaml/setup-ocaml@605a7e998e76e035b82c14d618a6e1010732c4ce # v3.7.1 + - uses: ocaml/setup-ocaml@f92e0606b7ae4873dd1238465ea4bf6f8e40d85c # v3.7.2 with: ocaml-compiler: "5.5.0" - uses: astral-sh/setup-uv@20cfd1bf945f4377ade1205e4dbc17946fc9a30d # v10.0.1 @@ -68,7 +68,7 @@ jobs: - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 with: persist-credentials: false - - uses: ocaml/setup-ocaml@605a7e998e76e035b82c14d618a6e1010732c4ce # v3.7.1 + - uses: ocaml/setup-ocaml@f92e0606b7ae4873dd1238465ea4bf6f8e40d85c # v3.7.2 with: ocaml-compiler: "5.5.0" - uses: astral-sh/setup-uv@20cfd1bf945f4377ade1205e4dbc17946fc9a30d # v10.0.1 @@ -86,7 +86,7 @@ jobs: - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 with: persist-credentials: false - - uses: ocaml/setup-ocaml@605a7e998e76e035b82c14d618a6e1010732c4ce # v3.7.1 + - uses: ocaml/setup-ocaml@f92e0606b7ae4873dd1238465ea4bf6f8e40d85c # v3.7.2 with: ocaml-compiler: "5.5.0" - uses: astral-sh/setup-uv@20cfd1bf945f4377ade1205e4dbc17946fc9a30d # v10.0.1 @@ -130,7 +130,7 @@ jobs: - uses: astral-sh/setup-uv@20cfd1bf945f4377ade1205e4dbc17946fc9a30d # v10.0.1 with: python-version: "3.12" - - uses: ocaml/setup-ocaml@605a7e998e76e035b82c14d618a6e1010732c4ce # v3.7.1 + - uses: ocaml/setup-ocaml@f92e0606b7ae4873dd1238465ea4bf6f8e40d85c # v3.7.2 with: ocaml-compiler: "5.5.0" - working-directory: trading-engine @@ -170,7 +170,7 @@ jobs: - uses: astral-sh/setup-uv@20cfd1bf945f4377ade1205e4dbc17946fc9a30d # v10.0.1 with: python-version: "3.12" - - uses: ocaml/setup-ocaml@605a7e998e76e035b82c14d618a6e1010732c4ce # v3.7.1 + - uses: ocaml/setup-ocaml@f92e0606b7ae4873dd1238465ea4bf6f8e40d85c # v3.7.2 with: ocaml-compiler: "5.5.0" - working-directory: trading-engine diff --git a/.github/workflows/codeql.yml b/.github/workflows/codeql.yml index e3f3ba8..c0c2bbc 100644 --- a/.github/workflows/codeql.yml +++ b/.github/workflows/codeql.yml @@ -38,11 +38,11 @@ jobs: - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 with: persist-credentials: false - - uses: github/codeql-action/init@db488ddef3bf6cb639b32c2e9a7c0a7ea8271d28 # v4.37.8 + - uses: github/codeql-action/init@cdf488f595d80d6e07e03d4674febd5ab45fa938 # v4.37.9 with: languages: ${{ matrix.language }} build-mode: none queries: security-extended - - uses: github/codeql-action/analyze@db488ddef3bf6cb639b32c2e9a7c0a7ea8271d28 # v4.37.8 + - uses: github/codeql-action/analyze@cdf488f595d80d6e07e03d4674febd5ab45fa938 # v4.37.9 with: category: "/language:${{ matrix.language }}" diff --git a/.github/workflows/docs.yml b/.github/workflows/docs.yml index 8a62cdf..cab06c6 100644 --- a/.github/workflows/docs.yml +++ b/.github/workflows/docs.yml @@ -65,7 +65,7 @@ jobs: - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 with: persist-credentials: false - - uses: ocaml/setup-ocaml@605a7e998e76e035b82c14d618a6e1010732c4ce # v3.7.1 + - uses: ocaml/setup-ocaml@f92e0606b7ae4873dd1238465ea4bf6f8e40d85c # v3.7.2 with: ocaml-compiler: "5.5.0" - uses: astral-sh/setup-uv@20cfd1bf945f4377ade1205e4dbc17946fc9a30d # v10.0.1 diff --git a/.github/workflows/release-candidate.yml b/.github/workflows/release-candidate.yml index 86870a7..131a9f8 100644 --- a/.github/workflows/release-candidate.yml +++ b/.github/workflows/release-candidate.yml @@ -80,7 +80,7 @@ jobs: - name: Validate manual tag boundary if: github.event_name == 'workflow_dispatch' run: test "$GITHUB_REF" = "refs/tags/v$RELEASE_VERSION" - - uses: ocaml/setup-ocaml@605a7e998e76e035b82c14d618a6e1010732c4ce # v3.7.1 + - uses: ocaml/setup-ocaml@f92e0606b7ae4873dd1238465ea4bf6f8e40d85c # v3.7.2 with: ocaml-compiler: "5.5.0" - uses: astral-sh/setup-uv@20cfd1bf945f4377ade1205e4dbc17946fc9a30d # v10.0.1 diff --git a/CHANGELOG.md b/CHANGELOG.md index 45ff8dc..f7f8876 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,12 @@ # Changelog +## Unreleased + +## [1.1.1] - 2026-09-04 + +- Make development bootstrap repair a stale globally registered coverage repository URL before + selecting it for the repository-local switch. + ## [1.1.0] - 2026-08-26 - Reset the scenario, journal, strategy, execution-configuration, CLI, and diagnostic contracts to diff --git a/contracts/strategy/v1/fixtures/external.strategy.jsonl b/contracts/strategy/v1/fixtures/external.strategy.jsonl index 36693da..bbd0e57 100644 --- a/contracts/strategy/v1/fixtures/external.strategy.jsonl +++ b/contracts/strategy/v1/fixtures/external.strategy.jsonl @@ -1,4 +1,4 @@ -{"strategy_protocol_version":"1","transcript_sequence":"1","direction":"engine_to_strategy","message":{"strategy_protocol_version":"1","strategy_sequence":"1","message_type":"initialize","payload":{"engine_version":"1.1.0","scenario_contract_version":"1","scenario_sha256":"7c1991b8f4662c51faf8b3436999fdce8f0295ee6c0ef5d20a0658c960d04c4d","run_id":"external-demo","base_currency":"USD","initial_portfolio":{"cash":[{"currency":"USD","amount":"10000"}],"positions":[],"marks":[],"fx_rates":[{"currency":"USD","rate":"1"}]},"venue_calendars":[{"calendar_id":"demo-xnas-2026","calendar_version":"1","venue_id":"XNAS","instrument_ids":["demo-equity-acme"],"sessions":[{"session_date":"2026-01-01","policy":"holiday","phases":[]},{"session_date":"2026-01-02","policy":"regular","phases":[{"phase":"premarket","opens_at":"2026-01-02T09:00:00.000000Z","closes_at":"2026-01-02T14:25:00.000000Z"},{"phase":"opening_auction","opens_at":"2026-01-02T14:25:00.000000Z","closes_at":"2026-01-02T14:30:00.000000Z"},{"phase":"regular","opens_at":"2026-01-02T14:30:00.000000Z","closes_at":"2026-01-02T20:55:00.000000Z"},{"phase":"closing_auction","opens_at":"2026-01-02T20:55:00.000000Z","closes_at":"2026-01-02T21:00:00.000000Z"},{"phase":"postmarket","opens_at":"2026-01-02T21:00:00.000000Z","closes_at":"2026-01-03T01:00:00.000000Z"}]},{"session_date":"2026-01-05","policy":"regular","phases":[{"phase":"regular","opens_at":"2026-01-05T14:30:00.000000Z","closes_at":"2026-01-05T21:00:00.000000Z"}]},{"session_date":"2026-01-06","policy":"regular","phases":[{"phase":"regular","opens_at":"2026-01-06T14:30:00.000000Z","closes_at":"2026-01-06T21:00:00.000000Z"}]},{"session_date":"2026-01-07","policy":"regular","phases":[{"phase":"regular","opens_at":"2026-01-07T14:30:00.000000Z","closes_at":"2026-01-07T21:00:00.000000Z"}]},{"session_date":"2026-01-08","policy":"early_close","phases":[{"phase":"regular","opens_at":"2026-01-08T14:30:00.000000Z","closes_at":"2026-01-08T18:00:00.000000Z"}]}]}],"financing":{"day_count":"actual_365","compounding":"simple","borrow_missing_data":"reject","cash_missing_data":"reject","locate_policy":"clip_fill","recall_policy":"close_out"},"settlement":{"cash_buying_power":"total_cash","position_availability":"total_positions","calendars":[{"calendar_id":"default-settlement","version":"1","business_dates":["2026-01-02","2026-01-05","2026-01-06","2026-01-07","2026-01-08","2026-01-09","2026-02-02","2026-02-03","2026-02-04","2026-02-05"]}],"rules":[{"instrument_id":"demo-equity-acme","calendar_id":"default-settlement","lag_business_days":1}]},"instruments":[{"instrument_id":"demo-equity-acme","symbol":"ACME","quote_currency":"USD","tick_size":"0.01","lot_size":"1"}],"risk":{"max_gross_exposure":"1000000","max_leverage":"2","instrument_policies":[{"instrument_id":"demo-equity-acme","max_order_quantity":"1000","max_long_position":"1000","max_short_position":"1000","max_notional_exposure":"1000000","initial_margin_bps":5000,"maintenance_margin_bps":2500,"shorting_allowed":true}],"groups":[]},"execution":{"model":"completed_bar_v1","configuration":{"version":"1","participation_bps":5000,"fee_schedules":[{"schedule_id":"external-acme-fees-v1","instrument_id":"demo-equity-acme","settlement_currency":"USD","minimum":null,"maximum":null,"components":[{"name":"broker","currency":"USD","kind":"fixed","value":"0.25","rounding":"up","applies_to":"any"},{"name":"exchange","currency":"USD","kind":"notional_bps","value":10,"rounding":"up","applies_to":"any"}]}]}},"metadata":{"producer":"strategy-protocol-fixture"}}}} +{"strategy_protocol_version":"1","transcript_sequence":"1","direction":"engine_to_strategy","message":{"strategy_protocol_version":"1","strategy_sequence":"1","message_type":"initialize","payload":{"engine_version":"1.1.1","scenario_contract_version":"1","scenario_sha256":"7c1991b8f4662c51faf8b3436999fdce8f0295ee6c0ef5d20a0658c960d04c4d","run_id":"external-demo","base_currency":"USD","initial_portfolio":{"cash":[{"currency":"USD","amount":"10000"}],"positions":[],"marks":[],"fx_rates":[{"currency":"USD","rate":"1"}]},"venue_calendars":[{"calendar_id":"demo-xnas-2026","calendar_version":"1","venue_id":"XNAS","instrument_ids":["demo-equity-acme"],"sessions":[{"session_date":"2026-01-01","policy":"holiday","phases":[]},{"session_date":"2026-01-02","policy":"regular","phases":[{"phase":"premarket","opens_at":"2026-01-02T09:00:00.000000Z","closes_at":"2026-01-02T14:25:00.000000Z"},{"phase":"opening_auction","opens_at":"2026-01-02T14:25:00.000000Z","closes_at":"2026-01-02T14:30:00.000000Z"},{"phase":"regular","opens_at":"2026-01-02T14:30:00.000000Z","closes_at":"2026-01-02T20:55:00.000000Z"},{"phase":"closing_auction","opens_at":"2026-01-02T20:55:00.000000Z","closes_at":"2026-01-02T21:00:00.000000Z"},{"phase":"postmarket","opens_at":"2026-01-02T21:00:00.000000Z","closes_at":"2026-01-03T01:00:00.000000Z"}]},{"session_date":"2026-01-05","policy":"regular","phases":[{"phase":"regular","opens_at":"2026-01-05T14:30:00.000000Z","closes_at":"2026-01-05T21:00:00.000000Z"}]},{"session_date":"2026-01-06","policy":"regular","phases":[{"phase":"regular","opens_at":"2026-01-06T14:30:00.000000Z","closes_at":"2026-01-06T21:00:00.000000Z"}]},{"session_date":"2026-01-07","policy":"regular","phases":[{"phase":"regular","opens_at":"2026-01-07T14:30:00.000000Z","closes_at":"2026-01-07T21:00:00.000000Z"}]},{"session_date":"2026-01-08","policy":"early_close","phases":[{"phase":"regular","opens_at":"2026-01-08T14:30:00.000000Z","closes_at":"2026-01-08T18:00:00.000000Z"}]}]}],"financing":{"day_count":"actual_365","compounding":"simple","borrow_missing_data":"reject","cash_missing_data":"reject","locate_policy":"clip_fill","recall_policy":"close_out"},"settlement":{"cash_buying_power":"total_cash","position_availability":"total_positions","calendars":[{"calendar_id":"default-settlement","version":"1","business_dates":["2026-01-02","2026-01-05","2026-01-06","2026-01-07","2026-01-08","2026-01-09","2026-02-02","2026-02-03","2026-02-04","2026-02-05"]}],"rules":[{"instrument_id":"demo-equity-acme","calendar_id":"default-settlement","lag_business_days":1}]},"instruments":[{"instrument_id":"demo-equity-acme","symbol":"ACME","quote_currency":"USD","tick_size":"0.01","lot_size":"1"}],"risk":{"max_gross_exposure":"1000000","max_leverage":"2","instrument_policies":[{"instrument_id":"demo-equity-acme","max_order_quantity":"1000","max_long_position":"1000","max_short_position":"1000","max_notional_exposure":"1000000","initial_margin_bps":5000,"maintenance_margin_bps":2500,"shorting_allowed":true}],"groups":[]},"execution":{"model":"completed_bar_v1","configuration":{"version":"1","participation_bps":5000,"fee_schedules":[{"schedule_id":"external-acme-fees-v1","instrument_id":"demo-equity-acme","settlement_currency":"USD","minimum":null,"maximum":null,"components":[{"name":"broker","currency":"USD","kind":"fixed","value":"0.25","rounding":"up","applies_to":"any"},{"name":"exchange","currency":"USD","kind":"notional_bps","value":10,"rounding":"up","applies_to":"any"}]}]}},"metadata":{"producer":"strategy-protocol-fixture"}}}} {"strategy_protocol_version":"1","transcript_sequence":"2","direction":"strategy_to_engine","message":{"strategy_protocol_version":"1","strategy_sequence":"1","message_type":"ready","payload":{"strategy_name":"fixture-strategy","strategy_version":"1"}}} {"strategy_protocol_version":"1","transcript_sequence":"3","direction":"engine_to_strategy","message":{"strategy_protocol_version":"1","strategy_sequence":"2","message_type":"event","payload":{"context":{"now":"2026-01-02T21:00:02.000000Z","portfolio":{"base_currency":"USD","cash":"10000","net_market_value":"0","long_market_value":"0","short_market_value":"0","gross_exposure":"0","equity":"10000","weights_available":true,"cash_weight":"1","cash_balances":[{"currency":"USD","amount":"10000","fx_rate":"1","base_value":"10000","interest":"0","base_interest":"0","settled_amount":"10000","unsettled_amount":"0","base_settled_value":"10000","base_unsettled_value":"0"}],"positions":[{"instrument_id":"demo-equity-acme","quantity":"0","mark":"104","base_market_value":"0","weight":"0","settled_quantity":"0","unsettled_quantity":"0"}],"group_exposures":[]},"working_orders":[],"latest_bars":[{"instrument_id":"demo-equity-acme","open":"100","high":"105","low":"99","close":"104","volume":"100"}]},"event":{"type":"market_slice_closed","market_slice":{"slice_sequence":"1","start_at":"2026-01-02T14:30:00.000000Z","end_at":"2026-01-02T21:00:00.000000Z","available_at":"2026-01-02T21:00:01.000000Z","received_at":"2026-01-02T21:00:02.000000Z","bars":[{"instrument_id":"demo-equity-acme","open":"100","high":"105","low":"99","close":"104","volume":"100"}],"fx_rates":[{"currency":"USD","rate":"1"}],"corporate_actions":[],"borrow_observations":[{"instrument_id":"demo-equity-acme","effective_at":"2026-01-02T14:30:00.000000Z","available_quantity":"1000","annual_rate_bps":100,"recalled":false}],"cash_rate_observations":[{"currency":"USD","effective_at":"2026-01-02T14:30:00.000000Z","credit_rate_bps":0,"debit_rate_bps":0}],"settlement_failures":[],"lifecycle_events":[],"market_events":[],"order_book_events":[]}}}}} {"strategy_protocol_version":"1","transcript_sequence":"4","direction":"strategy_to_engine","message":{"strategy_protocol_version":"1","strategy_sequence":"2","message_type":"intents","payload":{"intents":[{"type":"target_quantities","targets":[{"instrument_id":"demo-equity-acme","quantity":"2"}]},{"type":"emit_metric","name":"fixture_signal","value":{"type":"numeric","value":"2"},"unit":"score","dimensions":{"source":"fixture"},"aggregation":"last"}]}}} diff --git a/dune-project b/dune-project index eb0b69d..82635be 100644 --- a/dune-project +++ b/dune-project @@ -1,7 +1,7 @@ (lang dune 3.24) (name trading_engine) -(version 1.1.0) +(version 1.1.1) (generate_opam_files false) (implicit_transitive_deps false) diff --git a/lib/contract.ml b/lib/contract.ml index 202d0bb..b308f4e 100644 --- a/lib/contract.ml +++ b/lib/contract.ml @@ -2,7 +2,7 @@ let version = "1" let supported_versions = [ version ] let is_supported version = List.mem version supported_versions let strategy_protocol_version = "1" -let engine_version = "1.1.0" +let engine_version = "1.1.1" let strings values = `List (List.map (fun value -> `String value) values) let capabilities_to_yojson () = diff --git a/scripts/bootstrap-development-environment b/scripts/bootstrap-development-environment index 35a9ca2..4f3bf46 100755 --- a/scripts/bootstrap-development-environment +++ b/scripts/bootstrap-development-environment @@ -35,10 +35,19 @@ if [ ! -f "$repository_root/_opam/.opam-switch/switch-config" ]; then fi printf '%s\n' "Registering the checksummed OCaml coverage package..." -opam repository add "$coverage_repository_name" "$coverage_repository" \ - --rank 1 \ - --switch "$repository_root" \ - --yes +if opam repository list --all --short --color never 2>/dev/null \ + | grep -Fqx "$coverage_repository_name"; then + opam repository set-url "$coverage_repository_name" "$coverage_repository" --yes + opam repository add "$coverage_repository_name" \ + --rank 1 \ + --switch "$repository_root" \ + --yes +else + opam repository add "$coverage_repository_name" "$coverage_repository" \ + --rank 1 \ + --switch "$repository_root" \ + --yes +fi if opam pin list --switch "$repository_root" --short | grep -Fqx "bisect_ppx"; then opam pin remove bisect_ppx \ diff --git a/test/cli.t b/test/cli.t index 33fada3..a96444b 100644 --- a/test/cli.t +++ b/test/cli.t @@ -1,8 +1,8 @@ $ ../bin/main.exe --version - 1.1.0 + 1.1.1 $ ../bin/main.exe --capabilities - {"engine_version":"1.1.0","scenario_contract_versions":["1"],"journal_contract_versions":["1"],"scenario_formats":["json","jsonl"],"journal_formats":["jsonl"],"execution_models":["completed_bar_v1","completed_bar_next_open_v1","completed_bar_adverse_touch_v1","quote_trade_v1","order_book_v1"],"execution_model_contracts":[{"name":"completed_bar_v1","configuration_versions":["1"],"scenario_contract_versions":["1"],"required_fields":["version","participation_bps","fee_schedules"],"configuration_required_fields":{"1":["version","participation_bps","fee_schedules"]},"supported_order_types":["market","limit","stop","stop_limit"],"data_requirements":["completed_ohlcv_bars"],"limits":{"participation_bps":{"minimum":0,"maximum":10000}}},{"name":"completed_bar_next_open_v1","configuration_versions":["1"],"scenario_contract_versions":["1"],"required_fields":["version","participation_bps","fee_schedules","spread_model","impact_model"],"configuration_required_fields":{"1":["version","participation_bps","fee_schedules","spread_model","impact_model"]},"supported_order_types":["market","limit","stop","stop_limit"],"data_requirements":["completed_ohlcv_bars","bar_volume_for_linear_impact"],"limits":{"participation_bps":{"minimum":0,"maximum":10000},"half_spread_bps":{"minimum":0,"maximum":10000},"impact_coefficient_bps":{"minimum":0,"maximum":10000}}},{"name":"completed_bar_adverse_touch_v1","configuration_versions":["1"],"scenario_contract_versions":["1"],"required_fields":["version","participation_bps","fee_schedules","spread_model","impact_model"],"configuration_required_fields":{"1":["version","participation_bps","fee_schedules","spread_model","impact_model"]},"supported_order_types":["market","limit","stop","stop_limit"],"data_requirements":["completed_ohlcv_bars","bar_volume_for_linear_impact"],"limits":{"participation_bps":{"minimum":0,"maximum":10000},"half_spread_bps":{"minimum":0,"maximum":10000},"impact_coefficient_bps":{"minimum":0,"maximum":10000}}},{"name":"quote_trade_v1","configuration_versions":["1"],"scenario_contract_versions":["1"],"required_fields":["version","participation_bps","fee_schedules"],"configuration_required_fields":{"1":["version","participation_bps","fee_schedules"]},"supported_order_types":["market","limit","stop","stop_limit"],"data_requirements":["causally_ordered_bid_ask_quotes","aggressor_classified_trades_for_passive_fills","completed_bars_for_valuation"],"limits":{"participation_bps":{"minimum":0,"maximum":10000}}},{"name":"order_book_v1","configuration_versions":["1"],"scenario_contract_versions":["1"],"required_fields":["version","participation_bps","fee_schedules","max_depth_levels"],"configuration_required_fields":{"1":["version","participation_bps","fee_schedules","max_depth_levels"]},"supported_order_types":["market","limit","stop","stop_limit"],"data_requirements":["slice_open_level_two_snapshot","contiguous_absolute_level_updates","aggressor_classified_depth_consuming_trades","completed_bars_for_valuation"],"limits":{"participation_bps":{"minimum":0,"maximum":10000},"max_depth_levels":{"minimum":1,"maximum":1024}}}],"strategy_protocol_versions":["1"],"resource_limits":{"version":"1","scenario_record_bytes":1048576,"strategy_message_bytes":1048576,"internal_events":100000,"catalog_instruments":4096,"intents_per_batch":4096,"artifact_record_bytes":2097152}} + {"engine_version":"1.1.1","scenario_contract_versions":["1"],"journal_contract_versions":["1"],"scenario_formats":["json","jsonl"],"journal_formats":["jsonl"],"execution_models":["completed_bar_v1","completed_bar_next_open_v1","completed_bar_adverse_touch_v1","quote_trade_v1","order_book_v1"],"execution_model_contracts":[{"name":"completed_bar_v1","configuration_versions":["1"],"scenario_contract_versions":["1"],"required_fields":["version","participation_bps","fee_schedules"],"configuration_required_fields":{"1":["version","participation_bps","fee_schedules"]},"supported_order_types":["market","limit","stop","stop_limit"],"data_requirements":["completed_ohlcv_bars"],"limits":{"participation_bps":{"minimum":0,"maximum":10000}}},{"name":"completed_bar_next_open_v1","configuration_versions":["1"],"scenario_contract_versions":["1"],"required_fields":["version","participation_bps","fee_schedules","spread_model","impact_model"],"configuration_required_fields":{"1":["version","participation_bps","fee_schedules","spread_model","impact_model"]},"supported_order_types":["market","limit","stop","stop_limit"],"data_requirements":["completed_ohlcv_bars","bar_volume_for_linear_impact"],"limits":{"participation_bps":{"minimum":0,"maximum":10000},"half_spread_bps":{"minimum":0,"maximum":10000},"impact_coefficient_bps":{"minimum":0,"maximum":10000}}},{"name":"completed_bar_adverse_touch_v1","configuration_versions":["1"],"scenario_contract_versions":["1"],"required_fields":["version","participation_bps","fee_schedules","spread_model","impact_model"],"configuration_required_fields":{"1":["version","participation_bps","fee_schedules","spread_model","impact_model"]},"supported_order_types":["market","limit","stop","stop_limit"],"data_requirements":["completed_ohlcv_bars","bar_volume_for_linear_impact"],"limits":{"participation_bps":{"minimum":0,"maximum":10000},"half_spread_bps":{"minimum":0,"maximum":10000},"impact_coefficient_bps":{"minimum":0,"maximum":10000}}},{"name":"quote_trade_v1","configuration_versions":["1"],"scenario_contract_versions":["1"],"required_fields":["version","participation_bps","fee_schedules"],"configuration_required_fields":{"1":["version","participation_bps","fee_schedules"]},"supported_order_types":["market","limit","stop","stop_limit"],"data_requirements":["causally_ordered_bid_ask_quotes","aggressor_classified_trades_for_passive_fills","completed_bars_for_valuation"],"limits":{"participation_bps":{"minimum":0,"maximum":10000}}},{"name":"order_book_v1","configuration_versions":["1"],"scenario_contract_versions":["1"],"required_fields":["version","participation_bps","fee_schedules","max_depth_levels"],"configuration_required_fields":{"1":["version","participation_bps","fee_schedules","max_depth_levels"]},"supported_order_types":["market","limit","stop","stop_limit"],"data_requirements":["slice_open_level_two_snapshot","contiguous_absolute_level_updates","aggressor_classified_depth_consuming_trades","completed_bars_for_valuation"],"limits":{"participation_bps":{"minimum":0,"maximum":10000},"max_depth_levels":{"minimum":1,"maximum":1024}}}],"strategy_protocol_versions":["1"],"resource_limits":{"version":"1","scenario_record_bytes":1048576,"strategy_message_bytes":1048576,"internal_events":100000,"catalog_instruments":4096,"intents_per_batch":4096,"artifact_record_bytes":2097152}} $ ../bin/main.exe --validate-only --input ../contracts/v1/fixtures/demo.scenario.json valid run=demo instruments=1 schedule=2 slices=4 scenario_sha256=e2227af76072fab8151c3e1bd86f401293d16a736e32040efdaf8761cd397574 diff --git a/test/dune b/test/dune index 34999af..f9cbec6 100644 --- a/test/dune +++ b/test/dune @@ -117,6 +117,7 @@ (alias runtest) (deps test_development_environment.py + ../scripts/bootstrap-development-environment ../scripts/check-schema-environment.py) (action (run python3 %{dep:test_development_environment.py}))) diff --git a/test/test_development_environment.py b/test/test_development_environment.py index 4534a41..5b5cdd8 100644 --- a/test/test_development_environment.py +++ b/test/test_development_environment.py @@ -1,7 +1,10 @@ from __future__ import annotations import importlib.util +import os import pathlib +import shutil +import subprocess import tempfile import unittest @@ -53,5 +56,93 @@ def test_reports_missing_mismatched_and_unexpected_packages(self) -> None: ) +class BootstrapEnvironmentTest(unittest.TestCase): + def test_repairs_a_registered_repository_before_selecting_it(self) -> None: + result, commands, repository_root = self.run_bootstrap(repository_registered=True) + + self.assertEqual(result.returncode, 0, result.stderr) + self.assertIn( + f"repository set-url trading-engine-coverage {repository_root / 'opam-repository'} --yes", + commands, + ) + self.assertIn( + "repository add trading-engine-coverage --rank 1 " + f"--switch {repository_root} --yes", + commands, + ) + + def test_registers_a_missing_repository_with_its_local_url(self) -> None: + result, commands, repository_root = self.run_bootstrap(repository_registered=False) + + self.assertEqual(result.returncode, 0, result.stderr) + self.assertFalse(any("repository set-url" in command for command in commands)) + self.assertIn( + "repository add trading-engine-coverage " + f"{repository_root / 'opam-repository'} --rank 1 " + f"--switch {repository_root} --yes", + commands, + ) + + def run_bootstrap( + self, *, repository_registered: bool + ) -> tuple[subprocess.CompletedProcess[str], list[str], pathlib.Path]: + temporary = tempfile.TemporaryDirectory() + self.addCleanup(temporary.cleanup) + repository_root = pathlib.Path(temporary.name) + scripts = repository_root / "scripts" + scripts.mkdir() + bootstrap = scripts / "bootstrap-development-environment" + shutil.copy2(REPOSITORY_ROOT / "scripts" / bootstrap.name, bootstrap) + self.write_executable( + scripts / "check-development-environment", "#!/bin/sh\nexit 0\n" + ) + (repository_root / "_opam/.opam-switch").mkdir(parents=True) + (repository_root / "_opam/.opam-switch/switch-config").touch() + schema_bin = repository_root / ".venv-schema/bin" + schema_bin.mkdir(parents=True) + self.write_executable(schema_bin / "python", "#!/bin/sh\nexit 0\n") + (repository_root / "requirements").mkdir() + (repository_root / "requirements/schema.lock").touch() + (repository_root / "opam-repository").mkdir() + + fake_bin = repository_root / "fake-bin" + fake_bin.mkdir() + opam_log = repository_root / "opam.log" + self.write_executable( + fake_bin / "opam", + "#!/bin/sh\n" + 'printf \'%s\\n\' "$*" >> "$OPAM_LOG"\n' + 'if [ "$1" = repository ] && [ "$2" = list ] ' + '&& [ "$REPOSITORY_REGISTERED" = 1 ]; then\n' + " printf '%s\\n' trading-engine-coverage\n" + "fi\n", + ) + self.write_executable(fake_bin / "uv", "#!/bin/sh\nexit 0\n") + self.write_executable(fake_bin / "python3", "#!/bin/sh\nexit 0\n") + environment = os.environ.copy() + environment.update( + { + "OPAM_LOG": str(opam_log), + "PATH": f"{fake_bin}:{environment['PATH']}", + "REPOSITORY_REGISTERED": "1" if repository_registered else "0", + } + ) + + result = subprocess.run( + [str(bootstrap)], + cwd=repository_root, + env=environment, + check=False, + capture_output=True, + text=True, + ) + return result, opam_log.read_text(encoding="utf-8").splitlines(), repository_root + + @staticmethod + def write_executable(path: pathlib.Path, contents: str) -> None: + path.write_text(contents, encoding="utf-8") + path.chmod(0o755) + + if __name__ == "__main__": unittest.main()