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
3 changes: 2 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ AlgoLens and the engine (trade-ngin) **talk directly over gRPC**, and Postgres s

| Direction | How | What |
|---|---|---|
| AlgoLens → engine | gRPC to `engine-rpc:50051` on the private Docker network `qt` (env `ENGINE_RPC_ADDR`; `DESK_AGENT_ADDR` is still read for one release; 3 s timeout) | The desk service `algogators.desk.DeskService`: `RunDesk` (a save), `RequestOverride`, `RecordDecision`, `Publish`, `Rescind`, `GetRunStatus`. Contract: `algolens-api/proto/algogators/desk.proto`, vendored byte for byte from trade-ngin `proto/algogators/desk.proto` |
| AlgoLens → engine | gRPC to `engine-rpc:50051` on the private Docker network `qt` (env `ENGINE_RPC_ADDR`; `DESK_AGENT_ADDR` is still read for one release; 3 s timeout) | The desk service `algogators.desk.DeskService`: `RunDesk` (a save), `RequestOverride`, `RecordDecision`, `Publish`, `Rescind`, `GetRunStatus`, `GetDeskRisk`. Contract: `algolens-api/proto/algogators/desk.proto`, vendored byte for byte from trade-ngin `proto/algogators/desk.proto` |
| Engine → AlgoLens | Postgres (`new_algo_data`) | The three books in `trading.positions` (`system`, `qt_proposal`, `qt`), the command log `trading.position_overrides` (status and result of every command), and `live_run_metadata.published_by` / `published_at` / `settings_used`, plus `publish_source` / `sent_at` once trade-ngin migration 026 is applied (read as NULL before) |

**How a command works:**
Expand All @@ -40,6 +40,7 @@ AlgoLens and the engine (trade-ngin) **talk directly over gRPC**, and Postgres s
- **E-mail:** override approval links go to the VP and the President (env `QT_APPROVERS`, set on both sides). The daily book e-mails come from the engine.
- **Daily cutoff (America/New_York, every calendar day):** the desk **approves** the day (the UI's Approve button; command kind `publish`, RPC `Publish`) by 09:30. Approved books are e-mailed at 09:30; an approval between 09:30 and 10:00 is e-mailed at once. At 10:00 an unapproved day gets the model's book, published (`publish_source='fallback'`) and e-mailed by the engine. From 10:00 AlgoLens refuses (409) to approve, save, request or decide an override for that day; the engine refuses too. The desk banner counts down on the server's clock (`serverTime` and `deadlines` in the desk state).
- **Rescind approval (contract C8, trade-ngin migration 029):** before 09:30 New York the desk may take back its own unsent approval (command kind `rescind`, RPC `Rescind`). The engine clears `published_by` / `published_at` / `publish_source`, so the day takes edits and a new approval again. AlgoLens refuses (409) from 09:30, on an engine publish (fallback, model-only), once sent, and while a publish or rescind is open; the engine refuses too.
- **Risk check (contract C9, desk.proto 1.3.0):** a desk save runs the engine's risk pass on the desk's positions. AlgoLens reads what it did with `GetDeskRisk` (read-only) and shows it on the desk and on the override approval page: within or outside the limits, the limit that bound, the stored book's leverage and margin against the configured limits, and the symbols each step moved. A breach is a risk limit that bound (`cap`, `overlay`, `clip`, `trim`, the overlay cutting the book, a term still over its limit); trading rules (`sign_close`, `rounding`, `buffer`, `search`, `hold`) are shown, not blocking. **Approve is refused (409)** while a save is still running, while the report is of an older save than the latest done one, while the positions breach a limit and no override was approved, and while the check cannot be read (fail closed). The desk then saves within the limits or requests an override; an approved override books the ask exactly and passes. The engine's fallback publish is unchanged.
- **Feature flag:** the desk UI and its write routes sit behind `QT_DESK_ENABLED`.
- **Spec:** trade-ngin `docs/design/qt-contract.md` and `docs/design/qt-master-rulings.md`. The gRPC channel amends the master document's original "never call each other" (decided 2026-10-09).

Expand Down
30 changes: 21 additions & 9 deletions algolens-api/algogators/desk_pb2.py

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

59 changes: 59 additions & 0 deletions algolens-api/algogators/desk_pb2_grpc.py
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,9 @@ class DeskServiceStub:
after 10:00 (or on a published day) it is refused. No field changed.
1.2.0 2026-10-13 Rescind (contract C8): the desk takes back its own approval before the
09:30 send. New RPC and message only; no field changed.
1.3.0 2026-10-14 GetDeskRisk (contract C9): the risk report of the day's qt book and
whether the desk's positions breach a risk limit. New RPC and messages
only; no field changed.

"""

Expand Down Expand Up @@ -78,6 +81,11 @@ def __init__(self, channel):
request_serializer=algogators_dot_desk__pb2.RunStatusRequest.SerializeToString,
response_deserializer=algogators_dot_desk__pb2.RunStatus.FromString,
_registered_method=True)
self.GetDeskRisk = channel.unary_unary(
'/algogators.desk.DeskService/GetDeskRisk',
request_serializer=algogators_dot_desk__pb2.DeskRiskRequest.SerializeToString,
response_deserializer=algogators_dot_desk__pb2.DeskRisk.FromString,
_registered_method=True)


class DeskServiceServicer:
Expand All @@ -94,6 +102,9 @@ class DeskServiceServicer:
after 10:00 (or on a published day) it is refused. No field changed.
1.2.0 2026-10-13 Rescind (contract C8): the desk takes back its own approval before the
09:30 send. New RPC and message only; no field changed.
1.3.0 2026-10-14 GetDeskRisk (contract C9): the risk report of the day's qt book and
whether the desk's positions breach a risk limit. New RPC and messages
only; no field changed.

"""

Expand Down Expand Up @@ -155,6 +166,19 @@ def GetRunStatus(self, request, context):
context.set_details('Method not implemented!')
raise NotImplementedError('Method not implemented!')

def GetDeskRisk(self, request, context):
"""Since 1.3.0 (contract C9). The risk report of the day's qt book: what the engine's last run of
it (the model run, a desk save, an approved override) recorded in trading.live_results
risk_detail, the configured limits, and whether the desk's positions breach a risk limit (a
symbol moved by cap, overlay, clip or trim; the overlay cutting the book; the book still over
a limit). Trading rules (sign_close, rounding, buffer, search, hold) are reported, not
breaches. Read-only. AlgoLens refuses the desk's approval while within_limits is false and no
override was approved.
"""
context.set_code(grpc.StatusCode.UNIMPLEMENTED)
context.set_details('Method not implemented!')
raise NotImplementedError('Method not implemented!')


def add_DeskServiceServicer_to_server(servicer, server):
rpc_method_handlers = {
Expand Down Expand Up @@ -188,6 +212,11 @@ def add_DeskServiceServicer_to_server(servicer, server):
request_deserializer=algogators_dot_desk__pb2.RunStatusRequest.FromString,
response_serializer=algogators_dot_desk__pb2.RunStatus.SerializeToString,
),
'GetDeskRisk': grpc.unary_unary_rpc_method_handler(
servicer.GetDeskRisk,
request_deserializer=algogators_dot_desk__pb2.DeskRiskRequest.FromString,
response_serializer=algogators_dot_desk__pb2.DeskRisk.SerializeToString,
),
}
generic_handler = grpc.method_handlers_generic_handler(
'algogators.desk.DeskService', rpc_method_handlers)
Expand All @@ -210,6 +239,9 @@ class DeskService:
after 10:00 (or on a published day) it is refused. No field changed.
1.2.0 2026-10-13 Rescind (contract C8): the desk takes back its own approval before the
09:30 send. New RPC and message only; no field changed.
1.3.0 2026-10-14 GetDeskRisk (contract C9): the risk report of the day's qt book and
whether the desk's positions breach a risk limit. New RPC and messages
only; no field changed.

"""

Expand Down Expand Up @@ -374,3 +406,30 @@ def GetRunStatus(request,
timeout,
metadata,
_registered_method=True)

@staticmethod
def GetDeskRisk(request,
target,
options=(),
channel_credentials=None,
call_credentials=None,
insecure=False,
compression=None,
wait_for_ready=None,
timeout=None,
metadata=None):
return grpc.experimental.unary_unary(
request,
target,
'/algogators.desk.DeskService/GetDeskRisk',
algogators_dot_desk__pb2.DeskRiskRequest.SerializeToString,
algogators_dot_desk__pb2.DeskRisk.FromString,
options,
channel_credentials,
insecure,
call_credentials,
compression,
wait_for_ready,
timeout,
metadata,
_registered_method=True)
Loading
Loading