|
|
@@ -1,170 +1,107 @@
|
|
|
-# Strategy Engine Architecture – Concept Paper
|
|
|
+# Strategy Engine Architecture
|
|
|
|
|
|
## 1. Overview
|
|
|
|
|
|
-This document defines a modular architecture for a strategy execution system consisting of:
|
|
|
+This document describes how strategy definitions become running instances in `trader-mcp`.
|
|
|
+The design separates:
|
|
|
|
|
|
-* A **host engine**
|
|
|
-* Pluggable **strategy modules**
|
|
|
-* Configurable **strategy instances**
|
|
|
-* A **dashboard UI**
|
|
|
-
|
|
|
-The system separates **definition**, **configuration**, **execution**, and **visualization** into clearly distinct layers.
|
|
|
-
|
|
|
----
|
|
|
+- strategy definition
|
|
|
+- persisted instance record
|
|
|
+- runtime execution
|
|
|
+- dashboard rendering
|
|
|
|
|
|
## 2. Core Concepts
|
|
|
|
|
|
-### 2.1 Strategy Definition
|
|
|
-
|
|
|
-A strategy is a Python module that defines:
|
|
|
-
|
|
|
-* Logic (`on_tick`)
|
|
|
-* State initialization (`init`)
|
|
|
-* Runtime visualization (`render`)
|
|
|
-* Configuration schema (`CONFIG_SCHEMA`)
|
|
|
+### 2.1 Strategy definition
|
|
|
|
|
|
-Characteristics:
|
|
|
+A strategy definition is reusable code, usually a Python module, that exposes the strategy class and behavior.
|
|
|
|
|
|
-* Stateless at definition level
|
|
|
-* Reusable across multiple instances
|
|
|
-* Stored as a single `.py` file (optionally + config)
|
|
|
+It typically defines:
|
|
|
|
|
|
----
|
|
|
+- `init()`
|
|
|
+- `on_tick()`
|
|
|
+- `render()`
|
|
|
+- `CONFIG_SCHEMA`
|
|
|
|
|
|
-### 2.2 Strategy Instance
|
|
|
+### 2.2 Strategy instance
|
|
|
|
|
|
-A strategy instance represents a **configured and uniquely identifiable deployment** of a strategy.
|
|
|
+A strategy instance is a configured, uniquely identifiable deployment of a strategy definition.
|
|
|
|
|
|
-#### Immutable Identity
|
|
|
+It has:
|
|
|
|
|
|
-Defined at creation and never changed:
|
|
|
+- immutable identity
|
|
|
+- mutable config
|
|
|
+- runtime state
|
|
|
+- execution mode
|
|
|
|
|
|
-* `id` (unique internal identifier)
|
|
|
-* `strategy_type`
|
|
|
-* `account`
|
|
|
-* `market` (e.g. BTC/USDT)
|
|
|
+#### Immutable identity
|
|
|
|
|
|
-Purpose:
|
|
|
+Examples:
|
|
|
|
|
|
-* Stable logging
|
|
|
-* Auditability
|
|
|
-* Reproducibility
|
|
|
+- `id`
|
|
|
+- `strategy_type`
|
|
|
+- `account`
|
|
|
+- `market`
|
|
|
|
|
|
----
|
|
|
+These fields should be stable and audit-friendly.
|
|
|
|
|
|
-#### Mutable Configuration
|
|
|
-
|
|
|
-User-editable and persisted:
|
|
|
-
|
|
|
-```json
|
|
|
-{
|
|
|
- "risk": 0.01,
|
|
|
- "window": 20,
|
|
|
- "note": "test run"
|
|
|
-}
|
|
|
-```
|
|
|
+#### Mutable config
|
|
|
|
|
|
-Characteristics:
|
|
|
+Config is user-editable and persistent.
|
|
|
+It is stored as JSON in the database and supplied to the strategy on load.
|
|
|
|
|
|
-* Editable via UI
|
|
|
-* Stored in database
|
|
|
-* Survives restarts
|
|
|
-
|
|
|
----
|
|
|
-
|
|
|
-#### Runtime State
|
|
|
-
|
|
|
-* Managed internally by the strategy
|
|
|
-* Not user-editable
|
|
|
-* Typically not persisted (initially)
|
|
|
-
|
|
|
-Example:
|
|
|
-
|
|
|
-```json
|
|
|
-{
|
|
|
- "prices": [],
|
|
|
- "position": 0
|
|
|
-}
|
|
|
-```
|
|
|
+#### Runtime state
|
|
|
|
|
|
----
|
|
|
+State is owned by the strategy instance and is not part of persistent config.
|
|
|
|
|
|
-## 3. System Layers
|
|
|
+## 3. System Model
|
|
|
|
|
|
```text
|
|
|
-Strategy Definition → Strategy Instance → Runtime Execution → UI Rendering
|
|
|
+Strategy Definition -> Persisted Instance Record -> Running Instance -> UI View
|
|
|
```
|
|
|
|
|
|
----
|
|
|
+The database stores desired state.
|
|
|
+The engine reconciles runtime from that desired state.
|
|
|
|
|
|
-## 4. Modes of Operation
|
|
|
+## 4. Modes
|
|
|
|
|
|
-Each instance has exactly one mode:
|
|
|
+Instances should use a small mode set:
|
|
|
|
|
|
```text
|
|
|
-mode ∈ { off, observe, active }
|
|
|
+off, observe, active
|
|
|
```
|
|
|
|
|
|
-### Modes
|
|
|
+### Meaning
|
|
|
|
|
|
-| Mode | Loaded | Receives Ticks | Can Trade |
|
|
|
-| ------- | ------ | -------------- | --------- |
|
|
|
-| off | ❌ | ❌ | ❌ |
|
|
|
-| observe | ✅ | ✅ | ❌ |
|
|
|
-| active | ✅ | ✅ | ✅ |
|
|
|
+- `off`, not instantiated, no ticks, no trading
|
|
|
+- `observe`, instantiated, ticks enabled, trading disabled
|
|
|
+- `active`, instantiated, ticks enabled, trading enabled
|
|
|
|
|
|
----
|
|
|
+### Mode transitions
|
|
|
|
|
|
-### Design Principle
|
|
|
+- `off` <-> `observe` is the power toggle
|
|
|
+- `observe` <-> `active` is the activation toggle
|
|
|
+- activation should be disabled while mode is `off`
|
|
|
|
|
|
-* Strategies always execute the same logic
|
|
|
-* Capabilities are restricted externally via context
|
|
|
-* No internal branching based on mode
|
|
|
+### Paused runtime state
|
|
|
|
|
|
----
|
|
|
+`paused` is not a persisted mode.
|
|
|
+It is a runtime freeze state controlled by the engine.
|
|
|
|
|
|
-## 5. Persistence vs Runtime
|
|
|
+- the instance stays loaded
|
|
|
+- ticks are skipped
|
|
|
+- trading is skipped
|
|
|
+- render can be skipped while paused
|
|
|
|
|
|
-### Persistent Layer (Database)
|
|
|
+This keeps the stored mode model small while still allowing a temporary freeze.
|
|
|
|
|
|
-Source of truth:
|
|
|
+## 5. Reconciliation
|
|
|
|
|
|
-```json
|
|
|
-{
|
|
|
- "id": "...",
|
|
|
- "strategy_type": "...",
|
|
|
- "account": "...",
|
|
|
- "market": "...",
|
|
|
- "mode": "observe",
|
|
|
- "config": {...}
|
|
|
-}
|
|
|
-```
|
|
|
-
|
|
|
----
|
|
|
-
|
|
|
-### Runtime Layer (Engine Memory)
|
|
|
-
|
|
|
-```python
|
|
|
-running_instances = {
|
|
|
- "id": StrategyInstance(...)
|
|
|
-}
|
|
|
-```
|
|
|
-
|
|
|
-Derived from persistent state.
|
|
|
-
|
|
|
----
|
|
|
-
|
|
|
-## 6. Reconciliation Model
|
|
|
-
|
|
|
-The engine continuously aligns runtime with the database.
|
|
|
-
|
|
|
-### Pseudocode
|
|
|
+The engine should reconcile persisted records against runtime instances when records change or at startup.
|
|
|
|
|
|
```python
|
|
|
def reconcile():
|
|
|
for record in db.instances:
|
|
|
-
|
|
|
if record.mode != "off" and record.id not in running:
|
|
|
load_instance(record)
|
|
|
|
|
|
@@ -172,228 +109,48 @@ def reconcile():
|
|
|
unload_instance(record.id)
|
|
|
```
|
|
|
|
|
|
----
|
|
|
-
|
|
|
-### Key Insight
|
|
|
-
|
|
|
-> The system is **declarative**:
|
|
|
-> The database defines the desired state, the engine enforces it.
|
|
|
-
|
|
|
----
|
|
|
-
|
|
|
-## 7. Strategy SDK Contract
|
|
|
-
|
|
|
-### Base Structure
|
|
|
-
|
|
|
-```python
|
|
|
-class Strategy:
|
|
|
- CONFIG_SCHEMA = {}
|
|
|
-
|
|
|
- def __init__(self, context, config):
|
|
|
- self.context = context
|
|
|
- self.config = config
|
|
|
- self.state = self.init()
|
|
|
-
|
|
|
- def init(self):
|
|
|
- return {}
|
|
|
-
|
|
|
- def on_tick(self, tick):
|
|
|
- pass
|
|
|
-
|
|
|
- def render(self):
|
|
|
- return {"widgets": []}
|
|
|
-```
|
|
|
-
|
|
|
----
|
|
|
-
|
|
|
-### Context Interface
|
|
|
-
|
|
|
-Strategies interact only through context:
|
|
|
-
|
|
|
-```python
|
|
|
-context.get_price()
|
|
|
-context.place_order(...)
|
|
|
-context.get_orders()
|
|
|
-```
|
|
|
-
|
|
|
----
|
|
|
-
|
|
|
-### Mode Enforcement
|
|
|
-
|
|
|
-```python
|
|
|
-def place_order(...):
|
|
|
- if mode != "active":
|
|
|
- raise Exception("Not allowed")
|
|
|
-```
|
|
|
-
|
|
|
----
|
|
|
-
|
|
|
-## 8. UI Architecture
|
|
|
-
|
|
|
-### 8.1 Config UI
|
|
|
-
|
|
|
-Defined via `CONFIG_SCHEMA`
|
|
|
-
|
|
|
-Purpose:
|
|
|
-
|
|
|
-* Generate forms
|
|
|
-* Validate input
|
|
|
-
|
|
|
-Example:
|
|
|
-
|
|
|
-```json
|
|
|
-{
|
|
|
- "risk": {
|
|
|
- "type": "float",
|
|
|
- "default": 0.01
|
|
|
- }
|
|
|
-}
|
|
|
-```
|
|
|
-
|
|
|
----
|
|
|
-
|
|
|
-### 8.2 Runtime UI
|
|
|
+## 6. Config Reload Semantics
|
|
|
|
|
|
-Generated dynamically:
|
|
|
-
|
|
|
-```python
|
|
|
-def render(state):
|
|
|
- return {
|
|
|
- "widgets": [
|
|
|
- {"type": "line_chart", "data": [...]},
|
|
|
- {"type": "metric", "label": "PnL", "value": 123}
|
|
|
- ]
|
|
|
- }
|
|
|
-```
|
|
|
-
|
|
|
----
|
|
|
-
|
|
|
-### Principle
|
|
|
+Config changes should normally trigger a reload.
|
|
|
|
|
|
```text
|
|
|
-Config UI = user input
|
|
|
-Runtime UI = system output
|
|
|
-```
|
|
|
-
|
|
|
----
|
|
|
-
|
|
|
-## 9. Dashboard Responsibilities
|
|
|
-
|
|
|
-### Strategy List View
|
|
|
-
|
|
|
-Displays:
|
|
|
-
|
|
|
-* Instance ID
|
|
|
-* Strategy type
|
|
|
-* Market
|
|
|
-* Account
|
|
|
-* Mode
|
|
|
-* Status
|
|
|
-
|
|
|
----
|
|
|
-
|
|
|
-### Detail View
|
|
|
-
|
|
|
-#### Sections:
|
|
|
-
|
|
|
-1. **Identity (immutable)**
|
|
|
-2. **Config (editable)**
|
|
|
-3. **Runtime UI (widgets)**
|
|
|
-4. **Controls (mode switching)**
|
|
|
-
|
|
|
----
|
|
|
-
|
|
|
-## 10. Execution Model
|
|
|
-
|
|
|
-### Tick Loop
|
|
|
-
|
|
|
-```python
|
|
|
-for instance in running_instances:
|
|
|
- instance.on_tick(tick)
|
|
|
+unload_instance(id)
|
|
|
+load_instance(updated_record)
|
|
|
```
|
|
|
|
|
|
----
|
|
|
-
|
|
|
-### Rendering
|
|
|
-
|
|
|
-```python
|
|
|
-def get_ui(instance_id):
|
|
|
- return instance.render()
|
|
|
-```
|
|
|
-
|
|
|
----
|
|
|
-
|
|
|
-### Optimization
|
|
|
-
|
|
|
-* Rendering is **on-demand only**
|
|
|
-* No UI computation when not requested
|
|
|
-
|
|
|
----
|
|
|
-
|
|
|
-## 11. Design Principles
|
|
|
-
|
|
|
-### 1. Separation of Concerns
|
|
|
-
|
|
|
-* Strategy logic vs system control
|
|
|
-* Config vs runtime state
|
|
|
-* UI input vs UI output
|
|
|
-
|
|
|
----
|
|
|
+This is the clean default because it is deterministic and easy to debug.
|
|
|
|
|
|
-### 2. Declarative Control
|
|
|
+Selective state carryover can exist later, but reload is the default.
|
|
|
|
|
|
-* No direct lifecycle manipulation
|
|
|
-* DB defines desired state
|
|
|
+## 7. Engine Responsibilities
|
|
|
|
|
|
----
|
|
|
+The engine should:
|
|
|
|
|
|
-### 3. Minimal Control Surface
|
|
|
+- load strategy modules
|
|
|
+- instantiate strategies with context + config
|
|
|
+- manage lifecycle
|
|
|
+- tick loaded strategies
|
|
|
+- render on demand
|
|
|
+- enforce execution mode through the context, including binding the instance `account_id` and `client_id` when orders are sent to exec-mcp
|
|
|
|
|
|
-* Single `mode` field
|
|
|
-* Avoid multiple flags
|
|
|
+## 8. Dashboard Responsibilities
|
|
|
|
|
|
----
|
|
|
+The dashboard should show:
|
|
|
|
|
|
-### 4. Capability-Based Execution
|
|
|
+- identity
|
|
|
+- config
|
|
|
+- current mode
|
|
|
+- runtime status
|
|
|
+- rendered widgets
|
|
|
|
|
|
-* Strategies always run logic
|
|
|
-* Context restricts actions
|
|
|
+It should not own trading logic.
|
|
|
|
|
|
----
|
|
|
+## 9. Recommended Direction
|
|
|
|
|
|
-### 5. Instance-Centric Design
|
|
|
-
|
|
|
-* Everything revolves around instances
|
|
|
-* Not strategies as singletons
|
|
|
-
|
|
|
----
|
|
|
-
|
|
|
-## 12. Future Extensions
|
|
|
-
|
|
|
-* Backtesting (reuse instance config)
|
|
|
-* Simulation mode (observe + virtual trading)
|
|
|
-* Distributed execution
|
|
|
-* Strategy versioning
|
|
|
-* State persistence and replay
|
|
|
-* Advanced UI widgets (logs, trades, signals)
|
|
|
-
|
|
|
----
|
|
|
-
|
|
|
-## 13. Summary
|
|
|
-
|
|
|
-The system consists of:
|
|
|
-
|
|
|
-* **Strategies**: reusable logic modules
|
|
|
-* **Instances**: configured, persistent deployments
|
|
|
-* **Engine**: reconciles desired vs actual state
|
|
|
-* **UI**: renders config and runtime data separately
|
|
|
-
|
|
|
----
|
|
|
-
|
|
|
-### Core Insight
|
|
|
-
|
|
|
-```text
|
|
|
-Let strategies always think — but only sometimes act.
|
|
|
-```
|
|
|
+The architecture is strongest when:
|
|
|
|
|
|
----
|
|
|
+- config is declarative
|
|
|
+- state is instance-local
|
|
|
+- context is capability-only and may enrich calls with the instance `account_id` and `client_id`
|
|
|
+- reload is the default config update mechanism
|
|
|
+- the database is the source of truth
|