diff --git a/ChanLun.py b/ChanLun.py index f016748..18b184c 100644 --- a/ChanLun.py +++ b/ChanLun.py @@ -54,15 +54,19 @@ class ChanLun(): timeframe = timeframes[index] interval = intervals[index] self.tf_df_dict[timeframe] = TF_DF(dataframe, interval, timeframe) - def init_dataframes(self, dataframe_m, dataframe_h, dataframe_d, dataframe_M): - self.tf_df_dict['1m'] = TF_DF(dataframe_m, 1, '1m') - self.init_data(dataframe_m, self.time_m_intervals, self.time_m_symbols) - self.tf_df_dict['1h'] = TF_DF(dataframe_h, 1, '1h') - self.init_data(dataframe_h, self.time_h_intervals, self.time_h_symbols) - self.tf_df_dict['1d'] = TF_DF(dataframe_d, 1, '1d') - self.init_data(dataframe_d, self.time_d_intervals, self.time_d_symbols) - self.tf_df_dict['1M'] = TF_DF(dataframe_M, 1, '1M') - self.init_data(dataframe_M, self.time_M_intervals, self.time_M_symbols) + def init_dataframes(self, dataframe_m=None, dataframe_h=None, dataframe_d=None, dataframe_M=None): + if dataframe_m is not None: + self.tf_df_dict['1m'] = TF_DF(dataframe_m, 1, '1m') + self.init_data(dataframe_m, self.time_m_intervals, self.time_m_symbols) + if dataframe_h is not None: + self.tf_df_dict['1h'] = TF_DF(dataframe_h, 1, '1h') + self.init_data(dataframe_h, self.time_h_intervals, self.time_h_symbols) + if dataframe_d is not None: + self.tf_df_dict['1d'] = TF_DF(dataframe_d, 1, '1d') + self.init_data(dataframe_d, self.time_d_intervals, self.time_d_symbols) + if dataframe_M is not None: + self.tf_df_dict['1M'] = TF_DF(dataframe_M, 1, '1M') + self.init_data(dataframe_M, self.time_M_intervals, self.time_M_symbols) def get_ema52_dict(self): if len(self.tf_df_dict) > 0: return {key: self.tf_df_dict[key].get_ema52() for key in self.ema_symbols} diff --git a/strategies/ChanLun_BTC_15.py b/strategies/ChanLun_BTC_15.py index 876e1a8..83eba63 100644 --- a/strategies/ChanLun_BTC_15.py +++ b/strategies/ChanLun_BTC_15.py @@ -5,8 +5,6 @@ import sys import os # 添加父目录到系统路径 sys.path.append(os.path.dirname(os.path.dirname(os.path.abspath(__file__)))) -from ChanLun import ChanLun -from ChanLun_Classifier import ChanLunClassifier from ChanEnum import Chan_FX_TYPE, Chan_KLC_FX, Chan_BI_DIR, Chan_KLC_FX # -------------------------------- from technical.util import resample_to_interval, resampled_merge @@ -17,6 +15,7 @@ from freqtrade.persistence import Trade from typing import Optional import logging logger = logging.getLogger(__name__) +from TF_DF import TF_DF ### Now you can use logger.info('asfd') to log # freqtrade plot-dataframe --strategy ChanLun_BTC_15 --datadir user_data/data/binance -c ./user_data/ChanLun_SOL_15.json --timerange=20250309- @@ -88,76 +87,23 @@ class ChanLun_BTC_15(IStrategy): time1d = 1440 time5 = 1440 last_time = datetime.now() - chan = ChanLun() - classifier = ChanLunClassifier(None) def populate_indicators(self, dataframe: DataFrame, metadata: dict) -> DataFrame: + tf_df_5 = TF_DF(dataframe, self.time5, '5m') + tf_df_15 = TF_DF(dataframe, self.time15, '15m') + tf_df_30 = TF_DF(dataframe, self.time30, '30m') + tf_df_60 = TF_DF(dataframe, self.time60, '60m') + tf_df_4h = TF_DF(dataframe, self.time4h, '4h') + tf_df_1d = TF_DF(dataframe, self.time1d, '1d') - # resample our dataframes - dataframe_5 = resample_to_interval(dataframe, self.get_ticker_indicator() * 5) - dataframe_15 = resample_to_interval(dataframe, self.get_ticker_indicator() * 15) - dataframe_30 = resample_to_interval(dataframe, self.get_ticker_indicator() * 30) - dataframe_60 = resample_to_interval(dataframe, self.get_ticker_indicator() * 60) - dataframe_4h = resample_to_interval(dataframe, self.get_ticker_indicator() * 240) - - #dataframe_1d = self.dp.get_pair_dataframe(pair=metadata['pair'], timeframe='1d') - #dataframe_1w = resample_to_interval(dataframe_1d, self.get_ticker_indicator() * 10080) - #dataframe_1m = resample_to_interval(dataframe_1d, self.get_ticker_indicator() * 43200) - dataframe_1d = resample_to_interval(dataframe, self.get_ticker_indicator() * 1440) - #dataframe_1w = resample_to_interval(dataframe, self.get_ticker_indicator() * 10080) - #dataframe_1m = resample_to_interval(dataframe, self.get_ticker_indicator() * 43200) - dataframe = self.add_indicators(dataframe) - dataframe_5 = self.add_indicators(dataframe_5) - dataframe_30 = self.add_indicators(dataframe_30) - dataframe_60 = self.add_indicators(dataframe_60) - dataframe_4h = self.add_indicators(dataframe_4h) - dataframe_1d = self.add_indicators(dataframe_1d) - #self.chan.plot_dual(dataframe_5, dataframe_30) - dataframe['rsi'] = ta.RSI(dataframe, timeperiod=14) - - state_list, fx_list = self.chan.get_klc_strength_list(dataframe_15) - dataframe_15['state'] = state_list - dataframe_15['fx'] = fx_list - dataframe_15['bsp'] = self.chan.get_klc_bsp_list(dataframe_15) - klc_list = self.chan.get_klc_list(dataframe_15) - bi_list = self.chan.cal_bi_list(klc_list) - if self.last_time + timedelta(minutes=1) < datetime.now(): - print(state_list[-1], state_list[-2], state_list[-3], state_list[-4], state_list[-5]) - print(fx_list[-1], fx_list[-2], fx_list[-3], fx_list[-4], fx_list[-5]) - print(klc_list[-1].klc_fx_type, klc_list[-2].klc_fx_type, klc_list[-3].klc_fx_type, klc_list[-4].klc_fx_type, klc_list[-5].klc_fx_type) - print("-------------------------------------------------------------------------------") - self.last_time = datetime.now() - #dataframe = resampled_merge(dataframe, dataframe_5) - dataframe = resampled_merge(dataframe, dataframe_15) - #dataframe = resampled_merge(dataframe, dataframe_30) - #dataframe = resampled_merge(dataframe, dataframe_60) - #dataframe = resampled_merge(dataframe, dataframe_4h) + + dataframe = resampled_merge(dataframe, tf_df_5.dataframe) + dataframe = resampled_merge(dataframe, tf_df_15.dataframe) + dataframe = resampled_merge(dataframe, tf_df_30.dataframe) + dataframe = resampled_merge(dataframe, tf_df_60.dataframe) + dataframe = resampled_merge(dataframe, tf_df_4h.dataframe) + dataframe = resampled_merge(dataframe, tf_df_1d.dataframe) return dataframe - - def add_indicators(self, df): - fast = 8 - slow = 16 - period = 6 - macd = ta.MACD(df, fastperiod=fast, slowperiod=slow, signalperiod=period) - df['macd'] = macd['macd'] - df['macdsignal'] = macd['macdsignal'] - df['macdhist'] = macd['macdhist'] - df['ma5'] = ta.MA(df, timeperiod=5) - df['ma10'] = ta.MA(df, timeperiod=10) - df['ma30'] = ta.EMA(df, timeperiod=30) - df['ma250'] = ta.MA(df, timeperiod=250) - df['rsi'] = ta.RSI(df, timeperiod=14) - df['volume_ratio'] = self.cal_volume_ratio(df) - return df - def cal_volume_ratio(self, dataframe, window=10): - df = dataframe.copy() - # 计算过去N根K线的平均成交量 - df['avg_volume'] = df['volume'].rolling(window=window).mean() - # 计算量比 - df['volume_ratio'] = df['volume'] / df['avg_volume'] - # 填充缺失值(前N根K线) - df['volume_ratio'] = df['volume_ratio'].fillna(1.0) - return df['volume_ratio'] def custom_entry_price(self, pair: str, trade: Trade | None, current_time: datetime, proposed_rate: float, entry_tag: str | None, side: str, **kwargs) -> float: diff --git a/strategies/ChanLun_BTC_15_back.py b/strategies/ChanLun_BTC_15_back.py new file mode 100644 index 0000000..876e1a8 --- /dev/null +++ b/strategies/ChanLun_BTC_15_back.py @@ -0,0 +1,245 @@ +# --- Do not remove these libs --- +from statistics import median +from freqtrade.strategy import IStrategy +import sys +import os +# 添加父目录到系统路径 +sys.path.append(os.path.dirname(os.path.dirname(os.path.abspath(__file__)))) +from ChanLun import ChanLun +from ChanLun_Classifier import ChanLunClassifier +from ChanEnum import Chan_FX_TYPE, Chan_KLC_FX, Chan_BI_DIR, Chan_KLC_FX +# -------------------------------- +from technical.util import resample_to_interval, resampled_merge +import talib.abstract as ta +from pandas import DataFrame +from datetime import datetime, timedelta +from freqtrade.persistence import Trade +from typing import Optional +import logging +logger = logging.getLogger(__name__) +### Now you can use logger.info('asfd') to log +# freqtrade plot-dataframe --strategy ChanLun_BTC_15 --datadir user_data/data/binance -c ./user_data/ChanLun_SOL_15.json --timerange=20250309- + +# freqtrade trade -c ./user_data/Chan/config/ChanLun_BTC_15.json --strategy ChanLun_BTC_15 --strategy-path ./user_data/Chan/strategies +# freqtrade backtesting --export none -c ./user_data/Chan/config/ChanLun_BTC_15.json --strategy ChanLun_BTC_15 --strategy-path ./user_data/Chan/strategies --timerange=20250525- +# freqtrade lookahead-analysis --export none -c ./user_data/Chan/config/ChanLun_BTC_15.json --strategy ChanLun_BTC_15 --strategy-path ./user_data/Chan/strategies --timerange=20250525- +# freqtrade download-data -c ./user_data/Chan/config/ChanLun_BTC_15.json -t 1m --pairs BTC/USDT:USDT --timerange=20250405- +# freqtrade hyperopt --hyperopt-loss SharpeHyperOptLossDaily --spaces roi stoploss --strategy ChanLun_BTC_15 --strategy-path ./user_data/Chan/strategies -c ./user_data/Chan/config/ChanLun_BTC_15.json -e 200 --timerange=20250201-20250401 + +# sudo docker compose run --rm chan_btc backtesting -c ./user_data/Chan/config/ChanLun_SOL.json --strategy ChanLun_SOL --strategy-path ./user_data/Chan/strategies --timerange=20250101- +# sudo docker compose run --rm chan_btc download-data -c ./user_data/Chan/config/ChanLun_BTC_15.json --pairs BTC/USDT:USDT -t 1m --timerange 20240101- +# sudo docker compose run --rm chan_btc trade -c ./user_data/Chan/config/ChanLun_BTC_15.json --strategy ChanLun_BTC_15 --strategy-path ./user_data/Chan/strategies + +class ChanLun_BTC_15(IStrategy): + INTERFACE_VERSION: int = 3 + # Minimal ROI designed for the strategy. + # This attribute will be overridden if the config file contains "minimal_roi" + # 30m and 1h + minimal_roi = { + "0": 0.60, + "360": 0.2, + "640": 0.1, + "1200": 0 + } + # 5m and 15m + minimal_roi = { + "0": 0.1, + "60": 0.05, + "120": 0.02, + "240": 0 + } + # 5m and 15m + minimal_roi_1 = { + "0": 0.05, + "120": 0.02, + "240": 0.01, + "360": 0 + } + # 15m and 30m + minimal_roi_1 = { + "0": 0.1, + "240": 0.05, + "480": 0.03, + "600": 0 + } + minimal_roi_2 = { + "0": 0.10, + "1200": 0.05, + "2400": 0.025, + "3600": 0 + } + can_short = True + lev = 1.0 + stoploss = -0.3 + bsp_offset = 2 + trailing_stop = False + trailing_stop_positive = 0.025 + trailing_stop_positive_offset = 0.045 + trailing_only_offset_is_reached = False + + position_adjustment_enable = True + startup_candle_count = 100 + + time5 = 5 + time15 = 15 + time30 = 30 + time60 = 60 + time4h = 240 + time1d = 1440 + time5 = 1440 + last_time = datetime.now() + chan = ChanLun() + classifier = ChanLunClassifier(None) + def populate_indicators(self, dataframe: DataFrame, metadata: dict) -> DataFrame: + + # resample our dataframes + dataframe_5 = resample_to_interval(dataframe, self.get_ticker_indicator() * 5) + dataframe_15 = resample_to_interval(dataframe, self.get_ticker_indicator() * 15) + dataframe_30 = resample_to_interval(dataframe, self.get_ticker_indicator() * 30) + dataframe_60 = resample_to_interval(dataframe, self.get_ticker_indicator() * 60) + dataframe_4h = resample_to_interval(dataframe, self.get_ticker_indicator() * 240) + + #dataframe_1d = self.dp.get_pair_dataframe(pair=metadata['pair'], timeframe='1d') + #dataframe_1w = resample_to_interval(dataframe_1d, self.get_ticker_indicator() * 10080) + #dataframe_1m = resample_to_interval(dataframe_1d, self.get_ticker_indicator() * 43200) + + dataframe_1d = resample_to_interval(dataframe, self.get_ticker_indicator() * 1440) + #dataframe_1w = resample_to_interval(dataframe, self.get_ticker_indicator() * 10080) + #dataframe_1m = resample_to_interval(dataframe, self.get_ticker_indicator() * 43200) + dataframe = self.add_indicators(dataframe) + dataframe_5 = self.add_indicators(dataframe_5) + dataframe_30 = self.add_indicators(dataframe_30) + dataframe_60 = self.add_indicators(dataframe_60) + dataframe_4h = self.add_indicators(dataframe_4h) + dataframe_1d = self.add_indicators(dataframe_1d) + #self.chan.plot_dual(dataframe_5, dataframe_30) + dataframe['rsi'] = ta.RSI(dataframe, timeperiod=14) + + state_list, fx_list = self.chan.get_klc_strength_list(dataframe_15) + dataframe_15['state'] = state_list + dataframe_15['fx'] = fx_list + dataframe_15['bsp'] = self.chan.get_klc_bsp_list(dataframe_15) + klc_list = self.chan.get_klc_list(dataframe_15) + bi_list = self.chan.cal_bi_list(klc_list) + if self.last_time + timedelta(minutes=1) < datetime.now(): + print(state_list[-1], state_list[-2], state_list[-3], state_list[-4], state_list[-5]) + print(fx_list[-1], fx_list[-2], fx_list[-3], fx_list[-4], fx_list[-5]) + print(klc_list[-1].klc_fx_type, klc_list[-2].klc_fx_type, klc_list[-3].klc_fx_type, klc_list[-4].klc_fx_type, klc_list[-5].klc_fx_type) + print("-------------------------------------------------------------------------------") + self.last_time = datetime.now() + #dataframe = resampled_merge(dataframe, dataframe_5) + dataframe = resampled_merge(dataframe, dataframe_15) + #dataframe = resampled_merge(dataframe, dataframe_30) + #dataframe = resampled_merge(dataframe, dataframe_60) + #dataframe = resampled_merge(dataframe, dataframe_4h) + return dataframe + + def add_indicators(self, df): + fast = 8 + slow = 16 + period = 6 + macd = ta.MACD(df, fastperiod=fast, slowperiod=slow, signalperiod=period) + df['macd'] = macd['macd'] + df['macdsignal'] = macd['macdsignal'] + df['macdhist'] = macd['macdhist'] + df['ma5'] = ta.MA(df, timeperiod=5) + df['ma10'] = ta.MA(df, timeperiod=10) + df['ma30'] = ta.EMA(df, timeperiod=30) + df['ma250'] = ta.MA(df, timeperiod=250) + df['rsi'] = ta.RSI(df, timeperiod=14) + df['volume_ratio'] = self.cal_volume_ratio(df) + return df + def cal_volume_ratio(self, dataframe, window=10): + df = dataframe.copy() + # 计算过去N根K线的平均成交量 + df['avg_volume'] = df['volume'].rolling(window=window).mean() + # 计算量比 + df['volume_ratio'] = df['volume'] / df['avg_volume'] + # 填充缺失值(前N根K线) + df['volume_ratio'] = df['volume_ratio'].fillna(1.0) + return df['volume_ratio'] + + def custom_entry_price(self, pair: str, trade: Trade | None, current_time: datetime, proposed_rate: float, + entry_tag: str | None, side: str, **kwargs) -> float: + new_entryprice = proposed_rate + if trade: + if trade.is_short: + new_entryprice = proposed_rate - 50 + else: + new_entryprice = proposed_rate + 50 + return new_entryprice + + def custom_exit_price(self, pair: str, trade: Trade, + current_time: datetime, proposed_rate: float, + current_profit: float, exit_tag: str | None, **kwargs) -> float: + new_exitprice = proposed_rate + if trade: + if trade.is_short: + new_exitprice = proposed_rate + 50 + else: + new_exitprice = proposed_rate - 50 + return new_exitprice + + def populate_entry_trend(self, dataframe: DataFrame, metadata: dict) -> DataFrame: + state_str = 'resample_{}_state'.format(self.get_ticker_indicator()*self.time5) + fx_str = 'resample_{}_fx'.format(self.get_ticker_indicator()*self.time5) + bsp_str = 'resample_{}_bsp'.format(self.get_ticker_indicator()*self.time5) + shift = self.time5*self.bsp_offset + dataframe.loc[ + ( + #(dataframe['state'] == "-30") + #(dataframe[state_str].shift(shift) > 1.0) & + #(dataframe[fx_str].shift(shift) == -1) + (dataframe[bsp_str].shift(shift) == -1) + #(dataframe['resample_{}_state'.format(self.get_ticker_indicator()*self.time5)].shift(self.time5) == "-10") & + #(dataframe['resample_{}_state'.format(self.get_ticker_indicator()*self.time30)] == "-10") & + #(dataframe['resample_{}_state'.format(self.get_ticker_indicator()*self.time5)].shift(self.time5) == "-10") + #(qtpylib.crossed_above(dataframe['macd'], dataframe['macdsignal'])) + ), + ['enter_long', 'enter_tag']] = (1, 'long_signal_chan') + dataframe.loc[ + ( + #(dataframe['state'] == "-30") + #(dataframe[state_str].shift(shift) > 1.0) & + #(dataframe[fx_str].shift(shift) == 1) + (dataframe[bsp_str].shift(shift) == 1) + #(dataframe['resample_{}_state'.format(self.get_ticker_indicator()*self.time5)].shift(self.time5) == "-10") & + #(dataframe['resample_{}_state'.format(self.get_ticker_indicator()*self.time30)] == "-10") & + #(dataframe['resample_{}_state'.format(self.get_ticker_indicator()*self.time5)].shift(self.time5) == "-10") + #(qtpylib.crossed_above(dataframe['macd'], dataframe['macdsignal'])) + ), + ['enter_short', 'enter_tag']] = (1, 'short_signal_chan') + return dataframe + def populate_exit_trend(self, dataframe: DataFrame, metadata: dict) -> DataFrame: + state_str = 'resample_{}_state'.format(self.get_ticker_indicator()*self.time5) + fx_str = 'resample_{}_fx'.format(self.get_ticker_indicator()*self.time5) + bsp_str = 'resample_{}_bsp'.format(self.get_ticker_indicator()*self.time5) + shift = self.time5*self.bsp_offset + dataframe.loc[ + ( + #(dataframe['state']== "30") + #(dataframe[state_str].shift(shift) > 1.0) & + #(dataframe[fx_str].shift(shift) == 1) + (dataframe[bsp_str].shift(shift) == 1) + #(dataframe['resample_{}_state'.format(self.get_ticker_indicator()*self.time30)] == "10") & + #(dataframe['resample_{}_state'.format(self.get_ticker_indicator()*self.time60)] == "10") + ), + ['exit_long', 'exit_tag']] = (1, 'long_close_signal_chan') + dataframe.loc[ + ( + #(dataframe['state']== "30") + #(dataframe[state_str].shift(shift) > 1.0) & + #(dataframe[fx_str].shift(shift) == -1) + (dataframe[bsp_str].shift(shift) == -1) + #(dataframe['resample_{}_state'.format(self.get_ticker_indicator()*self.time30)] == "10") & + #(dataframe['resample_{}_state'.format(self.get_ticker_indicator()*self.time60)] == "10") + ), + ['exit_short', 'exit_tag']] = (1, 'short_close_signal_chan') + return dataframe + def leverage(self, pair: str, current_time: datetime, current_rate: float, + proposed_leverage: float, max_leverage: float, entry_tag: Optional[str], side: str, + **kwargs) -> float: + return self.lev + + def get_ticker_indicator(self): + return int(self.timeframe[:-1]) \ No newline at end of file diff --git a/web/cn_stock_data.py b/web/cn_stock_data.py index 42280cd..b76ad3e 100644 --- a/web/cn_stock_data.py +++ b/web/cn_stock_data.py @@ -77,6 +77,8 @@ class ChinaStockData: def get_popular_stocks(self): """获取热门A股股票代码列表 - 扩展版本,按行业分类""" return [ + # 包装引印刷 + {'symbol': '002836', 'name': '新宏泽', 'sector': '包装印刷'}, # 银行股 {'symbol': '600036', 'name': '招商银行', 'sector': '银行'}, {'symbol': '000001', 'name': '平安银行', 'sector': '银行'},