local strategy
This commit is contained in:
50
AGENTS.md
50
AGENTS.md
@ -41,10 +41,13 @@ src/
|
|||||||
│ ├── custom_timeframe_generator.py # 37m, 148m, 1d aggregation
|
│ ├── custom_timeframe_generator.py # 37m, 148m, 1d aggregation
|
||||||
│ ├── indicator_engine.py # SMA/EMA computation & storage
|
│ ├── indicator_engine.py # SMA/EMA computation & storage
|
||||||
│ ├── brain.py # Strategy evaluation & decision logging
|
│ ├── brain.py # Strategy evaluation & decision logging
|
||||||
│ └── backtester.py # Historical replay driver
|
│ └── backtester.py # Historical replay driver (server-side)
|
||||||
└── api/
|
├── api/
|
||||||
├── server.py # FastAPI app, endpoints for data/backtests
|
│ ├── server.py # FastAPI app, endpoints for data/backtests
|
||||||
└── dashboard/static/index.html # Real-time web dashboard
|
│ └── dashboard/static/index.html # Real-time web dashboard with client-side simulation
|
||||||
|
└── strategies/
|
||||||
|
├── base.py # Base strategy interface
|
||||||
|
└── ma_strategy.py # Configurable MA strategy (period: 5-500)
|
||||||
config/data_config.yaml # Operational config & indicator settings
|
config/data_config.yaml # Operational config & indicator settings
|
||||||
docker/ # Docker orchestration & init-scripts
|
docker/ # Docker orchestration & init-scripts
|
||||||
scripts/ # Deploy, backup, & utility scripts
|
scripts/ # Deploy, backup, & utility scripts
|
||||||
@ -52,17 +55,30 @@ scripts/ # Deploy, backup, & utility scripts
|
|||||||
|
|
||||||
## Architecture & Data Flow
|
## Architecture & Data Flow
|
||||||
|
|
||||||
|
### Live Trading (Server-Side)
|
||||||
```
|
```
|
||||||
Live: WS -> Buffer -> DB -> CustomTF -> IndicatorEngine -> Brain -> Decisions
|
Live: WS -> Buffer -> DB -> CustomTF -> IndicatorEngine -> Brain -> Decisions
|
||||||
│ │
|
│ │
|
||||||
Backtest: DB (History) -> Backtester ─────────┴─────────────┘
|
Backtest: DB (History) -> Backtester ─────────┴─────────────┘
|
||||||
```
|
```
|
||||||
|
|
||||||
|
### Client-Side Simulation (Dashboard)
|
||||||
|
```
|
||||||
|
User Input -> API /candles/bulk -> Browser (ClientStrategyEngine) -> Chart Visualization
|
||||||
|
↓ ↓
|
||||||
|
Historical Data Buy/Sell Markers
|
||||||
|
```
|
||||||
|
|
||||||
- **Stateless Logic**: `IndicatorEngine` and `Brain` are driver-agnostic. They read from DB
|
- **Stateless Logic**: `IndicatorEngine` and `Brain` are driver-agnostic. They read from DB
|
||||||
and write to DB, unaware if the trigger is live WS or backtest replay.
|
and write to DB, unaware if the trigger is live WS or backtest replay.
|
||||||
- **Consistency**: Indicators are computed exactly the same way for live and backtest.
|
- **Consistency**: Indicators are computed exactly the same way for live and backtest.
|
||||||
- **Visualization**: Dashboard queries `indicators` and `decisions` tables directly.
|
- **Visualization**: Dashboard queries `indicators` and `decisions` tables directly.
|
||||||
Decisions contain a JSON snapshot of indicators at the moment of decision.
|
Decisions contain a JSON snapshot of indicators at the moment of decision.
|
||||||
|
- **Client-Side Simulation**: All strategy simulations run in the browser using `ClientStrategyEngine`.
|
||||||
|
The server only provides historical candle data via API. This minimizes server load and allows
|
||||||
|
interactive backtesting with configurable parameters (MA period 5-500).
|
||||||
|
- **Full Historical Display**: After simulation, the chart displays the complete date range used,
|
||||||
|
not just the default 1000 recent candles.
|
||||||
|
|
||||||
## Key Dataclasses
|
## Key Dataclasses
|
||||||
|
|
||||||
@ -109,6 +125,32 @@ class Decision: # Brain output
|
|||||||
- **Logging**: Use `logger = logging.getLogger(__name__)`.
|
- **Logging**: Use `logger = logging.getLogger(__name__)`.
|
||||||
- **Config**: Load from `config/data_config.yaml` or env vars.
|
- **Config**: Load from `config/data_config.yaml` or env vars.
|
||||||
|
|
||||||
|
## Strategy Configuration
|
||||||
|
|
||||||
|
The system uses a single configurable Moving Average strategy (`ma_strategy`) with a dynamic period (5-500).
|
||||||
|
|
||||||
|
### Strategy Files
|
||||||
|
- `src/strategies/base.py` - Base strategy interface with `SignalType` enum
|
||||||
|
- `src/strategies/ma_strategy.py` - Configurable MA strategy implementation
|
||||||
|
|
||||||
|
### Client-Side vs Server-Side
|
||||||
|
|
||||||
|
| Feature | Client-Side (Dashboard) | Server-Side (CLI/API) |
|
||||||
|
|---------|------------------------|----------------------|
|
||||||
|
| **Purpose** | Interactive simulation | Production backtesting |
|
||||||
|
| **Strategy** | Single configurable MA (period 5-500) | Configurable via strategy registry |
|
||||||
|
| **Indicators** | Calculated in browser (SMA, RSI, etc.) | Pre-computed in database |
|
||||||
|
| **Data Flow** | API → Browser → Chart | DB → Backtester → DB |
|
||||||
|
| **Performance** | Fast, interactive | Thorough, historical |
|
||||||
|
|
||||||
|
### Simulation Workflow
|
||||||
|
1. User selects date range and MA period (5-500) in dashboard sidebar
|
||||||
|
2. Browser fetches full historical data from `/api/v1/candles/bulk`
|
||||||
|
3. `ClientStrategyEngine` calculates indicators client-side using JavaScript
|
||||||
|
4. Simulation runs on complete dataset, generating buy/sell signals
|
||||||
|
5. Chart updates to show full historical range with trade markers
|
||||||
|
6. Results displayed in sidebar (win rate, P&L, profit factor)
|
||||||
|
|
||||||
## Common Tasks
|
## Common Tasks
|
||||||
|
|
||||||
### Add New Indicator
|
### Add New Indicator
|
||||||
|
|||||||
@ -1483,54 +1483,37 @@
|
|||||||
return tfCandles[pointers[tf]].close;
|
return tfCandles[pointers[tf]].close;
|
||||||
};
|
};
|
||||||
|
|
||||||
// Debug logging for first evaluation
|
// Simple logic for MA Trend strategy
|
||||||
if (index === 1) {
|
if (config.id === 'ma_trend') {
|
||||||
console.log('First candle time:', candles[index].time, 'Date:', new Date(candles[index].time * 1000));
|
const period = config.params?.period || 44;
|
||||||
console.log('MA44 value:', getVal('ma44', primaryTF));
|
|
||||||
}
|
// Debug logging for first evaluation
|
||||||
|
if (index === 1) {
|
||||||
// Simple logic for MVP strategies
|
console.log('First candle time:', candles[index].time, 'Date:', new Date(candles[index].time * 1000));
|
||||||
if (config.id === 'ma44_strategy') {
|
console.log(`MA${period} value:`, getVal(`ma${period}`, primaryTF));
|
||||||
const ma44 = getVal('ma44', primaryTF);
|
}
|
||||||
|
const maValue = getVal(`ma${period}`, primaryTF);
|
||||||
const price = candles[index].close;
|
const price = candles[index].close;
|
||||||
|
|
||||||
// Optional: Multi-TF trend filter
|
// Optional: Multi-TF trend filter (must align for both entry and exit)
|
||||||
const secondaryTF = config.timeframes?.secondary?.[0];
|
const secondaryTF = config.timeframes?.secondary?.[0];
|
||||||
let trendOk = true;
|
let secondaryBullish = true;
|
||||||
|
let secondaryBearish = true;
|
||||||
if (secondaryTF) {
|
if (secondaryTF) {
|
||||||
const secondaryPrice = getPrice(secondaryTF);
|
const secondaryPrice = getPrice(secondaryTF);
|
||||||
const secondaryMA = getVal(`ma44_${secondaryTF}`, secondaryTF);
|
const secondaryMA = getVal(`ma${period}_${secondaryTF}`, secondaryTF);
|
||||||
trendOk = secondaryPrice > secondaryMA;
|
if (secondaryPrice !== null && secondaryMA !== null) {
|
||||||
|
secondaryBullish = secondaryPrice > secondaryMA;
|
||||||
|
secondaryBearish = secondaryPrice < secondaryMA;
|
||||||
|
}
|
||||||
if (index === 1) {
|
if (index === 1) {
|
||||||
console.log(`Trend check: ${secondaryTF} price=${secondaryPrice}, MA=${secondaryMA}, trendOk=${trendOk}`);
|
console.log(`Trend check: ${secondaryTF} price=${secondaryPrice}, MA=${secondaryMA}, bullish=${secondaryBullish}, bearish=${secondaryBearish}`);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (ma44) {
|
if (maValue) {
|
||||||
if (price > ma44 && trendOk) return 'BUY';
|
if (price > maValue && secondaryBullish) return 'BUY';
|
||||||
if (price < ma44) return 'SELL';
|
if (price < maValue && secondaryBearish) return 'SELL';
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (config.id === 'ma125_strategy') {
|
|
||||||
const ma125 = getVal('ma125', primaryTF);
|
|
||||||
const price = candles[index].close;
|
|
||||||
|
|
||||||
// Optional: Multi-TF trend filter
|
|
||||||
const secondaryTF = config.timeframes?.secondary?.[0];
|
|
||||||
let trendOk = true;
|
|
||||||
if (secondaryTF) {
|
|
||||||
const secondaryPrice = getPrice(secondaryTF);
|
|
||||||
const secondaryMA = getVal(`ma125_${secondaryTF}`, secondaryTF);
|
|
||||||
trendOk = secondaryPrice > secondaryMA;
|
|
||||||
if (index === 1) {
|
|
||||||
console.log(`Trend check: ${secondaryTF} price=${secondaryPrice}, MA=${secondaryMA}, trendOk=${trendOk}`);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (ma125) {
|
|
||||||
if (price > ma125 && trendOk) return 'BUY';
|
|
||||||
if (price < ma125) return 'SELL';
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -2131,6 +2114,9 @@
|
|||||||
this.loadInitialData();
|
this.loadInitialData();
|
||||||
this.loadTA();
|
this.loadTA();
|
||||||
|
|
||||||
|
// Clear simulation results when changing timeframe
|
||||||
|
clearSimulationResults();
|
||||||
|
|
||||||
// Update simulation panel timeframe display
|
// Update simulation panel timeframe display
|
||||||
updateTimeframeDisplay();
|
updateTimeframeDisplay();
|
||||||
}
|
}
|
||||||
@ -2312,6 +2298,36 @@
|
|||||||
window.tradeLineSeries = [];
|
window.tradeLineSeries = [];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function clearSimulationResults() {
|
||||||
|
// Clear markers from chart
|
||||||
|
clearSimulationMarkers();
|
||||||
|
|
||||||
|
// Clear simulation data
|
||||||
|
window.lastSimulationResults = null;
|
||||||
|
|
||||||
|
// Hide results section
|
||||||
|
const resultsSection = document.getElementById('resultsSection');
|
||||||
|
if (resultsSection) {
|
||||||
|
resultsSection.style.display = 'none';
|
||||||
|
}
|
||||||
|
|
||||||
|
// Reset results display
|
||||||
|
const simTrades = document.getElementById('simTrades');
|
||||||
|
const simWinRate = document.getElementById('simWinRate');
|
||||||
|
const simPnL = document.getElementById('simPnL');
|
||||||
|
const simProfitFactor = document.getElementById('simProfitFactor');
|
||||||
|
const equitySparkline = document.getElementById('equitySparkline');
|
||||||
|
|
||||||
|
if (simTrades) simTrades.textContent = '0';
|
||||||
|
if (simWinRate) simWinRate.textContent = '0%';
|
||||||
|
if (simPnL) {
|
||||||
|
simPnL.textContent = '$0.00';
|
||||||
|
simPnL.style.color = '';
|
||||||
|
}
|
||||||
|
if (simProfitFactor) simProfitFactor.textContent = '0';
|
||||||
|
if (equitySparkline) equitySparkline.innerHTML = '';
|
||||||
|
}
|
||||||
|
|
||||||
// Set default start date (7 days ago)
|
// Set default start date (7 days ago)
|
||||||
function setDefaultStartDate() {
|
function setDefaultStartDate() {
|
||||||
@ -2398,11 +2414,8 @@
|
|||||||
|
|
||||||
// Strategy parameter definitions
|
// Strategy parameter definitions
|
||||||
const StrategyParams = {
|
const StrategyParams = {
|
||||||
ma44_strategy: [
|
ma_trend: [
|
||||||
{ name: 'maPeriod', label: 'MA Period', type: 'number', default: 44, min: 5, max: 500 }
|
{ name: 'period', label: 'MA Period', type: 'number', default: 44, min: 5, max: 500 }
|
||||||
],
|
|
||||||
ma125_strategy: [
|
|
||||||
{ name: 'maPeriod', label: 'MA Period', type: 'number', default: 125, min: 5, max: 500 }
|
|
||||||
]
|
]
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -2462,7 +2475,8 @@
|
|||||||
value="${param.default}"
|
value="${param.default}"
|
||||||
${param.min !== undefined ? `min="${param.min}"` : ''}
|
${param.min !== undefined ? `min="${param.min}"` : ''}
|
||||||
${param.max !== undefined ? `max="${param.max}"` : ''}
|
${param.max !== undefined ? `max="${param.max}"` : ''}
|
||||||
${param.step !== undefined ? `step="${param.step}"` : ''}>
|
${param.step !== undefined ? `step="${param.step}"` : ''}
|
||||||
|
>
|
||||||
</div>
|
</div>
|
||||||
`).join('');
|
`).join('');
|
||||||
}
|
}
|
||||||
@ -2571,6 +2585,7 @@
|
|||||||
// Build strategy config
|
// Build strategy config
|
||||||
const engineConfig = {
|
const engineConfig = {
|
||||||
id: strategyConfig.id,
|
id: strategyConfig.id,
|
||||||
|
params: strategyConfig.params,
|
||||||
timeframes: { primary: interval, secondary: secondaryTF ? [secondaryTF] : [] },
|
timeframes: { primary: interval, secondary: secondaryTF ? [secondaryTF] : [] },
|
||||||
indicators: []
|
indicators: []
|
||||||
};
|
};
|
||||||
@ -2581,37 +2596,21 @@
|
|||||||
console.log(' Available candles:', Object.keys(candlesMap));
|
console.log(' Available candles:', Object.keys(candlesMap));
|
||||||
|
|
||||||
// Add indicator based on strategy
|
// Add indicator based on strategy
|
||||||
if (strategyConfig.id === 'ma44_strategy') {
|
if (strategyConfig.id === 'ma_trend') {
|
||||||
|
const period = strategyConfig.params?.period || 44;
|
||||||
// Primary timeframe indicator
|
// Primary timeframe indicator
|
||||||
engineConfig.indicators.push({
|
engineConfig.indicators.push({
|
||||||
name: 'ma44',
|
name: `ma${period}`,
|
||||||
type: 'sma',
|
type: 'sma',
|
||||||
params: { period: strategyConfig.params.maPeriod || 44 },
|
params: { period: period },
|
||||||
timeframe: interval
|
timeframe: interval
|
||||||
});
|
});
|
||||||
// Confirmation timeframe indicator (for trend filter)
|
// Confirmation timeframe indicator (for trend filter)
|
||||||
if (secondaryTF) {
|
if (secondaryTF) {
|
||||||
engineConfig.indicators.push({
|
engineConfig.indicators.push({
|
||||||
name: `ma44_${secondaryTF}`,
|
name: `ma${period}_${secondaryTF}`,
|
||||||
type: 'sma',
|
type: 'sma',
|
||||||
params: { period: strategyConfig.params.maPeriod || 44 },
|
params: { period: period },
|
||||||
timeframe: secondaryTF
|
|
||||||
});
|
|
||||||
}
|
|
||||||
} else if (strategyConfig.id === 'ma125_strategy') {
|
|
||||||
// Primary timeframe indicator
|
|
||||||
engineConfig.indicators.push({
|
|
||||||
name: 'ma125',
|
|
||||||
type: 'sma',
|
|
||||||
params: { period: strategyConfig.params.maPeriod || 125 },
|
|
||||||
timeframe: interval
|
|
||||||
});
|
|
||||||
// Confirmation timeframe indicator (for trend filter)
|
|
||||||
if (secondaryTF) {
|
|
||||||
engineConfig.indicators.push({
|
|
||||||
name: `ma125_${secondaryTF}`,
|
|
||||||
type: 'sma',
|
|
||||||
params: { period: strategyConfig.params.maPeriod || 125 },
|
|
||||||
timeframe: secondaryTF
|
timeframe: secondaryTF
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@ -2652,6 +2651,23 @@
|
|||||||
// Show results section
|
// Show results section
|
||||||
document.getElementById('resultsSection').style.display = 'block';
|
document.getElementById('resultsSection').style.display = 'block';
|
||||||
|
|
||||||
|
// Update chart with full historical data from simulation
|
||||||
|
if (window.dashboard && candlesMap[interval]) {
|
||||||
|
const chartData = candlesMap[interval].map(c => ({
|
||||||
|
time: c.time,
|
||||||
|
open: c.open,
|
||||||
|
high: c.high,
|
||||||
|
low: c.low,
|
||||||
|
close: c.close
|
||||||
|
}));
|
||||||
|
window.dashboard.candleSeries.setData(chartData);
|
||||||
|
window.dashboard.allData.set(interval, chartData);
|
||||||
|
console.log(`Chart updated with ${chartData.length} candles from simulation range`);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Show simulation markers on chart
|
||||||
|
showSimulationMarkers();
|
||||||
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Simulation error:', error);
|
console.error('Simulation error:', error);
|
||||||
alert('Simulation error: ' + error.message);
|
alert('Simulation error: ' + error.message);
|
||||||
@ -3112,7 +3128,7 @@
|
|||||||
const controller = new AbortController();
|
const controller = new AbortController();
|
||||||
const timeoutId = setTimeout(() => controller.abort(), 5000);
|
const timeoutId = setTimeout(() => controller.abort(), 5000);
|
||||||
|
|
||||||
const response = await fetch('/api/v1/strategies', {
|
const response = await fetch('/api/v1/strategies?_=' + Date.now(), {
|
||||||
signal: controller.signal
|
signal: controller.signal
|
||||||
});
|
});
|
||||||
clearTimeout(timeoutId);
|
clearTimeout(timeoutId);
|
||||||
|
|||||||
@ -10,7 +10,7 @@ from datetime import datetime, timedelta, timezone
|
|||||||
from typing import Optional, List
|
from typing import Optional, List
|
||||||
from contextlib import asynccontextmanager
|
from contextlib import asynccontextmanager
|
||||||
|
|
||||||
from fastapi import FastAPI, HTTPException, Query, BackgroundTasks
|
from fastapi import FastAPI, HTTPException, Query, BackgroundTasks, Response
|
||||||
from fastapi.staticfiles import StaticFiles
|
from fastapi.staticfiles import StaticFiles
|
||||||
from fastapi.responses import StreamingResponse
|
from fastapi.responses import StreamingResponse
|
||||||
from fastapi.middleware.cors import CORSMiddleware
|
from fastapi.middleware.cors import CORSMiddleware
|
||||||
@ -100,12 +100,16 @@ async def root():
|
|||||||
|
|
||||||
|
|
||||||
@app.get("/api/v1/strategies")
|
@app.get("/api/v1/strategies")
|
||||||
async def list_strategies():
|
async def list_strategies(response: Response):
|
||||||
"""List all available trading strategies with metadata"""
|
"""List all available trading strategies with metadata"""
|
||||||
|
# Prevent caching
|
||||||
|
response.headers["Cache-Control"] = "no-cache, no-store, must-revalidate"
|
||||||
|
response.headers["Pragma"] = "no-cache"
|
||||||
|
response.headers["Expires"] = "0"
|
||||||
|
|
||||||
# Strategy registry from brain.py
|
# Strategy registry from brain.py
|
||||||
strategy_registry = {
|
strategy_registry = {
|
||||||
"ma44_strategy": "src.strategies.ma44_strategy.MA44Strategy",
|
"ma_trend": "src.strategies.ma_strategy.MAStrategy",
|
||||||
"ma125_strategy": "src.strategies.ma125_strategy.MA125Strategy",
|
|
||||||
}
|
}
|
||||||
|
|
||||||
strategies = []
|
strategies = []
|
||||||
|
|||||||
@ -28,7 +28,9 @@ class HyperliquidBackfill:
|
|||||||
|
|
||||||
API_URL = "https://api.hyperliquid.xyz/info"
|
API_URL = "https://api.hyperliquid.xyz/info"
|
||||||
MAX_CANDLES_PER_REQUEST = 500
|
MAX_CANDLES_PER_REQUEST = 500
|
||||||
MAX_TOTAL_CANDLES = 5000
|
# Hyperliquid API might limit total history, but we'll set a high limit
|
||||||
|
# and stop when no more data is returned
|
||||||
|
MAX_TOTAL_CANDLES = 500000
|
||||||
|
|
||||||
# Standard timeframes supported by Hyperliquid
|
# Standard timeframes supported by Hyperliquid
|
||||||
INTERVALS = [
|
INTERVALS = [
|
||||||
|
|||||||
@ -1,63 +0,0 @@
|
|||||||
"""
|
|
||||||
MA125 Strategy
|
|
||||||
Simple trend following strategy.
|
|
||||||
- Long when Price > MA125
|
|
||||||
- Short when Price < MA125
|
|
||||||
"""
|
|
||||||
|
|
||||||
from typing import Dict, Any, List, Optional
|
|
||||||
from .base import BaseStrategy, StrategySignal, SignalType
|
|
||||||
|
|
||||||
class MA125Strategy(BaseStrategy):
|
|
||||||
@property
|
|
||||||
def name(self) -> str:
|
|
||||||
return "ma125_strategy"
|
|
||||||
|
|
||||||
@property
|
|
||||||
def required_indicators(self) -> List[str]:
|
|
||||||
return ["ma125"]
|
|
||||||
|
|
||||||
@property
|
|
||||||
def display_name(self) -> str:
|
|
||||||
return "MA125 Strategy"
|
|
||||||
|
|
||||||
@property
|
|
||||||
def description(self) -> str:
|
|
||||||
return "Long-term trend following using 125-period moving average. Better for identifying major trends."
|
|
||||||
|
|
||||||
def analyze(
|
|
||||||
self,
|
|
||||||
candle: Dict[str, Any],
|
|
||||||
indicators: Dict[str, float],
|
|
||||||
current_position: Optional[Dict[str, Any]] = None
|
|
||||||
) -> StrategySignal:
|
|
||||||
|
|
||||||
price = candle['close']
|
|
||||||
ma125 = indicators.get('ma125')
|
|
||||||
|
|
||||||
if ma125 is None:
|
|
||||||
return StrategySignal(SignalType.HOLD, 0.0, "MA125 not available")
|
|
||||||
|
|
||||||
# Current position state
|
|
||||||
is_long = current_position and current_position.get('type') == 'long'
|
|
||||||
is_short = current_position and current_position.get('type') == 'short'
|
|
||||||
|
|
||||||
# Logic: Price > MA125 -> Bullish
|
|
||||||
if price > ma125:
|
|
||||||
if is_long:
|
|
||||||
return StrategySignal(SignalType.HOLD, 1.0, f"Price {price:.2f} > MA125 {ma125:.2f}. Stay Long.")
|
|
||||||
elif is_short:
|
|
||||||
return StrategySignal(SignalType.CLOSE_SHORT, 1.0, f"Price {price:.2f} crossed above MA125 {ma125:.2f}. Close Short.")
|
|
||||||
else:
|
|
||||||
return StrategySignal(SignalType.OPEN_LONG, 1.0, f"Price {price:.2f} > MA125 {ma125:.2f}. Open Long.")
|
|
||||||
|
|
||||||
# Logic: Price < MA125 -> Bearish
|
|
||||||
elif price < ma125:
|
|
||||||
if is_short:
|
|
||||||
return StrategySignal(SignalType.HOLD, 1.0, f"Price {price:.2f} < MA125 {ma125:.2f}. Stay Short.")
|
|
||||||
elif is_long:
|
|
||||||
return StrategySignal(SignalType.CLOSE_LONG, 1.0, f"Price {price:.2f} crossed below MA125 {ma125:.2f}. Close Long.")
|
|
||||||
else:
|
|
||||||
return StrategySignal(SignalType.OPEN_SHORT, 1.0, f"Price {price:.2f} < MA125 {ma125:.2f}. Open Short.")
|
|
||||||
|
|
||||||
return StrategySignal(SignalType.HOLD, 0.0, "Price == MA125")
|
|
||||||
@ -1,63 +0,0 @@
|
|||||||
"""
|
|
||||||
MA44 Strategy
|
|
||||||
Simple trend following strategy.
|
|
||||||
- Long when Price > MA44
|
|
||||||
- Short when Price < MA44
|
|
||||||
"""
|
|
||||||
|
|
||||||
from typing import Dict, Any, List, Optional
|
|
||||||
from .base import BaseStrategy, StrategySignal, SignalType
|
|
||||||
|
|
||||||
class MA44Strategy(BaseStrategy):
|
|
||||||
@property
|
|
||||||
def name(self) -> str:
|
|
||||||
return "ma44_strategy"
|
|
||||||
|
|
||||||
@property
|
|
||||||
def required_indicators(self) -> List[str]:
|
|
||||||
return ["ma44"]
|
|
||||||
|
|
||||||
@property
|
|
||||||
def display_name(self) -> str:
|
|
||||||
return "MA44 Strategy"
|
|
||||||
|
|
||||||
@property
|
|
||||||
def description(self) -> str:
|
|
||||||
return "Buy when price crosses above MA44, sell when below. Good for trending markets."
|
|
||||||
|
|
||||||
def analyze(
|
|
||||||
self,
|
|
||||||
candle: Dict[str, Any],
|
|
||||||
indicators: Dict[str, float],
|
|
||||||
current_position: Optional[Dict[str, Any]] = None
|
|
||||||
) -> StrategySignal:
|
|
||||||
|
|
||||||
price = candle['close']
|
|
||||||
ma44 = indicators.get('ma44')
|
|
||||||
|
|
||||||
if ma44 is None:
|
|
||||||
return StrategySignal(SignalType.HOLD, 0.0, "MA44 not available")
|
|
||||||
|
|
||||||
# Current position state
|
|
||||||
is_long = current_position and current_position.get('type') == 'long'
|
|
||||||
is_short = current_position and current_position.get('type') == 'short'
|
|
||||||
|
|
||||||
# Logic: Price > MA44 -> Bullish
|
|
||||||
if price > ma44:
|
|
||||||
if is_long:
|
|
||||||
return StrategySignal(SignalType.HOLD, 1.0, f"Price {price:.2f} > MA44 {ma44:.2f}. Stay Long.")
|
|
||||||
elif is_short:
|
|
||||||
return StrategySignal(SignalType.CLOSE_SHORT, 1.0, f"Price {price:.2f} crossed above MA44 {ma44:.2f}. Close Short.")
|
|
||||||
else:
|
|
||||||
return StrategySignal(SignalType.OPEN_LONG, 1.0, f"Price {price:.2f} > MA44 {ma44:.2f}. Open Long.")
|
|
||||||
|
|
||||||
# Logic: Price < MA44 -> Bearish
|
|
||||||
elif price < ma44:
|
|
||||||
if is_short:
|
|
||||||
return StrategySignal(SignalType.HOLD, 1.0, f"Price {price:.2f} < MA44 {ma44:.2f}. Stay Short.")
|
|
||||||
elif is_long:
|
|
||||||
return StrategySignal(SignalType.CLOSE_LONG, 1.0, f"Price {price:.2f} crossed below MA44 {ma44:.2f}. Close Long.")
|
|
||||||
else:
|
|
||||||
return StrategySignal(SignalType.OPEN_SHORT, 1.0, f"Price {price:.2f} < MA44 {ma44:.2f}. Open Short.")
|
|
||||||
|
|
||||||
return StrategySignal(SignalType.HOLD, 0.0, "Price == MA44")
|
|
||||||
77
src/strategies/ma_strategy.py
Normal file
77
src/strategies/ma_strategy.py
Normal file
@ -0,0 +1,77 @@
|
|||||||
|
"""
|
||||||
|
Moving Average Strategy
|
||||||
|
Configurable trend following strategy.
|
||||||
|
- Long when Price > MA(period)
|
||||||
|
- Short when Price < MA(period)
|
||||||
|
"""
|
||||||
|
|
||||||
|
from typing import Dict, Any, List, Optional
|
||||||
|
from .base import BaseStrategy, StrategySignal, SignalType
|
||||||
|
|
||||||
|
class MAStrategy(BaseStrategy):
|
||||||
|
"""
|
||||||
|
Configurable Moving Average Strategy.
|
||||||
|
|
||||||
|
Config:
|
||||||
|
- period: int - MA period (default: 44)
|
||||||
|
"""
|
||||||
|
|
||||||
|
DEFAULT_PERIOD = 44
|
||||||
|
|
||||||
|
@property
|
||||||
|
def name(self) -> str:
|
||||||
|
return "ma_trend"
|
||||||
|
|
||||||
|
@property
|
||||||
|
def required_indicators(self) -> List[str]:
|
||||||
|
# Dynamic based on config
|
||||||
|
period = self.config.get('period', self.DEFAULT_PERIOD)
|
||||||
|
return [f"ma{period}"]
|
||||||
|
|
||||||
|
@property
|
||||||
|
def display_name(self) -> str:
|
||||||
|
return "MA Strategy"
|
||||||
|
|
||||||
|
@property
|
||||||
|
def description(self) -> str:
|
||||||
|
return "Configurable Moving Average strategy. Parameters: period (5-500, default: 44). Goes long when price > MA(period), short when price < MA(period). Optional multi-timeframe trend filter available."
|
||||||
|
|
||||||
|
def analyze(
|
||||||
|
self,
|
||||||
|
candle: Dict[str, Any],
|
||||||
|
indicators: Dict[str, float],
|
||||||
|
current_position: Optional[Dict[str, Any]] = None
|
||||||
|
) -> StrategySignal:
|
||||||
|
|
||||||
|
period = self.config.get('period', self.DEFAULT_PERIOD)
|
||||||
|
ma_key = f"ma{period}"
|
||||||
|
|
||||||
|
price = candle['close']
|
||||||
|
ma_value = indicators.get(ma_key)
|
||||||
|
|
||||||
|
if ma_value is None:
|
||||||
|
return StrategySignal(SignalType.HOLD, 0.0, f"MA{period} not available")
|
||||||
|
|
||||||
|
# Current position state
|
||||||
|
is_long = current_position and current_position.get('type') == 'long'
|
||||||
|
is_short = current_position and current_position.get('type') == 'short'
|
||||||
|
|
||||||
|
# Logic: Price > MA -> Bullish
|
||||||
|
if price > ma_value:
|
||||||
|
if is_long:
|
||||||
|
return StrategySignal(SignalType.HOLD, 1.0, f"Price {price:.2f} > MA{period} {ma_value:.2f}. Stay Long.")
|
||||||
|
elif is_short:
|
||||||
|
return StrategySignal(SignalType.CLOSE_SHORT, 1.0, f"Price {price:.2f} crossed above MA{period} {ma_value:.2f}. Close Short.")
|
||||||
|
else:
|
||||||
|
return StrategySignal(SignalType.OPEN_LONG, 1.0, f"Price {price:.2f} > MA{period} {ma_value:.2f}. Open Long.")
|
||||||
|
|
||||||
|
# Logic: Price < MA -> Bearish
|
||||||
|
elif price < ma_value:
|
||||||
|
if is_short:
|
||||||
|
return StrategySignal(SignalType.HOLD, 1.0, f"Price {price:.2f} < MA{period} {ma_value:.2f}. Stay Short.")
|
||||||
|
elif is_long:
|
||||||
|
return StrategySignal(SignalType.CLOSE_LONG, 1.0, f"Price {price:.2f} crossed below MA{period} {ma_value:.2f}. Close Long.")
|
||||||
|
else:
|
||||||
|
return StrategySignal(SignalType.OPEN_SHORT, 1.0, f"Price {price:.2f} < MA{period} {ma_value:.2f}. Open Short.")
|
||||||
|
|
||||||
|
return StrategySignal(SignalType.HOLD, 0.0, f"Price == MA{period}")
|
||||||
Reference in New Issue
Block a user