2 · Developing
Express your hypothesis as a Python strategy, with indicators and observers, inside Malgot's engine.
In Malgot you write strategies in Python, and they run inside a Rust engine. You get Python's expressiveness for the decision logic and Rust's speed and determinism for the simulation loop. A trading system is assembled from a few small, composable objects.
The building blocks
| Object | Role | Language | |---|---|---| | Strategy | Decides what to trade — emits signals each bar | Python | | Indicator | A reusable computed series (SMA, RSI, Z-Score…) | Python | | Observer | Records a value each bar for charting (equity, exposure…) | Python | | Metric | A single end-of-run number (CAGR, Sharpe…) | Python | | Signal Allocator | Turns raw signals into sized orders | configured | | Broker | Fees, slippage, margin, borrow — the cost model | configured |
You develop these in the Object Explorer (/framework), each with an in-browser
Monaco editor.
Anatomy of a strategy
A strategy subclasses Malgot.Strategy and implements evaluate(), returning a list
of Malgot.Signal. Indicators are linked to the strategy and read back by name:
import Malgot
import numpy as np
class MyMeanReversion(Malgot.Strategy):
def __init__(self):
super().__init__()
self.name = "My Mean Reversion"
# Defaults — overridden by DB-linked strategy parameters.
self.entry_z = 1.25
self.exit_z = 0.5
def evaluate(self):
signals = []
symbols = self.data.get_symbols()
# Linked indicator, aligned column-for-column with `symbols`.
z = self.state.get_indicator_values("Z-Score", symbols)
held = dict(self.state.get_all_positions()) # {symbol: qty}
for i, symbol in enumerate(symbols):
zi = float(z[i])
price = self.data.get_current_price(symbol)
if np.isnan(zi) or price <= 0:
continue
if symbol in held:
# Exit when the deviation has reverted.
if zi >= -self.exit_z:
signals.append(Malgot.Signal(symbol, "SELL", held[symbol]))
elif zi <= -self.entry_z:
# Enter on a statistically extreme dip.
size = (0.01 * self.state.total_value) / price
signals.append(Malgot.Signal(symbol, "BUY", size))
return signalsParameters belong in the database, not hard-coded
Give every knob a strategy parameter (with min/max/default). __init__ holds
only fallbacks. This is what makes the same strategy tunable from the UI and, later,
optimizable without touching code.
Signals carry intent, not just direction
A Signal can specify order type and risk legs, which the engine's broker turns
into real orders:
Malgot.Signal(
symbol, "BUY", size,
order_type="LIMIT", limit_price=99.5,
stop_loss=95.0, take_profit=110.0,
time_in_force="GTC",
)Market, limit, stop, stop-limit, trailing, and on-open/close orders are supported, plus bracket exits (OCO). Keep the strategy about when to trade; let the broker and signal allocator own sizing and execution mechanics.
Make it observable
You can only debug — and later trust — what you can see. Add observers for the quantities that explain the equity curve: net exposure, number of open positions, cash ratio, per-sleeve position value. When a backtest surprises you, observers are where you look first.
Separate concerns
Don't bake costs or sizing into the strategy
Commission, slippage, borrow fees and position sizing live in the broker and
allocator — not in evaluate(). This keeps the strategy portable across cost
models and lets you stress-test costs later without editing logic.
