Multi-domain actuation core: trading, Google Workspace, comms, documents — plus PyStator and PyGubernator bridges
Project description
PyActuator
The actuation core of the Optophi family. PyActuator is a stateless, multi-domain Python package for performing side-effecting operations on external systems on behalf of strategy, state-machine, and agent components. Trading is one domain among several — Google Workspace, Comms (Slack / Email), and Documents (PDF / Markdown / HTML) are first-class siblings.
Features
- Multi-domain —
pyactuator.domains.{trading,google,comms,documents}, each with the same shape:types,protocols,errors,adapters/. - Stateless core —
pyactuator.coreships envelopes, idempotency, retry, audit, policy, registry, and spec primitives. No I/O. - Adapter pattern — every external system is an adapter behind a
Protocol. Mock adapters ship with every domain. - Bridges — install pyactuator operations into PyStator state
machines (
integrations.pystator) and PyGubernator tool registries (integrations.pygubernator) with one call. - HTTP API — domain-scoped FastAPI routers under
/api/v1/<domain>/..., sharing a singleAdapterRegistry. - Optional deps —
alpaca-py,httpx,google-auth,jinja2,markdown-it-py,reportlabare all optional extras. The core has zero mandatory dependencies. - Backwards compatible — existing
from pyactuator import OrderRequestimports continue to work as re-exports of the trading domain.
Installation
# Core only (envelopes, registry, mock adapters)
pip install pyactuator
# Trading with Alpaca broker
pip install pyactuator[trading-alpaca]
# Google Workspace (Drive / Sheets / Docs)
pip install pyactuator[google]
# Slack via webhook or bot token
pip install pyactuator[comms-slack]
# PDF + Markdown rendering
pip install pyactuator[documents-pdf,documents-md]
# Everything
pip install pyactuator[all]
# Development
pip install -e ".[dev]"
Documentation
Published docs (Material for MkDocs) live at optophi.github.io/pyactuator.
To build or preview locally:
pip install pyactuator[docs] # or: pip install -e ".[dev]" (includes docs tooling)
mkdocs serve
The site configuration mirrors PyStator / PyCharter: mkdocs.yml, mkdocs.optophi.yml for the Optophi.com docs deployment, and docs/ as the documentation root. CI runs mkdocs build --strict when mkdocs.yml is present (see ./scripts/ci.sh).
Quick start: Trading (mock broker)
import asyncio
from decimal import Decimal
from pyactuator import OrderRequest, Side, OrderType, TimeInForce
from pyactuator.domains.trading.adapters.mock import MockExecutionClient
async def main() -> None:
client = MockExecutionClient()
response = await client.submit(OrderRequest(
client_order_id="my-order-001",
symbol="AAPL",
side=Side.BUY,
quantity=Decimal("10"),
order_type=OrderType.MARKET,
time_in_force=TimeInForce.DAY,
))
print(response.success, response.external_order_id)
status = await client.get_status(response.external_order_id)
print(status.status) # ExecutionStatus.FILLED
await client.close()
asyncio.run(main())
Quick start: Google Sheets (mock)
import asyncio
from pyactuator.domains.google.types import AppendRowsRequest
from pyactuator.domains.google.adapters.mock import MockSheetsClient
async def main() -> None:
sheets = MockSheetsClient()
await sheets.create_spreadsheet(spreadsheet_id="sheet-1", title="Daily PnL")
await sheets.append_rows(AppendRowsRequest(
spreadsheet_id="sheet-1",
range="Sheet1!A1",
values=[["2025-05-07", "AAPL", 1234.56]],
))
asyncio.run(main())
Quick start: PyStator action
PyActuator's PyStator bridge installs every domain operation as a
kwargs-aware action callable on a pystator.actions.ActionRegistry.
State machines call them by name from YAML, with parameters templated
from the FSM context.
import asyncio
from pystator.actions import ActionRegistry, ActionExecutor
from pystator.actions.types import ActionSpec
from pyactuator.core.audit import MemoryAuditWriter
from pyactuator.core.registry import AdapterRegistry
from pyactuator.domains.trading.adapters.mock import MockExecutionClient
from pyactuator.integrations.pystator import register_pyactuator_actions
async def main() -> None:
adapters = AdapterRegistry()
adapters.register("trading", "broker", MockExecutionClient())
audit = MemoryAuditWriter()
registry = ActionRegistry()
register_pyactuator_actions(registry, adapters=adapters, audit_writer=audit)
executor = ActionExecutor(registry)
result = await executor.execute_action_spec(
ActionSpec(
name="pyactuator.trading.place_order",
params={
"symbol": "AAPL",
"side": "buy",
"quantity": 1,
"order_type": "market",
"client_order_id": "coid-1",
},
),
ctx={},
)
print(result["success"], result["result"]["external_order_id"])
print("audit records:", len(audit.records()))
asyncio.run(main())
Quick start: PyGubernator tool
from pyactuator.core.registry import AdapterRegistry
from pyactuator.domains.trading.adapters.mock import MockExecutionClient
from pyactuator.integrations.pygubernator import (
iter_pyactuator_tool_specs,
register_pyactuator_tools,
)
from pygubernator.tools import ToolRegistry
adapters = AdapterRegistry()
adapters.register("trading", "broker", MockExecutionClient())
tools = ToolRegistry()
register_pyactuator_tools(tools, adapters=adapters)
for spec in iter_pyactuator_tool_specs():
print(spec.name, spec.metadata.side_effects, spec.metadata.idempotent)
Architecture
┌────────────────────────────────────────────────────────────┐
│ Callers │
│ ───────────────────────────────────────────────── │
│ PyStator FSMs PyGubernator agents HTTP clients │
└────────────┬──────────────┬──────────────┬─────────────────┘
│ │ │
actions tools /api/v1/<domain>
│ │ │
┌────────────▼──────────────▼──────────────▼─────────────────┐
│ pyactuator │
│ ┌──────────────────────────────────────────────────────┐ │
│ │ core: envelopes • idempotency • retry • audit │ │
│ │ policy • errors • registry • spec │ │
│ └──────────────────────────────────────────────────────┘ │
│ ┌────────────┐ ┌──────────┐ ┌──────────┐ ┌─────────────┐ │
│ │ trading │ │ google │ │ comms │ │ documents │ │
│ │ Alpaca/Mock│ │Drive/Sheets│ │Slack/Email│ │PDF/MD/HTML │ │
│ └────────────┘ └──────────┘ └──────────┘ └─────────────┘ │
│ ┌──────────────────────────────────────────────────────┐ │
│ │ integrations: pystator • pygubernator │ │
│ └──────────────────────────────────────────────────────┘ │
└────────────────────────────────────────────────────────────┘
│
┌────────┴────────┬────────────┬───────────┐
▼ ▼ ▼ ▼
Broker API Google APIs Slack API SMTP / SendGrid
(Alpaca, IB) (Drive/Sheets)
See ARCHITECTURE.md for the full layer model and
docs/guides/ for the per-topic guides:
domains-overview.md— what domains ship today and which surface to call them through.adding-a-domain.md— step-by-step recipe for adding a new domain.bridges.md— PyStator and PyGubernator bridge usage.
Domains shipped today
| Domain | Operations | Adapters |
|---|---|---|
trading |
submit, get_status, cancel, subscribe_fills, get_positions, get_account |
MockExecutionClient, AlpacaExecutionClient |
google |
Sheets append/read, Drive upload, Docs replace text / create | Mock + HTTPX-backed real adapters |
comms |
Slack send (webhook / API), Email send (SMTP / SendGrid) | Mock + real adapters |
documents |
Render templated PDF / Markdown / HTML | MockDocumentRenderer, JinjaMarkdownRenderer, ReportlabPdfRenderer |
Trading: error hierarchy
The trading domain still exposes its full error hierarchy from the
package root (re-exported from pyactuator.domains.trading.errors):
PyActuatorError
├── OrderValidationError # bad OrderRequest fields
├── OrderRejectedError # broker explicitly rejected
├── OrderNotFoundError # get_status / cancel on unknown order
├── BrokerConnectionError # network / timeout (retryable)
└── BrokerAdapterError # adapter misconfiguration
from pyactuator import (
PyActuatorError,
OrderNotFoundError,
BrokerConnectionError,
)
try:
status = await client.get_status("unknown-id")
except OrderNotFoundError:
print("Order does not exist at broker")
except BrokerConnectionError:
print("Transient failure — safe to retry")
except PyActuatorError:
print("Catch-all for any pyactuator error")
Adapter registry
All bridges and the FastAPI app share a single
pyactuator.core.registry.AdapterRegistry keyed on (domain, kind):
from pyactuator.core.registry import AdapterRegistry
from pyactuator.domains.trading.adapters.mock import MockExecutionClient
from pyactuator.domains.comms.adapters.mock import MockSlackClient
adapters = AdapterRegistry()
adapters.register("trading", "broker", MockExecutionClient())
adapters.register("comms", "slack", MockSlackClient())
broker = adapters.resolve("trading", "broker")
slack = adapters.resolve("comms", "slack")
Swapping a real adapter for a mock at startup is a one-liner.
Audit
Every bridge call writes an AuditRecord through an injected
AuditWriter:
from pyactuator.core.audit import MemoryAuditWriter
audit = MemoryAuditWriter()
register_pyactuator_actions(action_registry, adapters=adapters, audit_writer=audit)
for record in audit.records():
print(record.kind, record.action, record.success)
NullAuditWriter is the default; production deployments wire their own
writer (e.g. backed by a database or a log shipper).
HTTP API
Each domain ships a FastAPI router under /api/v1/<domain>/...:
| Route | Purpose |
|---|---|
POST /api/v1/orders |
Submit an order |
GET /api/v1/orders/{id} |
Order status |
POST /api/v1/google/sheets/append |
Append rows to a sheet |
POST /api/v1/google/drive/upload |
Upload a file to Drive |
POST /api/v1/google/docs/replace-text |
Replace text in a Google Doc |
POST /api/v1/comms/slack/send |
Send a Slack message |
POST /api/v1/comms/email/send |
Send an email |
POST /api/v1/documents/render |
Render a templated document |
Adapters resolve from app.state.adapter_registry. See
pyactuator.api.main for the wiring and pyactuator.api.dependencies.*
for the resolvers.
API reference (trading)
Protocols
| Protocol | Methods |
|---|---|
ExecutionClient |
submit, get_status, cancel, subscribe_fills, close |
BrokerQueryClient |
get_positions, get_position, get_account |
Types
| Type | Purpose |
|---|---|
OrderRequest |
Submit order params (validates on construction) |
OrderResponse |
Submission result (success, external_order_id, status) |
OrderStatus |
Full order status from broker |
Fill |
Execution/fill report |
CancelResponse |
Cancellation result |
Position |
Current position for a symbol |
Account |
Brokerage account info |
Enums
| Enum | Values |
|---|---|
Side |
BUY, SELL |
OrderType |
MARKET, LIMIT, STOP, STOP_LIMIT |
TimeInForce |
DAY, GTC, IOC, FOK, OPG, CLS |
ExecutionStatus |
PENDING_NEW, OPEN, PARTIALLY_FILLED, FILLED, CANCELED, REJECTED, EXPIRED |
Adapters
| Adapter | Install | Protocols |
|---|---|---|
MockExecutionClient |
core | ExecutionClient + BrokerQueryClient |
AlpacaExecutionClient |
pyactuator[trading-alpaca] |
ExecutionClient + BrokerQueryClient |
RetryExecutionClient |
core | wraps any ExecutionClient with retry |
License
MIT.
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file pyactuator-0.0.6.tar.gz.
File metadata
- Download URL: pyactuator-0.0.6.tar.gz
- Upload date:
- Size: 760.0 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.13
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
63d34bcbb6cd5361952fbe3860707f8a865d751b442d8be90b04fbddbdde2fc2
|
|
| MD5 |
9cd8ed9a0f1fd8c6e3c652d0d608abcb
|
|
| BLAKE2b-256 |
5fb548bdd369c22cbd52afdc4f8ff7ac4e9bd1bc3d6697944700559d3bca6f4d
|
Provenance
The following attestation bundles were made for pyactuator-0.0.6.tar.gz:
Publisher:
publish.yml on optophi/pyactuator
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
pyactuator-0.0.6.tar.gz -
Subject digest:
63d34bcbb6cd5361952fbe3860707f8a865d751b442d8be90b04fbddbdde2fc2 - Sigstore transparency entry: 1485885541
- Sigstore integration time:
-
Permalink:
optophi/pyactuator@b09f0f85995fe68c839f14ea523e56cfdfb816d0 -
Branch / Tag:
refs/tags/v0.0.6 - Owner: https://github.com/optophi
-
Access:
private
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish.yml@b09f0f85995fe68c839f14ea523e56cfdfb816d0 -
Trigger Event:
push
-
Statement type:
File details
Details for the file pyactuator-0.0.6-py3-none-any.whl.
File metadata
- Download URL: pyactuator-0.0.6-py3-none-any.whl
- Upload date:
- Size: 845.8 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.13
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
7b9ea0a2fe1270fe46bc83a888b331be55aeb12aa8417632f373e5b7bef42fab
|
|
| MD5 |
cf0f4b31397e8ad07b063f47ecd40927
|
|
| BLAKE2b-256 |
b7adc9405abeebe6ecf971d8f987c2686119908bd56541e1fb6a21e6d5239a7c
|
Provenance
The following attestation bundles were made for pyactuator-0.0.6-py3-none-any.whl:
Publisher:
publish.yml on optophi/pyactuator
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
pyactuator-0.0.6-py3-none-any.whl -
Subject digest:
7b9ea0a2fe1270fe46bc83a888b331be55aeb12aa8417632f373e5b7bef42fab - Sigstore transparency entry: 1485885559
- Sigstore integration time:
-
Permalink:
optophi/pyactuator@b09f0f85995fe68c839f14ea523e56cfdfb816d0 -
Branch / Tag:
refs/tags/v0.0.6 - Owner: https://github.com/optophi
-
Access:
private
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish.yml@b09f0f85995fe68c839f14ea523e56cfdfb816d0 -
Trigger Event:
push
-
Statement type: