""" Writes dashboard strategy settings into freqtrade's config.json and generates a matching strategy .py file so freqtrade picks them up. """ import json from pathlib import Path from app.config import settings from app.models import StrategyConfig STRATEGY_TEMPLATE = '''\ """Auto-generated strategy from dashboard. Do not edit manually.""" import numpy as np import pandas as pd from pandas import DataFrame from freqtrade.strategy import IStrategy, IntParameter import talib.abstract as ta from technical import qtpylib class DashboardStrategy(IStrategy): INTERFACE_VERSION = 3 can_short = False minimal_roi = {{ "60": 0.01, "30": 0.02, "0": 0.04, }} stoploss = {stoploss} trailing_stop = False timeframe = "{timeframe}" process_only_new_candles = True startup_candle_count = {startup_candle_count} use_exit_signal = True exit_profit_only = False ignore_roi_if_entry_signal = False buy_rsi = IntParameter(low=1, high=100, default={rsi_buy_threshold}, space="buy", optimize=True, load=True) sell_rsi = IntParameter(low=1, high=100, default={rsi_sell_threshold}, space="sell", optimize=True, load=True) order_types = {{ "entry": "limit", "exit": "limit", "stoploss": "market", "stoploss_on_exchange": False, }} order_time_in_force = {{"entry": "GTC", "exit": "GTC"}} def populate_indicators(self, dataframe: DataFrame, metadata: dict) -> DataFrame: dataframe["rsi"] = ta.RSI(dataframe, timeperiod={rsi_period}) dataframe["ema"] = ta.EMA(dataframe, timeperiod={ema_period}) bollinger = qtpylib.bollinger_bands( qtpylib.typical_price(dataframe), window={bollinger_window}, stds={bollinger_deviation} ) dataframe["bb_lowerband"] = bollinger["lower"] dataframe["bb_middleband"] = bollinger["mid"] dataframe["bb_upperband"] = bollinger["upper"] return dataframe def populate_entry_trend(self, dataframe: DataFrame, metadata: dict) -> DataFrame: dataframe.loc[ ( (qtpylib.crossed_above(dataframe["rsi"], self.buy_rsi.value)) & (dataframe["ema"] <= dataframe["bb_middleband"]) & (dataframe["ema"] > dataframe["ema"].shift(1)) & (dataframe["volume"] > 0) ), "enter_long", ] = 1 return dataframe def populate_exit_trend(self, dataframe: DataFrame, metadata: dict) -> DataFrame: dataframe.loc[ ( (qtpylib.crossed_above(dataframe["rsi"], self.sell_rsi.value)) & (dataframe["ema"] > dataframe["bb_middleband"]) & (dataframe["ema"] < dataframe["ema"].shift(1)) & (dataframe["volume"] > 0) ), "exit_long", ] = 1 return dataframe ''' def apply_strategy_to_freqtrade(config: StrategyConfig) -> None: _write_config_json(config) _write_strategy_file(config) def _write_config_json(config: StrategyConfig) -> None: config_path = Path(settings.FREQTRADE_CONFIG_PATH) with open(config_path, "r") as f: ft_config = json.load(f) ft_config["timeframe"] = config.timeframe ft_config["max_open_trades"] = config.max_open_trades ft_config["stake_amount"] = config.stake_amount ft_config["dry_run"] = config.dry_run ft_config["dry_run_wallet"] = config.dry_run_wallet ft_config["exchange"]["name"] = config.exchange_name # Keys are managed via environment variables on the dashboard host, # not via per-user strategy configs. if settings.FREQTRADE_EXCHANGE_KEY: ft_config["exchange"]["key"] = settings.FREQTRADE_EXCHANGE_KEY if settings.FREQTRADE_EXCHANGE_SECRET: ft_config["exchange"]["secret"] = settings.FREQTRADE_EXCHANGE_SECRET with open(config_path, "w") as f: json.dump(ft_config, f, indent=4) def _write_strategy_file(config: StrategyConfig) -> None: strategy_dir = Path(settings.FREQTRADE_STRATEGY_DIR) strategy_dir.mkdir(parents=True, exist_ok=True) strategy_path = strategy_dir / "dashboard_strategy.py" startup_candles = max(config.ema_period, config.bollinger_window, config.rsi_period) + 50 content = STRATEGY_TEMPLATE.format( stoploss=config.stoploss, timeframe=config.timeframe, startup_candle_count=startup_candles, rsi_period=config.rsi_period, rsi_buy_threshold=config.rsi_buy_threshold, rsi_sell_threshold=config.rsi_sell_threshold, ema_period=config.ema_period, bollinger_window=config.bollinger_window, bollinger_deviation=config.bollinger_deviation, ) strategy_path.write_text(content)