Skip to main content

Documentation Index

Fetch the complete documentation index at: https://docs.quanux.org/llms.txt

Use this file to discover all available pages before exploring further.

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.
ComponentBase classResponsibility
SignalSignalModuleAnalyze market data; return LONG, SHORT, or NEUTRAL
EntryEntryModuleGenerate an order dict when a signal is present
RiskRiskModuleCalculate stop loss, take profit, and trailing stop levels
ExitExitModuleDecide 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
component_type
string
required
The type of component to generate. Accepted values: indicator, entry, exit, strategy.
name
string
required
The name for the generated component, used as the file and class identifier.
target_lang
string
required
The output language. Accepted values: python, cython, cpp.
target_version
string
Semantic version string for the generated artifact, e.g. 1.0.0. Optional; defaults to null.
prompt
string
Natural-language description of the strategy logic. The Foundry uses this alongside the component type and name to guide code generation.
Response
status
string
required
Always "accepted" on success. The job runs asynchronously.
job_id
string
required
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"
  }'
Response
{
  "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
strategy_name
string
required
The name of the strategy to verify. Must match the name used in the forge request.
Response
status
string
required
Always "accepted". Verification runs asynchronously in a deterministic sandbox.
job_id
string
required
Unique job identifier for this verification run.
Example
cURL
curl -X POST http://localhost:8080/api/foundry/verify \
  -H "Authorization: Bearer YOUR_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"strategy_name": "SmaCrossover"}'
Response
{
  "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"