Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
53 commits
Select commit Hold shift + click to select a range
30959fa
chore(deps): bump urllib3 from 2.7.0 to 2.8.0
dependabot[bot] Oct 7, 2026
2206d66
docs: adopt shared focused-contribution policy
Oct 13, 2026
2ad36a8
fix: qualify CCXT with patched HTTP dependencies
Oct 17, 2026
caf206e
fix: match position lookups to canonical uppercase symbols
cursoragent Oct 7, 2026
34237eb
test: reproduce lowercase close through LiveEngine
Oct 17, 2026
d435e4d
fix: keep shadow order ids unique within one clock tick
cursoragent Oct 7, 2026
679fe19
fix: preserve unique shadow identities across broker restarts
Oct 17, 2026
e4e8f59
fix: sign Alpaca short positions from the venue side
cursoragent Oct 7, 2026
deee1f1
test: close SDK short snapshots through the Alpaca public broker
Oct 17, 2026
b088e84
fix: reject Alpaca fills that omit the filled quantity
cursoragent Oct 7, 2026
16ef90a
test: exercise Alpaca fill atomicity through registered SDK callbacks
Oct 17, 2026
e1b3022
fix: align aggregated bars across hour boundaries
cursoragent Oct 7, 2026
b01fdd5
Merge branch 'dependabot/uv/urllib3-2.8.0' into codex/contribution-po…
Oct 17, 2026
98e3784
docs: link contribution policy from the development entry point
Oct 17, 2026
dd8d002
docs: isolate quickstart from editable checkout environments
Oct 17, 2026
32b601c
fix: stop Alpaca stream resources after provider faults
Oct 17, 2026
078b776
test: require faulted stream task completion before disconnect returns
Oct 17, 2026
ff089d7
test: require fresh broker evidence after safety changes
Oct 17, 2026
94ff5c0
Merge remote-tracking branch 'origin/main' into fix/canonical-positio…
Oct 17, 2026
0741f47
test: require fresh broker evidence after safety changes
Oct 17, 2026
7be911c
Merge remote-tracking branch 'origin/main' into fix/shadow-order-iden…
Oct 17, 2026
89eb17f
Merge remote-tracking branch 'origin/main' into codex/contribution-po…
Oct 17, 2026
34d4eeb
docs: make the contribution link resolve from pull request bodies
Oct 17, 2026
b1c5193
fix: use canonical symbol whitespace in position lookups
Oct 17, 2026
dd00dc3
Merge remote-tracking branch 'origin/main' into fix/alpaca-short-dire…
Oct 17, 2026
9fce63a
Merge remote-tracking branch 'origin/main' into fix/alpaca-fill-evidence
Oct 17, 2026
1af9309
Merge remote-tracking branch 'origin/main' into fix/aggregate-bar-win…
Oct 17, 2026
65be6f1
test: retain IB and safety evidence checks after main integration
Oct 17, 2026
54bbca4
test: keep broker evidence invalidation checks on secure main
Oct 17, 2026
69f3fa8
fix: keep aggregation windows aligned across UTC dates
Oct 17, 2026
462a163
Merge remote-tracking branch 'origin/main' into fix/aggregate-bar-win…
Oct 17, 2026
750fd0f
Merge branch 'main' into fix/canonical-position-lookups
stefan-jansen Oct 17, 2026
bb389d3
Merge branch 'main' into fix/shadow-order-identities
stefan-jansen Oct 17, 2026
2c3dff7
Merge branch 'main' into fix/alpaca-short-direction
stefan-jansen Oct 17, 2026
246d8c6
Merge branch 'main' into fix/alpaca-fill-evidence
stefan-jansen Oct 17, 2026
9046504
Merge branch 'main' into fix/aggregate-bar-windows
stefan-jansen Oct 17, 2026
399deb5
Merge branch 'main' into fix/alpaca-faulted-disconnect
stefan-jansen Oct 17, 2026
6138c9a
Merge branch 'main' into codex/contribution-policy-adoption
stefan-jansen Oct 17, 2026
e441200
Merge branch 'main' into docs/isolated-published-quickstart
stefan-jansen Oct 17, 2026
4bcbea2
docs: make migration verification runnable and versioned
Oct 17, 2026
6303984
Merge branch 'main' into fix/canonical-position-lookups
stefan-jansen Oct 17, 2026
47b0dbf
fix: integrate shadow order identity repair into maintenance batch
Oct 18, 2026
485609f
fix: integrate Alpaca short position repair into maintenance batch
Oct 18, 2026
c7a19a2
fix: integrate Alpaca executed fill validation into maintenance batch
Oct 18, 2026
3631bca
fix: integrate exact bar interval repair into maintenance batch
Oct 18, 2026
fec5ada
fix: integrate faulted Alpaca stream cleanup into maintenance batch
Oct 18, 2026
63a49e6
docs: distinguish owner maintenance from external contribution rules
Oct 18, 2026
b90077b
docs: integrate isolated quickstart into maintenance batch
Oct 18, 2026
78275f9
docs: integrate runnable migration guide into maintenance batch
Oct 18, 2026
72b908e
ci: qualify the intended 0.1.3 maintenance candidate
Oct 18, 2026
88a5539
docs: adopt maintainer-directed change batching guidance
Oct 18, 2026
eb533cf
ci: align workflow identity validation with the 0.1.3 candidate
Oct 18, 2026
b9578bc
ci: keep provider qualification separate from evidence publication
Oct 18, 2026
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
12 changes: 11 additions & 1 deletion .github/ISSUE_TEMPLATE/bug.yml
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,9 @@ body:
- type: markdown
attributes:
value: |
Search existing issues first. Do not disclose suspected vulnerabilities here.
Follow the [contribution guide](https://github.com/ml4t/live/blob/main/CONTRIBUTING.md).
Search existing issues and describe one problem or coherent change per issue.
Report suspected vulnerabilities privately, not in this form.
- type: input
id: version
attributes:
Expand Down Expand Up @@ -63,3 +65,11 @@ body:
description: State the observable conditions that demonstrate the issue is resolved.
validations:
required: true
- type: textarea
id: ai-assistance
attributes:
label: AI assistance and accountability
description: State whether AI helped with this report and how. Confirm you checked the facts and can explain the reproduction or requested outcome.
placeholder: "None, or describe the assistance and your verification."
validations:
required: true
7 changes: 5 additions & 2 deletions .github/ISSUE_TEMPLATE/config.yml
Original file line number Diff line number Diff line change
Expand Up @@ -4,5 +4,8 @@ contact_links:
url: https://github.com/ml4t/live/security/advisories/new
about: Report suspected vulnerabilities privately.
- name: Usage questions
url: https://github.com/orgs/ml4t/discussions
about: Ask questions that do not describe a defect or requested change.
url: https://www.ml4trading.io/docs/live/
about: Read the usage documentation before opening a focused issue.
- name: Contribution guide
url: https://github.com/ml4t/live/blob/main/CONTRIBUTING.md
about: Read the shared contribution requirements and repository-specific checks.
14 changes: 14 additions & 0 deletions .github/ISSUE_TEMPLATE/documentation.yml
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,12 @@ labels:
- "status: needs-triage"
- "compatibility: none"
body:
- type: markdown
attributes:
value: |
Follow the [contribution guide](https://github.com/ml4t/live/blob/main/CONTRIBUTING.md).
Search existing issues and describe one problem or coherent change per issue.
Report suspected vulnerabilities privately, not in this form.
- type: input
id: version
attributes:
Expand Down Expand Up @@ -38,3 +44,11 @@ body:
label: Acceptance criteria
validations:
required: true
- type: textarea
id: ai-assistance
attributes:
label: AI assistance and accountability
description: State whether AI helped with this report and how. Confirm you checked the facts and can explain the reproduction or requested outcome.
placeholder: "None, or describe the assistance and your verification."
validations:
required: true
14 changes: 14 additions & 0 deletions .github/ISSUE_TEMPLATE/feature.yml
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,12 @@ labels:
- "status: needs-triage"
- "compatibility: affected"
body:
- type: markdown
attributes:
value: |
Follow the [contribution guide](https://github.com/ml4t/live/blob/main/CONTRIBUTING.md).
Search existing issues and describe one problem or coherent change per issue.
Report suspected vulnerabilities privately, not in this form.
- type: input
id: version
attributes:
Expand Down Expand Up @@ -49,3 +55,11 @@ body:
label: Acceptance criteria
validations:
required: true
- type: textarea
id: ai-assistance
attributes:
label: AI assistance and accountability
description: State whether AI helped with this report and how. Confirm you checked the facts and can explain the reproduction or requested outcome.
placeholder: "None, or describe the assistance and your verification."
validations:
required: true
15 changes: 14 additions & 1 deletion .github/PULL_REQUEST_TEMPLATE.md
Original file line number Diff line number Diff line change
@@ -1,7 +1,13 @@
Follow the [local contribution guide](https://github.com/ml4t/live/blob/main/CONTRIBUTING.md) and shared policy linked there.
External submissions should address one problem or coherent change. Maintainer-directed maintenance
may batch fixes and related documentation; reference all affected issues and verify the combined result.

## Owning issue

Closes #

Automated dependency updates and administrative metadata changes do not require a separate issue.

## Outcome

Describe the user-visible or standards outcome.
Expand All @@ -13,7 +19,8 @@ Describe the user-visible or standards outcome.

## Verification

- [ ] Tests reproduce the prior failure or verify the requested behavior
- [ ] For a defect, the same public-workflow regression fails on the relevant base and passes on the fix
- [ ] Exact base/fix revisions, commands, and results are included; documentation uses relevant checks
- [ ] Ruff lint and format checks pass
- [ ] `ty` passes
- [ ] Test suite passes
Expand All @@ -24,3 +31,9 @@ Describe the user-visible or standards outcome.
## Documentation and release

State the documentation, release notes, and patch-release work required after merge.

## AI assistance and accountability

State whether AI assisted with code or prose and identify its contribution.

- [ ] I reviewed and tested this submission and can explain its implementation and evidence
2 changes: 1 addition & 1 deletion .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -21,4 +21,4 @@ jobs:
uses: ./.github/workflows/stable-qualification.yml
with:
candidate-sha: ${{ github.event.pull_request.head.sha || github.sha }}
candidate-version: "0.1.2"
candidate-version: "0.1.3"
2 changes: 1 addition & 1 deletion .github/workflows/docs.yml
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ concurrency:

env:
CANDIDATE_SHA: ${{ github.event.pull_request.head.sha || github.sha }}
CANDIDATE_VERSION: "0.1.1"
CANDIDATE_VERSION: "0.1.3"

jobs:
build:
Expand Down
6 changes: 6 additions & 0 deletions .github/workflows/paper.yml
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,11 @@ on:
default: all
type: choice
options: [alpaca, ib, okx, all]
publish-evidence:
description: Publish a durable evidence prerelease (requires separate maintainer authorization)
required: false
default: false
type: boolean

permissions: {}

Expand Down Expand Up @@ -305,6 +310,7 @@ jobs:
archive:
name: Retain provider evidence
needs: paper
if: ${{ inputs.publish-evidence }}
runs-on: ubuntu-latest
permissions:
actions: read
Expand Down
20 changes: 20 additions & 0 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -55,3 +55,23 @@ uv run ty check
uv run pytest
uv run mkdocs build --strict
```

## Maintainer-directed change batching

For our own fixes and agent work directed by the maintainer, use a bounded, coherent PR rather
than one PR per small defect or implementation step. One PR may close several related issues
when the changes serve the same user workflow, subsystem repair or compatibility objective and
can be reviewed and reverted together. Keep implementation, regressions, documentation and
follow-up repairs together; use focused commits and retain evidence for each defect. Do not
create separate issues or PRs merely for individual files, tests, assertions or platform variants.

Split unrelated changes, independently risky or breaking changes, separate ownership boundaries,
and work that needs independent release or rollback decisions. A general maintenance label alone
does not make a batch coherent. Reuse or safely consolidate existing owned submissions instead of
opening overlapping PRs; preserve contributor attribution, recoverable history and other owners'
active work. Do not rewrite or close someone else's work merely to reduce the queue count.

Run focused checks while iterating, then qualify the complete final revision with every applicable
required gate. Group related work before expensive CI; do not weaken checks, bypass review or
claim old-head checks qualify changed code. This maintainer-directed batching rule takes precedence
over a literal one-issue-per-PR reading of contributor intake guidance.
27 changes: 27 additions & 0 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
# Contributing to ML4T Live

External submissions follow the
[shared ML4T contribution policy](https://github.com/ml4t/ecosystem/blob/main/CONTRIBUTING.md).
Search existing issues first. Describe one problem or coherent change per issue and submit one
focused PR linked to its owning issue. Automated dependency updates and administrative metadata
changes retain the shared exception.

Maintainer-directed maintenance may batch fixes and related documentation in one PR linked to all
affected issues. It does not require a separate PR for each issue or follow-up edit. Preserve each
fix's regression evidence and contributor attribution, and run applicable checks on the combined
result before merging.

For defects, provide a minimal reproduction through the public API or user workflow, package and
environment details, and expected behavior. Record a regression failing on the relevant base and
passing on the fix, with exact revisions, commands, and results. Correct verified numerical defects;
agree on intentional changes to definitions, annualization, signs, or defaults in the owning issue
and describe their compatibility impact. Documentation changes use relevant documentation checks.

Disclose whether AI assisted with code or prose and identify its contribution. Review and test your
submission yourself, and be prepared to explain its implementation, evidence, and review responses.

Start integration work in shadow or paper mode. Preserve explicit live-routing selection,
provider qualification, recovery behavior, and credential isolation.
Use the repository's [quality commands](AGENTS.md#quality-commands) and report their outcomes.
Report suspected vulnerabilities through
[private security reporting](https://github.com/ml4t/live/security/advisories/new).
2 changes: 2 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -94,6 +94,8 @@ shared lifecycle and canonical intent contracts. See the

## Development

Read the [contribution guide](CONTRIBUTING.md) before opening an issue or pull request.

```bash
git clone https://github.com/ml4t/live.git
cd live
Expand Down
5 changes: 4 additions & 1 deletion docs/getting-started/quickstart.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,9 +11,12 @@ Get the maintained example from the public repository, then run it with the publ
```bash
git clone https://github.com/ml4t/live.git
cd live
uv run --no-project --with ml4t-live python examples/shadow_mode_demo.py
uv run --isolated --no-project --with ml4t-live python examples/shadow_mode_demo.py
```

`--isolated` keeps an existing checkout environment from substituting an editable development
package for the published package.

The script defines a synthetic feed that emits one `DEMO` bar per second for 65 seconds. A moving
average strategy submits a buy or sell when its signal changes. `SafeBroker` records virtual fills
and positions under `execution_mode="shadow"`; the example broker raises if it receives a real
Expand Down
10 changes: 7 additions & 3 deletions docs/qualification.md
Original file line number Diff line number Diff line change
Expand Up @@ -70,9 +70,13 @@ provider's result. Documentation, metadata, workflows, generic build tools, and
the provider's reachable contract do not invalidate it. Missing inputs or an unsupported identity
schema fail closed.

The workflow stores redacted evidence as a 90-day Actions artifact and as a durable prerelease
asset named `provider-evidence-RUN_ID/provider-evidence.zip`. The release gate takes wheel and
source-distribution hashes from the exact candidate artifacts. It then selects complete lifecycle,
The workflow stores redacted evidence as a 90-day Actions artifact. Leave `publish-evidence` at
its default `false` to qualify without creating a GitHub release. Publishing a durable prerelease
asset named `provider-evidence-RUN_ID/provider-evidence.zip` requires separate maintainer
authorization and an explicit `publish-evidence: true` request. Retained, validated evidence can
also be archived after qualification once publication is authorized, without repeating the soak.
The release gate takes artifact hashes from the exact candidate wheel and source distribution.
It then selects complete lifecycle,
restart, and soak evidence independently for each matching provider contract. Provider evidence may
come from different current or earlier runs. Evidence has no time-based expiry. Missing, malformed,
mismatched, or unclassifiable evidence fails closed before publication. The Aug 10 run at
Expand Down
26 changes: 26 additions & 0 deletions docs/user-guide/backtest-to-live.md
Original file line number Diff line number Diff line change
Expand Up @@ -103,3 +103,29 @@ virtual positions, and orderly shutdown without a venue. Then compare the same s
and canonical intents in Backtest and Live on equivalent input. Record data cutoffs, execution
policy, starting positions, fees, and venue capabilities before interpreting outcome differences.
The [API reference](../api/index.md#engine) gives the Live engine signature.

## Run the installed-candidate comparison

From a Linux checkout of this repository with `uv` installed, run:

```bash
uv run --locked python scripts/qualification/check_dependency_matrix.py
```

The verifier builds the checkout's Live wheel, installs it into separate minimum, locked, and
maximum dependency environments, and runs the shared strategy through the real `Engine` and
`LiveEngine` contract tests. It also checks canonical order and broker contracts. The output names
the installed package versions for each profile; a failed contract or installation returns a
nonzero exit status. No provider credentials or venue orders are required.

The published shadow quickstart stack checked on 2026-10-17 was:

| Package | Version |
| --- | --- |
| `ml4t-live` | `0.1.2` |
| `ml4t-backtest` | `0.1.12` |
| `ml4t-specs` | `0.1.5` |

The complete shadow quickstart passed on that stack. The command above builds the current
checkout instead; use its reported versions when recording a new parity or migration result.
Neither result establishes equal venue execution or qualifies a later dependency release.
2 changes: 2 additions & 0 deletions docs/user-guide/feeds.md
Original file line number Diff line number Diff line change
Expand Up @@ -188,6 +188,8 @@ Optional symbol filtering:
feed = BarAggregator(raw_feed, bar_size_minutes=5, assets=["SPY", "QQQ"])
```

Intervals align to `1970-01-01T00:00:00Z` and keep their configured duration across UTC midnight.

The aggregator emits one event per asset so sparse symbols retain independent interval boundaries.
An elapsed interval emits once as `complete`. If shutdown occurs before the current interval ends,
its buffered value emits once as `evolving`. Late input cannot reopen an already completed interval.
Expand Down
4 changes: 4 additions & 0 deletions docs/user-guide/migration.md
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,10 @@ or different target policy is a migration failure even when both strategies emit
See [Backtest to Live](backtest-to-live.md) for the portability boundary and
[Candidate Qualification](../qualification.md) for the complete package gate.

Run the [installed-candidate comparison](backtest-to-live.md#run-the-installed-candidate-comparison)
to exercise both engine dispatch paths and record the resolved dependency versions. The same
section names the published shadow reference stack; record separate versions for a new parity run.

## Migrate From The Published Beta

The published `0.1.0b3` package remains the prerelease API migration baseline. The first stable
Expand Down
12 changes: 11 additions & 1 deletion scripts/qualification/check_workflows.py
Original file line number Diff line number Diff line change
Expand Up @@ -547,7 +547,7 @@ def validate_workflows(root: Path = WORKFLOW_ROOT) -> list[str]:
failures.append("CI does not call the reusable qualification workflow")
if ci_qualification.get("with") != {
"candidate-sha": "${{ github.event.pull_request.head.sha || github.sha }}",
"candidate-version": "0.1.2",
"candidate-version": "0.1.3",
}:
failures.append("CI does not bind the reusable qualification candidate")

Expand Down Expand Up @@ -659,6 +659,16 @@ def validate_workflows(root: Path = WORKFLOW_ROOT) -> list[str]:
failures.append("paper qualification does not retain its evidence bundle")

archive_job = paper.get("jobs", {}).get("archive", {})
publication_input = (
paper.get("on", {})
.get("workflow_dispatch", {})
.get("inputs", {})
.get("publish-evidence", {})
)
if publication_input.get("type") != "boolean" or publication_input.get("default") != "false":
failures.append("provider evidence publication is not an explicit opt-in")
if archive_job.get("if") != "${{ inputs.publish-evidence }}":
failures.append("provider evidence can publish without an explicit request")
if _needs(archive_job) != {"paper"} or archive_job.get("runs-on") != "ubuntu-latest":
failures.append("provider evidence is not archived by a hosted job after qualification")
archive_text = json.dumps(archive_job, sort_keys=True)
Expand Down
12 changes: 7 additions & 5 deletions src/ml4t/live/brokers/alpaca.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
from alpaca.trading.client import TradingClient
from alpaca.trading.enums import OrderSide as AlpacaOrderSide
from alpaca.trading.enums import OrderStatus as AlpacaOrderStatus
from alpaca.trading.enums import PositionSide as AlpacaPositionSide
from alpaca.trading.enums import QueryOrderStatus, TimeInForce
from alpaca.trading.requests import (
GetOrdersRequest,
Expand Down Expand Up @@ -201,9 +202,6 @@ async def connect(self) -> None:

async def disconnect(self) -> None:
"""Disconnect from Alpaca."""
if not self._connected:
return

stream_task = self._stream_task
if self._trading_stream:
try:
Expand Down Expand Up @@ -769,8 +767,6 @@ async def _on_trade_update(self, data: Any) -> None:
return

if event == "fill":
if filled_quantity == 0:
filled_quantity = order.quantity
if (
filled_quantity != order.quantity
or not math.isfinite(filled_price)
Expand Down Expand Up @@ -906,6 +902,12 @@ async def _sync_positions(self) -> None:
raise RuntimeError("Alpaca positions snapshot contains an empty symbol")
if not math.isfinite(quantity) or quantity == 0:
raise RuntimeError("Alpaca positions snapshot contains an invalid quantity")
if pos.side == AlpacaPositionSide.SHORT:
quantity = -abs(quantity)
elif pos.side != AlpacaPositionSide.LONG:
raise RuntimeError("Alpaca positions snapshot contains an invalid side")
else:
quantity = abs(quantity)
if not math.isfinite(entry_price) or entry_price < 0:
raise RuntimeError("Alpaca positions snapshot contains an invalid entry price")
if not math.isfinite(current_price) or current_price < 0:
Expand Down
7 changes: 3 additions & 4 deletions src/ml4t/live/feeds/aggregator.py
Original file line number Diff line number Diff line change
Expand Up @@ -323,17 +323,16 @@ def _signal_stop(self) -> None:
self._queue.finish(discard=False)

def _truncate_to_bar(self, dt: datetime) -> datetime:
"""Truncate datetime to bar boundary.
"""Truncate datetime to a fixed UTC epoch-aligned bar boundary.

Args:
dt: Datetime to truncate

Returns:
Datetime truncated to bar boundary (e.g., 10:35:42 -> 10:35:00 for 1-min bars)
"""
minutes_per_bar = int(self.bar_size.total_seconds() // 60)
truncated_minutes = (dt.minute // minutes_per_bar) * minutes_per_bar
return dt.replace(minute=truncated_minutes, second=0, microsecond=0)
epoch = datetime(1970, 1, 1, tzinfo=UTC)
return epoch + ((dt - epoch) // self.bar_size) * self.bar_size

async def _emit_asset_bar(
self,
Expand Down
Loading
Loading