Skip to content
Draft
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
114 changes: 114 additions & 0 deletions research/PHASE5_OFFLINE_ADAPTER.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,114 @@
# Phase 5 LongBridge offline research adapter

## Frozen scope

This slice compares the already frozen Phase 4 v3 QQQM/TQQQ/BOXX research
candidate with an isolated LongBridge offline representation. It does not
change LongBridge production execution, account, market-data, release, risk,
workflow or broker submission behavior. It does not grant paper, shadow or live
authority.

The exact write whitelist is:

- `research/phase5_offline_adapter.py`: a pure, unregistered research adapter;
- `tests/test_phase5_offline_adapter.py`: synthetic and approved-local-input checks;
- `research/PHASE5_OFFLINE_ADAPTER.md`: this scope and observed evidence.

The branch starts at LongBridgePlatform `origin/main`
`aa8432bd6d3c8dd8ed3a441241a146dd0ff1ca30`. Its locked shared package
refs are QPK `c7646a7168b3dafa763ef7751a182d23e8de7790` and UES
`8412d8338879f335ef8830678c2733e02e53e7c7`. Neither includes the
unmerged frozen Phase 4 v3 research candidate. The authorized research source
remains the separate UES worktree at
`aa555fa7cf04d85805de6301de468884755ccdde`, with Phase 4 v3 policy
SHA256 `95d19f727a5e7cae1d9ab5a4251d260acca0f5370ef075c85312a06887e2e20e`
and approved input manifest SHA256
`cb14a511083c824a748d137a271c93cfe0e8adf38f648905b26e37decf4c6182`.

The original LongBridge worktree has unrelated uncommitted changes and is
untouched. Inspected workflows run CI on pull requests; deployment and paper
candidate application require explicit dispatch, and automatic merge applies
only to eligible non-draft Dependabot pull requests. This branch will remain a
draft research PR.

## Starting diagnostic

The existing pure LongBridge paper mapper at
`application/paper_execution_command_consumer.py` had source SHA256
`71a6297f2dd0de678fa2e66c1b7ced3ee8983536643a6a15c28c96d29f56fe18`.
On the eight SHA-checked frozen 10 bps first-open paths, its value-delta
quantity matched no complete research whole-share set; all eight paths had at
least one fractional proposal, and one proposed a positive quantity where the
research ledger bought zero shares. No account, command consumer, execution
cycle or broker port was called. This diagnoses the pure mapper, not an actual
LongBridge fill or rejection.

The default native execution path also has distinct small-account bootstrap,
safe-haven cash substitution and limit-price funding rules. Those rules are
not implicitly changed to fit the frozen research candidate. The bounded
offline adapter must preserve the original research targets, member ownership,
whole-share quantities, costs and funding state separately from native order
semantics; unsupported states must remain explicit.

## Implemented conversion contract

`convert_funded_phase5_proposal(plan, funded)` accepts the frozen prior-close
plan and an already computed, open-price funded research proposal. It verifies
candidate and budget-policy identity, signal and execution dates, whole-share
targets, member ownership, buy order, filled quantities, cost at the declared
5/10/15 bps scenario, available settled cash, TQQQ member reserve, post-open
shares and cash, and the account wealth identity. It independently recomputes
the first-open funding bound from the supplied plan; it does not generate the
strategy target or run the historical core. The proposal cannot simply assert
success: altered trades, costs, cash, reserves, ownership or dates are rejected.

The output keeps the frozen USD and share targets, the explicitly unfilled
shares, research costs and simulated account state separate from a LongBridge
offline row. Each row has the `.US` platform symbol, buy side, positive whole
share quantity and `quantity_step=1`, open-price reference, proposed before and
after shares, and exposure direction. This row is not a native LongBridge
order, limit-price decision or broker fill. Zero-share targets produce no row.

This slice supports the cash-only first open with no existing holdings, sale
queue, receivables, option/collateral obligations or external flows. Other
states fail explicitly. It does not claim sale, T+2, dividend, corporate-action
reconstruction, full-window or production-platform equivalence. The verified
proposal is supplied separately; this adapter must not call the existing
fractional value-target mapper to recalculate it.

## Direct results

The approved local private source and daily ledgers were reused without new
acquisition. The eight predeclared first-open paths at 10 bps comprise four
principal scales and the enhanced/matched-defense pair. The test consumed the
reviewed Schwab pure research funding projection as an upstream calculation,
then independently checked its frozen plan and converted the result. All eight
LongBridge offline share sets matched the separately saved research ledger;
settled cash, executed cost and member reserve matched at `1e-6` USD
tolerance. One funded shortfall remained explicit. This is conversion fidelity
plus direct first-open accounting verification, not an independent rerun of the
funding strategy or a native LongBridge execution test.

Sixteen synthetic/negative direct test cases passed alongside the eight-path
opt-in private test. The cases cover repeated pure evaluation, source mutation
protection, whole-share output, zero-share omission, and rejection of altered
cost, post-state, reserve, owner, status, target or time. The ordinary CI test
skips the private case. No raw private prices, source rows or ledgers are part
of the branch or public PR artifacts.

Astra's preimplementation review recommended this bounded plan-plus-funded
proposal conversion instead of copying a third complete research funding
engine. It required independent verification of the original plan, arithmetic
and account state rather than trusting a success label. Final implementation
review and PR/CI evidence will be recorded after completion.

## Incremental independent review

Astra reviewed the three implemented files against the frozen research funding
contract and returned GO for this bounded offline conversion. It found no
blocking mismatch in the first-open buy order, whole shares, executed-cost
accounting, restricted cash, member reserve, account identity, candidate or
time checks. The review used source and synthetic evidence only; it did not
read or independently rerun private daily data. The eight-path conversion
result above comes from the direct approved-local test. Native LongBridge
execution, complete history and production authorization remain unverified.
231 changes: 231 additions & 0 deletions research/phase5_offline_adapter.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,231 @@
"""Verify and represent a frozen Phase 5 funding proposal for offline LongBridge research."""

from __future__ import annotations

import math
from datetime import date
from typing import Any, Mapping

SYMBOLS = ("TQQQ", "QQQM", "BOXX")
OWNERS = {"TQQQ": "tqqq_core", "QQQM": "outer", "BOXX": "outer"}
CANDIDATE_IDS = frozenset({
"qqqm_tqqq_guard_boxx_fixed_research_v3",
"qqqm_boxx_matched_defense_research_v3",
"qqqm_tqqq_guard_boxx_fixed_scale_research_v1",
"qqqm_boxx_matched_defense_scale_research_v1",
})
POLICY_VERSION = "fixed_45_05_49_01_vs_50_00_49_01_restricted_paid_cash_v3"
TOLERANCE_USD = 1e-6


def _number(value: Any, field: str) -> float:
if isinstance(value, bool):
raise ValueError(f"{field}: invalid number")
try:
number = float(value)
except (TypeError, ValueError) as exc:
raise ValueError(f"{field}: invalid number") from exc
if not math.isfinite(number) or number < 0:
raise ValueError(f"{field}: must be finite and nonnegative")
return number


def _integer(value: Any, field: str) -> int:
number = _number(value, field)
if number != int(number):
raise ValueError(f"{field}: whole shares required")
return int(number)


def _same_money(actual: Any, expected: float, field: str) -> None:
if abs(_number(actual, field) - expected) > TOLERANCE_USD:
raise ValueError(f"{field}: funded proposal mismatch")


def _same_symbols(actual: Mapping[str, Any], expected: Mapping[str, int], field: str) -> None:
if set(actual) != set(SYMBOLS) or any(
_integer(actual[symbol], f"{field}.{symbol}") != expected[symbol]
for symbol in SYMBOLS
):
raise ValueError(f"{field}: funded proposal mismatch")


def convert_funded_phase5_proposal(plan: Mapping[str, Any], funded: Mapping[str, Any]) -> dict:
"""Check a frozen funded research step before making LongBridge-shaped evidence.

No broker object, market-data port, release receipt, or order object is
accepted. The returned rows are offline research representations only.
"""
if plan.get("candidate_id") not in CANDIDATE_IDS or plan.get("budget_policy_version") != POLICY_VERSION:
raise ValueError("PHASE5_IDENTITY")
if funded.get("candidate_id") != plan["candidate_id"]:
raise ValueError("PHASE5_FUNDED_IDENTITY")
signal = date.fromisoformat(str(plan["signal_date"]))
execution = date.fromisoformat(str(plan["execution_date"]))
quote = date.fromisoformat(str(plan["quote_date"]))
if not signal < execution or quote != execution:
raise ValueError("PHASE5_TIME_CAUSALITY")
if funded.get("signal_date") != signal.isoformat() or funded.get("execution_date") != execution.isoformat():
raise ValueError("PHASE5_FUNDED_TIME")
if plan.get("company_actions_applied") is not True:
raise ValueError("PHASE5_COMPANY_ACTIONS")
if funded.get("scope") != "offline_research_open_price_simulation_only":
raise ValueError("PHASE5_FUNDED_SCOPE")
if funded.get("status") not in {"FEASIBLE_RESEARCH_PROPOSAL", "FUNDED_SHORTFALL"}:
raise ValueError("PHASE5_FUNDED_STATUS")
if type(plan.get("session_index")) is not int or plan["session_index"] < 0:
raise ValueError("PHASE5_SESSION_INDEX")
for field in ("option_liability_usd", "collateral_usd", "external_cash_flow_usd"):
if _number(plan.get(field, 0), field):
raise ValueError(f"PHASE5_UNSUPPORTED_{field.upper()}")

prices = {symbol: _number(plan["open_prices_usd"][symbol], f"open_prices_usd.{symbol}")
for symbol in SYMBOLS}
if set(plan["open_prices_usd"]) != set(SYMBOLS) or any(not price for price in prices.values()):
raise ValueError("PHASE5_PRICES")
if set(plan["account_shares"]) != set(SYMBOLS):
raise ValueError("PHASE5_ACCOUNT_SYMBOLS")
before = {symbol: _integer(plan["account_shares"][symbol], f"account_shares.{symbol}")
for symbol in SYMBOLS}
targets_by_owner = plan["target_usd_by_owner"]
positions_by_owner = plan["positions_shares_by_owner"]
if set(targets_by_owner) != set(OWNERS.values()) or set(positions_by_owner) != set(OWNERS.values()):
raise ValueError("PHASE5_OWNER_SET")
targets = {}
target_shares = {}
for owner in ("tqqq_core", "outer"):
if set(targets_by_owner[owner]) - set(SYMBOLS) or set(positions_by_owner[owner]) - set(SYMBOLS):
raise ValueError("PHASE5_OWNER_SYMBOLS")
targets[owner] = {symbol: _number(targets_by_owner[owner].get(symbol, 0),
f"targets.{owner}.{symbol}") for symbol in SYMBOLS}
target_shares[owner] = {symbol: math.floor(targets[owner][symbol] / prices[symbol])
for symbol in SYMBOLS}
for symbol in SYMBOLS:
member_before = _integer(positions_by_owner[owner].get(symbol, 0),
f"positions.{owner}.{symbol}")
if owner != OWNERS[symbol] and (member_before or targets[owner][symbol]):
raise ValueError("PHASE5_UNSUPPORTED_MEMBER_OWNERSHIP")
if any(before[symbol] != sum(_integer(positions_by_owner[owner].get(symbol, 0),
f"positions.{owner}.{symbol}")
for owner in ("tqqq_core", "outer")) for symbol in SYMBOLS):
raise ValueError("PHASE5_MEMBER_ACCOUNT_IDENTITY")
if funded.get("target_usd_by_owner") != targets or funded.get("target_shares_by_owner") != target_shares:
raise ValueError("PHASE5_FUNDED_TARGET")
total_target = {symbol: sum(target_shares[owner][symbol] for owner in ("tqqq_core", "outer"))
for symbol in SYMBOLS}
fee_bps = _number(plan["cost_bps_per_side"], "cost_bps_per_side")
if fee_bps not in {5, 10, 15}:
raise ValueError("PHASE5_UNSUPPORTED_COST")
rate = fee_bps / 10_000.0
budget = _number(plan["tqqq_member_budget_usd"], "tqqq_member_budget_usd")
cash_target = _number(plan["outer_cash_target_usd"], "outer_cash_target_usd")
if targets["tqqq_core"]["TQQQ"] > budget + TOLERANCE_USD:
raise ValueError("PHASE5_MEMBER_TARGET_BUDGET")
cash = _number(plan["settled_cash_usd"], "settled_cash_usd")
restricted = _number(plan["restricted_paid_cash_usd"], "restricted_paid_cash_usd")
if restricted > cash + TOLERANCE_USD:
raise ValueError("PHASE5_RESTRICTED_CASH")
if plan["pending_sales"] or plan["unpaid_receivables"]:
raise ValueError("PHASE5_UNSUPPORTED_PENDING_OR_RECEIVABLE")
if any(before.values()):
raise ValueError("PHASE5_UNSUPPORTED_EXISTING_HOLDINGS")

# This first-open slice verifies a previously funded proposal. It does not
# recreate the strategy's budget or state machine.
expected_rows = []
shares = dict(before)
costs = {symbol: 0.0 for symbol in SYMBOLS}
shortfalls = {}
for symbol in SYMBOLS:
gap = total_target[symbol] - shares[symbol]
reserve = 0.0 if symbol == "TQQQ" else max(0.0, budget - shares["TQQQ"] * prices["TQQQ"])
available = max(0.0, cash - restricted - cash_target - reserve)
affordable = math.floor((available + 1e-9) / (prices[symbol] * (1.0 + rate)))
fill = min(gap, affordable)
if fill:
cost = fill * prices[symbol] * rate
expected_rows.append({"symbol": symbol, "side": "buy", "shares": fill,
"owner": OWNERS[symbol], "open_price_usd": prices[symbol],
"research_cost_usd": cost})
cash -= fill * prices[symbol] + cost
costs[symbol] = cost
shares[symbol] += fill
if fill < gap:
shortfalls[symbol] = {"target_shares": total_target[symbol],
"proposed_shares": shares[symbol],
"reason": "insufficient_free_settled_cash"}
actual_rows = tuple(funded["simulated_trades"])
if len(actual_rows) != len(expected_rows):
raise ValueError("PHASE5_FUNDED_TRADES")
for actual, expected in zip(actual_rows, expected_rows, strict=True):
if type(actual.get("shares")) is not int or actual["shares"] <= 0:
raise ValueError("PHASE5_FUNDED_WHOLE_SHARES")
if set(actual) != set(expected) or any(
actual[key] != expected[key] for key in ("symbol", "side", "shares", "owner")
):
raise ValueError("PHASE5_FUNDED_TRADES")
_same_money(actual["open_price_usd"], expected["open_price_usd"], "trade.open_price_usd")
_same_money(actual["research_cost_usd"], expected["research_cost_usd"],
"trade.research_cost_usd")
if funded.get("unfilled_target_shares") != shortfalls or funded["status"] != (
"FUNDED_SHORTFALL" if shortfalls else "FEASIBLE_RESEARCH_PROPOSAL"
):
raise ValueError("PHASE5_FUNDED_SHORTFALL")
state = funded["simulated_post_open_state"]
_same_symbols(state["account_shares"], shares, "post.account_shares")
for owner in ("tqqq_core", "outer"):
expected_member = {symbol: shares[symbol] if OWNERS[symbol] == owner else 0
for symbol in SYMBOLS}
_same_symbols(state["positions_shares_by_owner"][owner], expected_member,
f"post.positions.{owner}")
if set(state["positions_shares_by_owner"]) != {"tqqq_core", "outer"}:
raise ValueError("PHASE5_FUNDED_OWNER_SET")
if state["pending_sales"] or state["unpaid_receivables"]:
raise ValueError("PHASE5_FUNDED_UNSUPPORTED_STATE")
_same_money(state["settled_cash_usd"], cash, "post.settled_cash_usd")
_same_money(state["restricted_paid_cash_usd"], restricted, "post.restricted_paid_cash_usd")
if set(funded["net_account_proposed_deltas"]) != set(SYMBOLS) or any(
_integer(funded["net_account_proposed_deltas"][symbol], f"delta.{symbol}") != shares[symbol]
for symbol in SYMBOLS
):
raise ValueError("PHASE5_FUNDED_ACCOUNT_DELTA")
if set(funded["research_cost_usd_by_symbol"]) != set(SYMBOLS):
raise ValueError("PHASE5_FUNDED_COST_SYMBOLS")
for symbol in SYMBOLS:
_same_money(funded["research_cost_usd_by_symbol"][symbol], costs[symbol], f"cost.{symbol}")
total_cost = math.fsum(costs.values())
_same_money(funded["research_total_cost_usd"], total_cost, "research_total_cost_usd")
reserve_after = max(0.0, budget - shares["TQQQ"] * prices["TQQQ"])
_same_money(funded["member_reserved_cash_after_usd"], reserve_after,
"member_reserved_cash_after_usd")
_same_money(funded["outer_free_settled_cash_after_usd"],
max(0.0, cash - restricted - reserve_after), "outer_free_settled_cash_after_usd")
prior_nav = _number(plan["settled_cash_usd"], "settled_cash_usd")
after_nav = cash + math.fsum(shares[symbol] * prices[symbol] for symbol in SYMBOLS)
_same_money(funded["pre_open_account_nav_usd"], prior_nav, "pre_open_account_nav_usd")
_same_money(funded["simulated_post_open_account_nav_usd"], after_nav,
"simulated_post_open_account_nav_usd")
identity_error = abs(prior_nav - total_cost - after_nav)
_same_money(funded["account_identity_error_usd"], identity_error,
"account_identity_error_usd")
_same_money(funded["released_pending_sale_usd"], 0.0, "released_pending_sale_usd")
if identity_error > TOLERANCE_USD:
raise ValueError("PHASE5_ACCOUNT_IDENTITY")

rows = tuple({"symbol": row["symbol"], "platform_symbol": f"{row['symbol']}.US",
"side": "buy", "quantity": row["shares"], "quantity_step": 1,
"owner": row["owner"], "reference_open_price_usd": row["open_price_usd"],
"reference_notional_usd": row["shares"] * row["open_price_usd"],
"research_cost_usd": row["research_cost_usd"],
"before_shares": before[row["symbol"]], "proposed_after_shares": shares[row["symbol"]],
"exposure_effect": "increases"} for row in expected_rows)
return {"status": funded["status"], "candidate_id": plan["candidate_id"],
"signal_date": signal.isoformat(), "execution_date": execution.isoformat(),
"target_usd_by_owner": targets, "target_shares_by_owner": target_shares,
"offline_proposals": rows, "unfilled_target_shares": shortfalls,
"research_total_cost_usd": total_cost,
"simulated_post_open_state": {"account_shares": shares, "settled_cash_usd": cash,
"member_reserved_cash_usd": reserve_after,
"restricted_paid_cash_usd": restricted},
"account_identity_error_usd": identity_error,
"scope": "longbridge_offline_research_conversion_only"}
Loading
Loading