This document defines the core strategy contract for trader-mcp.
It separates:
The goal is simple, single-file strategies that stay easy to reason about, safe to run, and consistent across the app.
Strategies should have a minimal API and minimal boilerplate.
account_id, client_id) to exec callsA given strategy instance should behave predictably for the same config and input stream.
Strategies must not touch engine internals or persistence directly. All external actions go through the injected context.
Each strategy is a Python class extending Strategy.
from strategy_sdk import Strategy
class MyStrategy(Strategy):
CONFIG_SCHEMA = {
"risk": {"type": "float", "default": 0.01},
"window": {"type": "int", "default": 20}
}
def init(self):
return {
"prices": [],
"position": 0
}
def on_tick(self, tick):
price = tick["price"]
self.state["prices"].append(price)
def render(self):
return {
"widgets": [
{"type": "line_chart", "data": self.state["prices"]}
]
}
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": []}
CONFIG_SCHEMA drives validation and UI generation.
CONFIG_SCHEMA = {
"risk": {
"type": "float",
"default": 0.01,
"min": 0.0,
"max": 1.0
},
"window": {
"type": "int",
"default": 20
}
}
self.state belongs to the strategy instance.
init() -> state created
on_tick() -> state updated
reload -> state reset
The context is a capability boundary, not a config loader.
account_id and client_id to execution callsExample:
class StrategyContext:
def get_price(self):
raise NotImplementedError
def place_order(self, side, amount):
raise NotImplementedError
def get_orders(self):
raise NotImplementedError
def log(self, message):
raise NotImplementedError
Strategies should return structured UI data, not HTML.
def render(self):
return {
"widgets": [
{"type": "line_chart", "data": [...]},
{"type": "metric", "label": "PnL", "value": 123.45}
]
}
The strategy SDK should make it obvious that: