Skip to content
Merged
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
2 changes: 1 addition & 1 deletion docs/engine.md
Original file line number Diff line number Diff line change
Expand Up @@ -223,7 +223,7 @@ parent = await engine.submit(OrderRequest(
market_id=bucket.market_id, side=Side.BUY, quantity=Decimal("500"), type="market", price=Decimal("0.42"),
book="alpha", params={"min_stay_s": 5, "max_rounds": 20},
))
engine.orders.get(parent.id).report() # filled, average, per venue, why it stopped
engine.managed_order_controller.get(parent.id).report() # filled, average, per venue, why it stopped
await engine.bucket_position(bucket.id) # the ledger's positions, netted in bucket terms
```

Expand Down
2 changes: 1 addition & 1 deletion docs/rfq-combo.md
Original file line number Diff line number Diff line change
Expand Up @@ -140,7 +140,7 @@ An RFQ is sized by contracts (`quantity`) **or** money (`amount`), never both.

1. **Accept is an order, and returns at once.**
- Re-read the quote (it must be `open`), take the price for the requested outcome and action, and refuse if it is worse than `worst_price`. Size an `amount` here (§3).
- Build an `OrderRequest(type=rfq_taker, outcome, action, quantity, price, params={rfq_id, quote_id})` and run `engine.check`: the kill switch, a paused book, the restricted list, size and notional, position caps, closing soon, daily loss. The price collar is skipped, as for market orders, because combos rarely have a mark.
- Build an `OrderRequest(type=rfq_taker, outcome, action, quantity, price, params={rfq_id, quote_id})` and run `engine.evaluate_order_risk`: the kill switch, a paused book, the restricted list, size and notional, position caps, closing soon, daily loss. The price collar is skipped, as for market orders, because combos rarely have a mark.
- Journal the intent (`operation="accept_quote"`, `target_order_id=quote_id`, key `rfq-accept:{venue}:{quote_id}`), mark it `sending`, call the venue, and return the intent. A network error raises `SubmissionInDoubt` and leaves the intent `sending`.
- An optional `wait` (with `timeout_s`) blocks until the quote is terminal; the default does not wait.
2. **Results arrive as events.** A follower polls the quote (or reads the venue's stream):
Expand Down
12 changes: 6 additions & 6 deletions examples/verify_kalshi_stop_fill.py
Original file line number Diff line number Diff line change
Expand Up @@ -188,7 +188,7 @@ async def verify(adapter, guard, directory, market, initial_book, trigger, limit
parent = await engine.submit(request)
parent_id = parent.id
await engine.on_book({"market_id": market_id})
if engine.orders.get(parent_id).state != "waiting" or guard.sent:
if engine.managed_order_controller.get(parent_id).state != "waiting" or guard.sent:
raise RuntimeError("stop was not below its trigger when armed")
await engine.stop()
engine = engine_for(adapter, directory / "orders.db")
Expand All @@ -198,12 +198,12 @@ async def verify(adapter, guard, directory, market, initial_book, trigger, limit
report["waiting_restart_verified"] = True
guard.stage = "entry"
deadline = time.monotonic() + wait_s
while engine.orders.get(parent_id).state == "waiting" and time.monotonic() < deadline:
while engine.managed_order_controller.get(parent_id).state == "waiting" and time.monotonic() < deadline:
book = await book_for(adapter, guard.ticker)
engine.set_book(market_id, book)
await engine.on_book({"market_id": market_id})
await engine.orders.on_timer()
parent = engine.orders.get(parent_id)
await engine.managed_order_controller.on_timer()
parent = engine.managed_order_controller.get(parent_id)
if parent.state != "waiting":
report["observed_trigger_ask"] = str(book.best_ask)
break
Expand All @@ -218,7 +218,7 @@ async def verify(adapter, guard, directory, market, initial_book, trigger, limit
return report
await engine.on_order(entry)
report.update(entry_status=entry.status.value, entry_filled=str(entry.filled), entry_average_price=str(entry.average_price),
entry_cost=str(entry.cost), entry_fee=str(entry.fee), parent_status=engine.orders.get(parent_id).as_order().status.value)
entry_cost=str(entry.cost), entry_fee=str(entry.fee), parent_status=engine.managed_order_controller.get(parent_id).as_order().status.value)
fills = await adapter.fetch_my_trades(market_id=market_id, order_id=entry.id)
report["entry_fill_records"] = [{"quantity": str(f.quantity), "price": str(f.price), "fee": str(f.fee)} for f in fills]
if entry.filled <= 0:
Expand All @@ -245,7 +245,7 @@ async def verify(adapter, guard, directory, market, initial_book, trigger, limit
finally:
guard.stage = "blocked"
try:
if parent_id and engine.orders.get(parent_id) and engine.orders.get(parent_id).state == "waiting":
if parent_id and engine.managed_order_controller.get(parent_id) and engine.managed_order_controller.get(parent_id).state == "waiting":
await engine.cancel(parent_id)
# The exit is IOC and already terminal; the entry may rest. Read back and cancel only
# these keys even after a failed/ambiguous response. Never resend.
Expand Down
4 changes: 2 additions & 2 deletions examples/verify_kalshi_stop_limit.py
Original file line number Diff line number Diff line change
Expand Up @@ -180,7 +180,7 @@ async def verify(adapter: KalshiTrading, guard: OneOrderGuard, directory: Path)
await engine.on_book({"market_id": MARKET})
finally:
guard.enabled = False
managed = engine.orders.get(parent_id)
managed = engine.managed_order_controller.get(parent_id)
if managed.state != "working" or len(managed.children) != 1 or guard.sent is None:
raise RuntimeError("trigger did not create exactly one child")
child = managed.children[0]
Expand All @@ -194,7 +194,7 @@ async def verify(adapter: KalshiTrading, guard: OneOrderGuard, directory: Path)
finally:
guard.enabled = False
try:
managed = engine.orders.get(parent_id) if parent_id else None
managed = engine.managed_order_controller.get(parent_id) if parent_id else None
if managed is not None and managed.live and managed.children:
guard.cancel_ids.update(c.order_id for c in managed.children)
try:
Expand Down
14 changes: 7 additions & 7 deletions examples/verify_polymarket_us_stop_fill.py
Original file line number Diff line number Diff line change
Expand Up @@ -184,15 +184,15 @@ async def verify(adapter, guard, directory, rules, initial, trigger, *, wait_s=2
# persists waiting before the next actual venue book observation.
if not initially_eligible:
await engine.on_book({"market_id": market_id})
assert engine.orders.get(parent_id).state == "waiting" and not guard.sent
assert engine.managed_order_controller.get(parent_id).state == "waiting" and not guard.sent
guard.stage = "entry"
deadline = time.monotonic() + wait_s
while engine.orders.get(parent_id).state == "waiting" and time.monotonic() < deadline:
while engine.managed_order_controller.get(parent_id).state == "waiting" and time.monotonic() < deadline:
book = await book_for(adapter, guard.slug)
engine.set_book(market_id, book)
await engine.on_book({"market_id": market_id})
await engine.orders.on_timer()
if engine.orders.get(parent_id).state != "waiting":
await engine.managed_order_controller.on_timer()
if engine.managed_order_controller.get(parent_id).state != "waiting":
report["observed_trigger_ask"] = str(book.best_ask)
break
await asyncio.sleep(poll_s)
Expand All @@ -202,7 +202,7 @@ async def verify(adapter, guard, directory, rules, initial, trigger, *, wait_s=2
if "entry" in guard.sent:
raise RuntimeError("create response unresolved; do not resend or infer acceptance")
await engine.cancel(parent_id)
managed = engine.orders.get(parent_id)
managed = engine.managed_order_controller.get(parent_id)
report["outcome"] = ("triggered but local dispatch failed" if managed.state == "rejected"
else "no threshold crossing; no venue order")
report["parent_status"] = managed.as_order().status.value
Expand All @@ -211,7 +211,7 @@ async def verify(adapter, guard, directory, rules, initial, trigger, *, wait_s=2
entry = await terminal_order(adapter, oid, guard)
await engine.on_order(entry)
report.update(entry_status=entry.status.value, entry_filled=str(entry.filled), entry_average_price=str(entry.average_price),
entry_fee=str(entry.fee), parent_status=engine.orders.get(parent_id).as_order().status.value)
entry_fee=str(entry.fee), parent_status=engine.managed_order_controller.get(parent_id).as_order().status.value)
if entry.filled <= 0:
report["outcome"] = "triggered without a fill"
return report
Expand Down Expand Up @@ -246,7 +246,7 @@ async def verify(adapter, guard, directory, rules, initial, trigger, *, wait_s=2
finally:
guard.stage = "blocked"
try:
if parent_id and engine.orders.get(parent_id) and engine.orders.get(parent_id).state == "waiting":
if parent_id and engine.managed_order_controller.get(parent_id) and engine.managed_order_controller.get(parent_id).state == "waiting":
await engine.cancel(parent_id)
for stage, oid in guard.ids.items():
order = await terminal_order(adapter, oid, guard)
Expand Down
6 changes: 3 additions & 3 deletions synpath/engine/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -76,9 +76,9 @@ instructions silently. Unattributed venue observations with no original request
are preserved in `historical_order_records`, with their original identity and
snapshot still available through `typed_order`.

Back up and verify restore before upgrading an existing store. These revisions
refuse destructive downgrade; rollback requires compatible code and a verified
schema/data backup. Migrations are application-managed because they require
Back up and verify restore before upgrading an existing store. The owner migration refuses downgrade; later revision downgrades may remove
fields or recreate dropped tables empty. Rollback requires compatible code and
a verified schema/data backup. Migrations are application-managed because they require
owner selection and locking; standalone/offline Alembic without that context is
refused. Selecting PostgreSQL creates a new store. Importing an existing SQLite
journal, including armed orders, is a separate operation and is not automatic.
Expand Down
4 changes: 2 additions & 2 deletions synpath/engine/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@
from .journal import Intent, IntentState, Journal, JournalEvent, LeaseLost
from .storage import JournalStore, create_journal
from .ledger import Ledger, PositionState, Rollup
from .orders import ManagedOrder, ManagedOrders
from .orders import ManagedOrder, ManagedOrderController
from .risk import Decision, KillSwitch, RiskConfig, RiskEngine
from .rfq import RfqDesk

Expand All @@ -33,6 +33,6 @@
"EventBus", "EngineEvent", "Subscription",
"Ledger", "PositionState", "Rollup",
"RiskConfig", "RiskEngine", "Decision", "KillSwitch",
"ManagedOrder", "ManagedOrders",
"ManagedOrder", "ManagedOrderController",
"RfqDesk",
]
2 changes: 1 addition & 1 deletion synpath/engine/__main__.py
Original file line number Diff line number Diff line change
Expand Up @@ -240,7 +240,7 @@ async def _status_loop(engine: Engine, interval: float) -> None:
log.info(
"synpath.engine: %s open orders, %s positions, realized %s, fees %s, events %s, halted=%s",
len(engine.open_orders()), len(engine.ledger.open_positions()), total.realized, total.fees,
engine.bus.published, engine.risk.kill.engaged,
engine.bus.published, engine.order_risk_checks.kill.engaged,
)


Expand Down
Loading
Loading