6ce128f576
Signed-off-by: TIANHE <TIANHE@GMAIL.COM>
236 lines
7.7 KiB
Markdown
236 lines
7.7 KiB
Markdown
# 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
|
|
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))
|
|
]
|
|
```
|
|
|
|
### 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
|
|
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'
|
|
}
|
|
]
|
|
}
|
|
```
|
|
|
|
## 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.
|
|
|