110 lines
4.1 KiB
Python
110 lines
4.1 KiB
Python
# /freqtrade/user_data/strategies/StaticGrid.py
|
|
from freqtrade.strategy import IStrategy
|
|
from pandas import DataFrame
|
|
import logging
|
|
import sys
|
|
|
|
logger = logging.getLogger(__name__)
|
|
|
|
class StaticGrid(IStrategy):
|
|
INTERFACE_VERSION = 3
|
|
timeframe = '1h'
|
|
can_short = False
|
|
minimal_roi = {"0": 100}
|
|
stoploss = -0.99
|
|
use_exit_signal = True
|
|
position_adjustment_enable = True
|
|
max_entry_position_adjustment = -1
|
|
|
|
# Lock configuration - disable lockout to allow immediate re-entry
|
|
cooldown_candles = 0 # No cooldown between trades
|
|
|
|
# Grid parameters
|
|
LOWER = 1500.0 # Minimum price
|
|
UPPER = 4500.0 # Maximum price
|
|
STEP = 50.0 # Grid spacing
|
|
STAKE = 40.0 # Per-trade stake
|
|
|
|
def __init__(self, config: dict) -> None:
|
|
super().__init__(config)
|
|
# Force write to stderr immediately on startup
|
|
print("[StaticGrid] Strategy initialized!", file=sys.stderr, flush=True)
|
|
|
|
def populate_indicators(self, dataframe: DataFrame, metadata: dict) -> DataFrame:
|
|
print(f"[StaticGrid] populate_indicators called, df len: {len(dataframe)}", file=sys.stderr, flush=True)
|
|
return dataframe
|
|
|
|
def populate_entry_trend(self, dataframe: DataFrame, metadata: dict) -> DataFrame:
|
|
"""
|
|
Static Grid Entry Logic:
|
|
- Buy at predefined grid levels: 1500, 1550, 1600, ..., 4500 (step 50)
|
|
- Also buy AT CURRENT PRICE and below it
|
|
- When price is at or below a grid level, produce buy signal
|
|
"""
|
|
dataframe['enter_long'] = False
|
|
|
|
if len(dataframe) == 0:
|
|
return dataframe
|
|
|
|
current_price = dataframe['close'].iloc[-1]
|
|
current_low = dataframe['low'].iloc[-1]
|
|
print(f"[StaticGrid] Current price: {current_price:.2f}, low: {current_low:.2f}", file=sys.stderr, flush=True)
|
|
|
|
# Define static grid levels
|
|
LOWER = 1500.0
|
|
UPPER = 4500.0
|
|
STEP = 50.0
|
|
|
|
# Generate all grid levels in the range
|
|
grid_levels = [LOWER + i * STEP for i in range(int((UPPER - LOWER) / STEP) + 1)]
|
|
|
|
# IMPORTANT: Add current price as a grid level too (rounded to nearest STEP)
|
|
# This ensures we can buy immediately at current price
|
|
current_grid_level = round(current_price / STEP) * STEP
|
|
if current_grid_level not in grid_levels and LOWER <= current_grid_level <= UPPER:
|
|
grid_levels.append(current_grid_level)
|
|
|
|
grid_levels.sort()
|
|
|
|
entry_count = 0
|
|
|
|
# For each grid level, check if price is at or below it
|
|
for grid_price in grid_levels:
|
|
# Buy if current low is at or below this grid level (with 0.5% tolerance)
|
|
if current_low <= grid_price * 1.005:
|
|
dataframe['enter_long'] = True
|
|
entry_count += 1
|
|
if entry_count <= 5: # Only print first 5 for brevity
|
|
print(f"[StaticGrid] Entry at grid {grid_price:.0f}", file=sys.stderr, flush=True)
|
|
|
|
if entry_count > 5:
|
|
print(f"[StaticGrid] ... and {entry_count - 5} more grid levels", file=sys.stderr, flush=True)
|
|
|
|
print(f"[StaticGrid] Total entry signals: {entry_count}", file=sys.stderr, flush=True)
|
|
|
|
return dataframe
|
|
|
|
def populate_exit_trend(self, dataframe: DataFrame, metadata: dict) -> DataFrame:
|
|
"""
|
|
Exit Logic - AGGRESSIVE:
|
|
- Exit as soon as price moves up at all (even 0.01% profit)
|
|
- This ensures we close positions quickly for grid trading
|
|
"""
|
|
dataframe['exit_long'] = False
|
|
|
|
if len(dataframe) < 2:
|
|
return dataframe
|
|
|
|
# EXTREME: Exit on ANY upward movement (even micro profits)
|
|
# This forces position closure to allow new entries
|
|
dataframe['exit_long'] = (dataframe['close'] > dataframe['open']).fillna(False)
|
|
|
|
exits = dataframe['exit_long'].sum()
|
|
if exits > 0:
|
|
print(f"[StaticGrid] Exit signals: {exits}", file=sys.stderr, flush=True)
|
|
|
|
return dataframe
|
|
|
|
def custom_stake_amount(self, **kwargs) -> float:
|
|
return self.STAKE
|