QuanuX strategies are built from modular components. You start in Python — where iteration is fast and the data science ecosystem is available — then graduate the logic to compiled code once you’re confident in the approach. This page covers how to write a strategy from scratch and how to use the Foundry API to generate higher-performance equivalents.
Strategy architecture
Every strategy in server/strategies/ is assembled from four component types, each inheriting from StrategyComponent defined in server/strategies/base.py.
| Component | Base class | Responsibility |
|---|
| Signal | SignalModule | Analyze market data; return LONG, SHORT, or NEUTRAL |
| Entry | EntryModule | Generate an order dict when a signal is present |
| Risk | RiskModule | Calculate stop loss, take profit, and trailing stop levels |
| Exit | ExitModule | Decide whether an open position should close |
A CompositeStrategy wires these components together and drives the on_bar loop.
Writing a strategy in Python
The example below is drawn from the SmaCrossover strategy in server/strategies/full/SmaCrossover/.
from server.strategies.base import SignalModule, SignalType, BaseParameters
from typing import Type
import pandas as pd
class SmaSignal(SignalModule):
"""Simple Moving Average Crossover Signal.
Long when Fast SMA > Slow SMA. Short when Fast SMA < Slow SMA.
"""
def define_parameters(self) -> Type[BaseParameters]:
class Params(BaseParameters):
fast_period: int = 10
slow_period: int = 20
return Params
def on_bar(self, bar_data: dict) -> SignalType:
if not hasattr(self, '_history'):
self._history = []
close = bar_data.get('close')
if close is None:
return SignalType.NEUTRAL
self._history.append(close)
slow = self.params['slow_period']
if len(self._history) > slow + 20:
self._history.pop(0)
if len(self._history) < slow:
return SignalType.NEUTRAL
series = pd.Series(self._history)
fast_ma = series.rolling(window=self.params['fast_period']).mean().iloc[-1]
slow_ma = series.rolling(window=self.params['slow_period']).mean().iloc[-1]
if fast_ma > slow_ma:
return SignalType.LONG
elif fast_ma < slow_ma:
return SignalType.SHORT
return SignalType.NEUTRAL
Define parameters using a nested Pydantic model inside define_parameters. QuanuX validates and coerces all values at runtime when you call update_parameters.
Using the Foundry to generate code
Once your Python prototype is working, submit it to the AI Foundry to generate a faster equivalent. The Foundry dispatches your request as a NATS payload and returns a job ID immediately.
POST /api/foundry/forge
Submit a generation job for a strategy component.
Request body
The type of component to generate. Accepted values: indicator, entry, exit, strategy.
The name for the generated component, used as the file and class identifier.
The output language. Accepted values: python, cython, cpp.
Semantic version string for the generated artifact, e.g. 1.0.0. Optional; defaults to null.
Natural-language description of the strategy logic. The Foundry uses this alongside the component type and name to guide code generation.
Response
Always "accepted" on success. The job runs asynchronously.
Unique job identifier in the format job_xxxxxxxx. Use this to correlate NATS telemetry events back to your request.
Example: generate a Cython signal component
curl -X POST http://localhost:8080/api/foundry/forge \
-H "Authorization: Bearer YOUR_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"component_type": "strategy",
"name": "SmaCrossover",
"target_lang": "cython",
"target_version": "1.0.0",
"prompt": "SMA crossover: long when 10-period SMA crosses above 20-period SMA, short on the reverse"
}'
{
"status": "accepted",
"job_id": "job_3f8a1c2d"
}
POST /api/foundry/verify
After the Foundry generates code, run equivalence testing to confirm the generated version produces mathematically identical output to your Python prototype.
Request body
The name of the strategy to verify. Must match the name used in the forge request.
Response
Always "accepted". Verification runs asynchronously in a deterministic sandbox.
Unique job identifier for this verification run.
Example
curl -X POST http://localhost:8080/api/foundry/verify \
-H "Authorization: Bearer YOUR_TOKEN" \
-H "Content-Type: application/json" \
-d '{"strategy_name": "SmaCrossover"}'
{
"status": "accepted",
"job_id": "job_9b4e7a01"
}
Git-as-Governance
Before a generated strategy can be promoted to C++ and deployed to the live spreader, it must be committed to your QuanuX repository. The spreader verifies the SHA-256 of every strategy artifact against a signed commit. An uncommitted or unsigned strategy will be rejected at deploy time.
Do not modify generated Cython .pyx files by hand after committing. Any change invalidates the SHA-256 signature and will block deployment. If you need to adjust the logic, submit a new forge request and re-verify.
Commit your strategy files before proceeding to backtesting:
git add server/strategies/full/SmaCrossover/
git commit -S -m "feat(strategy): add SmaCrossover v1.0.0"