This guide describes how to develop trading strategies using Python in the QuantDinger platform. QuantDinger provides a flexible execution environment that supports data access, indicator calculation, and signal generation.
Strategies in QuantDinger operate based on the Signal Provider mode. The system executes your Python script, which is expected to process market data (a DataFrame) and output trading signals.
The execution flow is as follows:
- Input: The system injects a
df(Pandas DataFrame) containing OHLCV data into your script environment. - Processing: You use Python (
pandas,numpy) to calculate indicators and definebuy/selllogic. - Output: You construct a specific
outputdictionary containing plot data and signals.
Your script runs in a sandboxed Python environment.
The following libraries are available by default (do not import them):
pd(pandas)np(numpy)
A Pandas DataFrame variable named df is automatically available in the global scope. It contains the historical market data for the selected symbol and timeframe.
Columns:
time: Timestamp (datetime or int, depending on context, usually localized)open: Open price (float)high: High price (float)low: Low price (float)close: Close price (float)volume: Trading volume (float)
Example:
# Access closing prices
closes = df['close']
# Calculate a Simple Moving Average (SMA)
sma_20 = df['close'].rolling(20).mean()A standard strategy script consists of three parts:
- Indicator Calculation: Compute technical indicators.
- Signal Generation: Define logic for Buy and Sell signals.
- Output Construction: Format the results for the chart and execution engine.
You can use standard Pandas operations to calculate indicators.
# Example: MACD Calculation
short_window = 12
long_window = 26
signal_window = 9
ema12 = df['close'].ewm(span=short_window, adjust=False).mean()
ema26 = df['close'].ewm(span=long_window, adjust=False).mean()
macd = ema12 - ema26
signal_line = macd.ewm(span=signal_window, adjust=False).mean()You MUST create two boolean Series in the df or as standalone variables, named buy and sell.
Trueindicates a signal trigger.Falseindicates no signal.
Important: Edge Triggering To avoid repeated signals on consecutive candles (which might lead to multiple orders depending on backend config), it is best practice to use edge-triggered signals (signal only on the moment the condition becomes true).
# Condition: Close price crosses above SMA 20
condition_buy = (df['close'] > sma_20) & (df['close'].shift(1) <= sma_20.shift(1))
# Condition: Close price crosses below SMA 20
condition_sell = (df['close'] < sma_20) & (df['close'].shift(1) >= sma_20.shift(1))
# Assign to df (Required for backtesting)
df['buy'] = condition_buy.fillna(False)
df['sell'] = condition_sell.fillna(False)Note on Signal Types:
- QuantDinger normalizes signals based on your strategy configuration (Long-only, Short-only, or Bi-directional).
- Your script simply outputs "buy" (bullish intent) or "sell" (bearish intent). The backend handles opening/closing positions.
For charting, you often want to place the signal icon slightly above or below the candle.
# Place Buy marker 0.5% below the Low
buy_marks = [
df['low'].iloc[i] * 0.995 if df['buy'].iloc[i] else None
for i in range(len(df))
]
# Place Sell marker 0.5% above the High
sell_marks = [
df['high'].iloc[i] * 1.005 if df['sell'].iloc[i] else None
for i in range(len(df))
]The final step is to assign a dictionary to the variable output. This tells the frontend what to draw and the backend where the signals are.
Structure:
output = {
"name": "My Strategy Name",
"plots": [ ... ], # List of lines/indicators to draw
"signals": [ ... ] # List of signal markers
}Plots Schema:
name: Legend name (e.g., "SMA 20")data: List of values (must matchdflength). Use.tolist().color: Hex color string (e.g., "#ff0000").overlay:Trueto draw on main chart (price),Falseto draw on separate pane (like RSI/MACD).
Signals Schema:
type: Must be "buy" or "sell".text: Text to display on icon (e.g., "B", "S").data: List of values (prices) where the icon appears.Nonewhere no signal.color: Icon color.
Here is a full, copy-pasteable example of a strategy that buys when SMA(10) crosses above SMA(30) and sells when it crosses below.
# 1. Indicator Calculation
# -----------------------
# Calculate Short and Long SMAs
sma_short = df['close'].rolling(10).mean()
sma_long = df['close'].rolling(30).mean()
# 2. Signal Logic
# -----------------------
# Buy: Short SMA crosses above Long SMA
raw_buy = (sma_short > sma_long) & (sma_short.shift(1) <= sma_long.shift(1))
# Sell: Short SMA crosses below Long SMA
raw_sell = (sma_short < sma_long) & (sma_short.shift(1) >= sma_long.shift(1))
# Clean up NaNs and ensure boolean type
buy = raw_buy.fillna(False)
sell = raw_sell.fillna(False)
# Assign to df columns (CRITICAL for backend execution)
df['buy'] = buy
df['sell'] = sell
# 3. Visual Formatting
# -----------------------
# Calculate marker positions
buy_marks = [
df['low'].iloc[i] * 0.995 if buy.iloc[i] else None
for i in range(len(df))
]
sell_marks = [
df['high'].iloc[i] * 1.005 if sell.iloc[i] else None
for i in range(len(df))
]
# 4. Final Output
# -----------------------
output = {
'name': 'Dual SMA Strategy',
'plots': [
{
'name': 'SMA 10',
'data': sma_short.fillna(0).tolist(),
'color': '#1890ff',
'overlay': True
},
{
'name': 'SMA 30',
'data': sma_long.fillna(0).tolist(),
'color': '#faad14',
'overlay': True
}
],
'signals': [
{
'type': 'buy',
'text': 'B',
'data': buy_marks,
'color': '#00E676'
},
{
'type': 'sell',
'text': 'S',
'data': sell_marks,
'color': '#FF5252'
}
]
}Rolling calculations (like rolling(14)) produce NaN values at the beginning of the data.
- Rule: Always handle
NaNs before generating signals. - Fix: Use
.fillna(0)or.fillna(False)depending on context.
The system executes trades based on the signal generated at the close of a bar.
- The backtester typically executes the order at the Open of the Next Bar.
- Your signal logic should rely on
close(current completed bar) orshift(1)(previous bar). Do not useshift(-1).
Avoid iterating over the DataFrame rows (for i in range(len(df)): ...) for calculation logic. It is slow.
- Bad: Loop to calculate SMA.
- Good:
df['close'].rolling(...). - Exception: Constructing the
buy_marks/sell_markslist usually requires a list comprehension, which is acceptable for visual output.
Since you cannot see print() output easily in some execution modes, check the backend logs (backend_api_python/logs/app.log) if your strategy fails to load.
- Common error:
KeyError(wrong column name). - Common error:
ValueError(arrays must be same length). Ensureplotsdata matchesdflength.