Files

236 lines
7.7 KiB
Markdown
Raw Permalink Normal View History

2025-12-30 17:03:22 +08:00
# QuantDinger Python Strategy Development Guide
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.
## 1. Overview
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:
1. **Input**: The system injects a `df` (Pandas DataFrame) containing OHLCV data into your script environment.
2. **Processing**: You use Python (`pandas`, `numpy`) to calculate indicators and define `buy`/`sell` logic.
3. **Output**: You construct a specific `output` dictionary containing plot data and signals.
---
## 2. Environment & Data
Your script runs in a sandboxed Python environment.
### 2.1 Pre-imported Libraries
The following libraries are available by default (do not `import` them):
* `pd` (pandas)
* `np` (numpy)
### 2.2 Input Data (`df`)
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:**
```python
# Access closing prices
closes = df['close']
# Calculate a Simple Moving Average (SMA)
sma_20 = df['close'].rolling(20).mean()
```
---
## 3. Developing a Strategy
A standard strategy script consists of three parts:
1. **Indicator Calculation**: Compute technical indicators.
2. **Signal Generation**: Define logic for Buy and Sell signals.
3. **Output Construction**: Format the results for the chart and execution engine.
### 3.1 Indicator Calculation
You can use standard Pandas operations to calculate indicators.
```python
# 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()
```
### 3.2 Signal Generation (Crucial)
You **MUST** create two boolean Series in the `df` or as standalone variables, named `buy` and `sell`.
* `True` indicates a signal trigger.
* `False` indicates 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).
```python
# 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.
### 3.3 Visual Markers
For charting, you often want to place the signal icon slightly above or below the candle.
```python
# Place Buy marker 0.5% below the Low
buy_marks = [
df['low'].iloc[i] * 0.995 if df['buy'].iloc[i] else None
2025-12-30 17:03:22 +08:00
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
2025-12-30 17:03:22 +08:00
for i in range(len(df))
]
```
### 3.4 The `output` Variable (Mandatory)
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:**
```python
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 match `df` length). Use `.tolist()`.
* `color`: Hex color string (e.g., "#ff0000").
* `overlay`: `True` to draw on main chart (price), `False` to 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. `None` where no signal.
* `color`: Icon color.
---
## 4. Complete Example: Dual SMA Crossover
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.
```python
# 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
2025-12-30 17:03:22 +08:00
for i in range(len(df))
]
sell_marks = [
df['high'].iloc[i] * 1.005 if sell.iloc[i] else None
2025-12-30 17:03:22 +08:00
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'
}
]
}
```
## 5. Best Practices & Troubleshooting
### 5.1 Handling NaNs
Rolling calculations (like `rolling(14)`) produce `NaN` values at the beginning of the data.
* **Rule**: Always handle `NaN`s before generating signals.
* **Fix**: Use `.fillna(0)` or `.fillna(False)` depending on context.
### 5.2 Look-ahead Bias
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) or `shift(1)` (previous bar). Do not use `shift(-1)`.
### 5.3 Performance
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_marks` list usually requires a list comprehension, which is acceptable for visual output.
### 5.4 Debugging
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). Ensure `plots` data matches `df` length.