diff --git a/ChanKLU.py b/ChanKLU.py index fd52f6a..75044cf 100644 --- a/ChanKLU.py +++ b/ChanKLU.py @@ -52,8 +52,8 @@ class ChanKLU: self.candle_dir = Chan_K_DIR.CROSS if self.close == self.open else Chan_K_DIR.BULL if self.close > self.open else Chan_K_DIR.BEAR self.strength = 0 if self.candle_dir == Chan_K_DIR.CROSS else self.cal_klu_strength() - self.continue_div = False - self.separate_div = False + self.continue_div = 0 + self.separate_div = 0 self.near0_return = 0 self.ema52 = 0 self.ema24 = 0 @@ -655,27 +655,27 @@ class ChanKLU: if self.signal > 0: if self.macd < self.signal: if 0 < self.low - self.ema52 < 100: - self.near0_return = 6 + self.near0_return = 0 elif self.close > self.ema52 and self.low < self.ema52 and self.open > self.ema52: self.near0_return = 7 elif self.close < self.ema52 and self.open > self.ema52 and self.high > self.ema52 and self.low < self.ema52: - self.near0_return = 8 + self.near0_return = 0 elif self.close < self.ema52 and self.open < self.ema52 and self.high > self.ema52 and self.low < self.ema52: - self.near0_return = 9 + self.near0_return = 0 elif self.close > self.ema52 and self.open > self.ema52 and self.high > self.ema52 and self.low < self.ema52: - self.near0_return = 10 + self.near0_return = 0 else: if self.macd > self.signal: if 0 < self.ema52 - self.high < 100: - self.near0_return = 61 + self.near0_return = 0 elif self.close < self.ema52 and self.high > self.ema52 and self.open < self.ema52: - self.near0_return = 71 + self.near0_return = 0 elif self.close < self.ema52 and self.open > self.ema52 and self.high > self.ema52 and self.low < self.ema52: self.near0_return = 81 elif self.close > self.ema52 and self.open < self.ema52 and self.high > self.ema52 and self.low < self.ema52: - self.near0_return = 91 + self.near0_return = 0 elif self.close > self.ema52 and self.high > self.ema52 and self.open > self.ema52 and self.low < self.ema52: - self.near0_return = 101 + self.near0_return = 0 # CROSS0 仅以 Signal 穿越零轴判定 if self.pre.signal >= 0 and self.signal < 0: self.macd_state = Chan_MACD_STATE.CROSS0_DOWN diff --git a/ChanLun.py b/ChanLun.py index a5d3c77..e05b839 100644 --- a/ChanLun.py +++ b/ChanLun.py @@ -20,19 +20,17 @@ import numpy as np from ChanMACD import ChanMACD class ChanLun(): - timeframes = ["5m", "15m", "30m", "60m", "4h"] - times = { - "5m": 5, - "15m": 15, - "30m": 30, - "60m": 60, - "4h": 240 - } + time3 = 3 time5 = 5 time15 = 15 time30 = 30 time60 = 60 + time2h = 120 time4h = 240 + time6h = 360 + time8h = 480 + time12h = 720 + time1d = 1440 def create_all_data(self, dataframe, ticker_indicator): all_data = dict() all_data['1m'] = dataframe @@ -82,25 +80,75 @@ class ChanLun(): klc_list = self.get_klc_list(dataframe) bi_list= self.cal_bi_list(klc_list) def get_klu_state_list(self, dataframe): - klu_list = self.get_klu_list(dataframe) - chanmacd = ChanMACD(klu_list) - state_list = [] - for klu in klu_list: - if klu.macd > 0: - if klu.separate_div: - state_list.append("30") - elif klu.continue_div: - state_list.append("20") - else: - state_list.append("00") - else: - if klu.separate_div: - state_list.append("-30") - elif klu.continue_div: - state_list.append("-20") - else: - state_list.append("00") + dataframe3 = resample_to_interval(dataframe, self.time3) + dataframe5 = resample_to_interval(dataframe, self.time5) + dataframe15 = resample_to_interval(dataframe, self.time15) + dataframe30 = resample_to_interval(dataframe, self.time30) + dataframe60 = resample_to_interval(dataframe, self.time60) + dataframe2h = resample_to_interval(dataframe, self.time2h) + dataframe6h = resample_to_interval(dataframe, self.time6h) + dataframe8h = resample_to_interval(dataframe, self.time8h) + dataframe12h = resample_to_interval(dataframe, self.time12h) + dataframe4h = resample_to_interval(dataframe, self.time4h) + dataframe1d = resample_to_interval(dataframe, self.time1d) + dataframe = self.add_indicators(dataframe) + dataframe3 = self.add_indicators(dataframe3) + dataframe5 = self.add_indicators(dataframe5) + dataframe15 = self.add_indicators(dataframe15) + dataframe30 = self.add_indicators(dataframe30) + dataframe60 = self.add_indicators(dataframe60) + dataframe2h = self.add_indicators(dataframe2h) + dataframe6h = self.add_indicators(dataframe6h) + dataframe8h = self.add_indicators(dataframe8h) + dataframe12h = self.add_indicators(dataframe12h) + dataframe4h = self.add_indicators(dataframe4h) + dataframe1d = self.add_indicators(dataframe1d) + return state_list + def add_indicators(self, df): + fast = 12 + slow = 26 + period = 9 + macd = ta.MACD(df, fastperiod=fast, slowperiod=slow, signalperiod=period) + bb365 = ta.BBANDS(df, timeperiod=365, nbdevup=3.0, nbdevdn=3.0, matype=0) + bb120 = ta.BBANDS(df, timeperiod=120, nbdevup=3.0, nbdevdn=3.0, matype=0) + bb30 = ta.BBANDS(df, timeperiod=41, nbdevup=2.3, nbdevdn=2.3, matype=0) + bb302 = ta.BBANDS(df, timeperiod=41, nbdevup=2.0, nbdevdn=2.0, matype=0) + bb30 = ta.BBANDS(df, timeperiod=20, nbdevup=2.0, nbdevdn=2.0, matype=0) + bb302 = ta.BBANDS(df, timeperiod=20, nbdevup=2.0, nbdevdn=2.0, matype=0) + # 计算布林带中轨(移动平均线) + bb30_middle = ta.SMA(df, timeperiod=90) + + # 手动计算布林带 %B 指标 (BBP) + # %B = (Price - Lower Band) / (Upper Band - Lower Band) + bbp365 = (df['close'] - bb365['lowerband']) / (bb365['upperband'] - bb365['lowerband']) + bbp120 = (df['close'] - bb120['lowerband']) / (bb120['upperband'] - bb120['lowerband']) + bbp30 = (df['close'] - bb30['lowerband']) / (bb30['upperband'] - bb30['lowerband']) + bbp302 = (df['close'] - bb302['lowerband']) / (bb302['upperband'] - bb302['lowerband']) + df['atr'] = ta.ATR(df, timeperiod=14) + df['bbup365'] = bb365['upperband'] + df['bblow365'] = bb365['lowerband'] + df['bbp365'] = bbp365 + df['bbup120'] = bb120['upperband'] + df['bblow120'] = bb120['lowerband'] + df['bbp120'] = bbp120 + df['bbup30'] = bb30['upperband'] + df['bblow30'] = bb30['lowerband'] + df['bbmiddle30'] = bb30_middle # 添加bb30中轨 + df['bbp30'] = bbp30 + df['bbup302'] = bb302['upperband'] + df['bblow302'] = bb302['lowerband'] + df['bbp302'] = bbp302 + df['macd'] = macd['macd'] + df['macdsignal'] = macd['macdsignal'] + df['macdhist'] = macd['macdhist'] + df['ema5'] = ta.EMA(df, timeperiod=5) + df['ema10'] = ta.EMA(df, timeperiod=10) + df['ema26'] = ta.EMA(df, timeperiod=26) + df['ema52'] = ta.EMA(df, timeperiod=52) + df['rsi'] = ta.RSI(df, timeperiod=14) + df['volume_ratio'] = self.cal_volume_ratio(df) + return df def get_klc_state_list(self, dataframe): klc_list = self.get_klc_list(dataframe) bi_list= self.cal_bi_list(klc_list) diff --git a/ChanMACDUnitTF.py b/ChanMACDUnitTF.py index e0a6f10..156ebb6 100644 --- a/ChanMACDUnitTF.py +++ b/ChanMACDUnitTF.py @@ -61,9 +61,9 @@ class ChanMACDUnitTF(): self.div_peak_list.append(histset.peak_klu) #print("Unittf: ", self.start_klu.time) if histset.peak_klu.macd > 0 and histset.histset_dir == Chan_MACDHISTSET_DIR.ABOVE: - histset.peak_klu.separate_div = True + histset.peak_klu.separate_div = self.div_count elif histset.peak_klu.macd < 0 and histset.histset_dir == Chan_MACDHISTSET_DIR.UNDER: - histset.peak_klu.separate_div = True + histset.peak_klu.separate_div = self.div_count else: if histset.peak_klu: self.peak_klu = histset.peak_klu diff --git a/K线动能理论.txt b/K线动能理论.txt index 5e7a835..bc96f52 100644 --- a/K线动能理论.txt +++ b/K线动能理论.txt @@ -1,6 +1,10 @@ EMA52线的反弹比零轴的反弹弱 EMA52线和MACD白线同时归零轴同时满足的话是完美形态,最佳买卖点 +跟随策略,先调整小级别,然后依次往大级别调整,直到整个周期结束 +大级别MACD在零轴之上为多头趋势,回调踩EMA52做多,直到跳空背离,隐形,更大级别EMA52顶部归零轴平仓 +大级别MACD在零轴之下为空头趋势,上涨踩EMA52做空,直到跳空背离,隐形,更大级别EMA52底部归零轴平仓 +盘整趋势在零轴上下移动,价格在大级别EMA52之间移动,根据连续跳空背离,隐形,归零轴EMA52线开仓和平仓 diff --git a/strategies/ChanLun_BTC_30.py b/strategies/ChanLun_BTC_30.py index f540fba..3298864 100644 --- a/strategies/ChanLun_BTC_30.py +++ b/strategies/ChanLun_BTC_30.py @@ -50,7 +50,7 @@ class ChanLun_BTC_30(IStrategy): "240": 0 } # 15m and 30m - minimal_roi = { + minimal_roi_1 = { "0": 0.1, "240": 0.05, "480": 0.03, @@ -64,7 +64,7 @@ class ChanLun_BTC_30(IStrategy): } can_short = True - lev = 2.5 + lev = 2.0 stoploss = -0.3 # 设置为很大的负值,让custom_stoploss来控制 use_custom_stoploss = True # 启用自定义止损 @@ -265,7 +265,14 @@ class ChanLun_BTC_30(IStrategy): else: # 最保守的回退:5% return -0.05 - + dataframe, _ = self.dp.get_analyzed_dataframe(trade.pair, self.timeframe) + last_candle = dataframe.iloc[-1].squeeze() + ema52_str = 'resample_{}_ema52'.format(self.get_ticker_indicator()*self.time60) + ema52_val = float(last_candle.get(ema52_str, 0) or 0) + close_str = 'resample_{}_close'.format(self.get_ticker_indicator()*self.time60) + close_val = float(last_candle.get(close_str, 0) or 0) + if close_val < ema52_val: + return -0.01 if trade.is_short: stop_price = trade.open_rate + float(entry_atr) else: @@ -325,12 +332,12 @@ class ChanLun_BTC_30(IStrategy): state30 = 'resample_{}_state'.format(self.get_ticker_indicator()*shift30) dataframe.loc[ ( - (dataframe[state30].shift(shift30) == "-20") + (dataframe[state60].shift(shift60) == "-20") ), ['enter_long', 'enter_tag']] = (1, 'long_30') dataframe.loc[ ( - (dataframe[state30].shift(shift30) == "20") + (dataframe[state30].shift(shift30) == "40") ), ['enter_short', 'enter_tag']] = (1, 'short_30') return dataframe @@ -341,8 +348,8 @@ class ChanLun_BTC_30(IStrategy): state30 = 'resample_{}_state'.format(self.get_ticker_indicator()*shift30) dataframe.loc[ ( - (dataframe[state30].shift(shift30) == "20") | - (dataframe[state30].shift(shift30) == "30") + (dataframe[state60].shift(shift60) == "20") | + (dataframe[state60].shift(shift60) == "30") ), ['exit_long', 'exit_tag']] = (1, 'long_close_30') dataframe.loc[ diff --git a/web/app.py b/web/app.py index 42093d5..7ffa53c 100644 --- a/web/app.py +++ b/web/app.py @@ -51,6 +51,7 @@ TIMEFRAMES = { '1h': '1小时', '2h': '2小时', '4h': '4小时', + '6h': '6小时', '8h': '8小时', '12h': '12小时', '1d': '日线', @@ -110,12 +111,12 @@ def get_crypto_kl_data(symbol, timeframe, limit=1000, start_time=None, end_time= # 根据时间周期调整每次请求的数据量 batch_size = 1000 # 默认批次大小 if timeframe in ['1m', '3m', '5m']: - batch_size = 500 # 分钟级数据减少批次大小 + batch_size = 1000 # 分钟级数据减少批次大小 elif timeframe in ['15m', '30m', '1h']: batch_size = 1000 else: batch_size = 1500 # 日线及以上可以获取更多 - batch_size = 1000 # 默认批次大小 + batch_size = 1500 # 默认批次大小 # 初始化存储所有K线数据的列表 all_ohlcv = [] @@ -1247,7 +1248,7 @@ def serialize_chan_macd_data(chan_macd_data, client_tz): serialized_data['klu_list'].append({ 'time': format_time_safely(getattr(klu, 'time', None), client_tz), 'continue_div': bool(getattr(klu, 'continue_div', False)), - 'separate_div': bool(getattr(klu, 'separate_div', False)), + 'separate_div': int(getattr(klu, 'separate_div', 0)) if getattr(klu, 'separate_div', 0) is not None else 0, 'near0_return': int(getattr(klu, 'near0_return', 0)) if getattr(klu, 'near0_return', 0) is not None else 0 }) except Exception as e: diff --git a/web/templates/index.html b/web/templates/index.html index 53b4bbf..80a448e 100644 --- a/web/templates/index.html +++ b/web/templates/index.html @@ -3068,8 +3068,8 @@ if (!item || !item.time) return; const ts = Math.floor(new Date(item.time).getTime() / 1000); if (isNaN(ts)) return; - if (item.separate_div === true) { - mainMarkers.push({ time: ts, position: 'aboveBar', color: '#03a9f4', shape: 'arrowUp', text: 'SD', size: 0.6 }); + if (Number(item.separate_div) > 0) { + mainMarkers.push({ time: ts, position: 'aboveBar', color: '#03a9f4', shape: 'arrowUp', text: `SD${Number(item.separate_div)}`, size: 0.6 }); } if (item.continue_div === true) { mainMarkers.push({ time: ts, position: 'belowBar', color: '#ff9800', shape: 'arrowDown', text: 'CD', size: 0.6 }); @@ -3086,8 +3086,8 @@ if (!item || !item.time) return; const ts = Math.floor(new Date(item.time).getTime() / 1000); if (isNaN(ts)) return; - if (item.separate_div === true) { - elementMarkers.push({ time: ts, position: 'aboveBar', color: '#9c27b0', shape: 'arrowUp', text: 'SD', size: 0.6 }); + if (Number(item.separate_div) > 0) { + elementMarkers.push({ time: ts, position: 'aboveBar', color: '#9c27b0', shape: 'arrowUp', text: `SD${Number(item.separate_div)}`, size: 0.6 }); } if (item.continue_div === true) { elementMarkers.push({ time: ts, position: 'belowBar', color: '#4caf50', shape: 'arrowDown', text: 'CD', size: 0.6 }); @@ -3127,8 +3127,8 @@ if (!item || !item.time) return; const ts = Math.floor(new Date(item.time).getTime() / 1000); if (isNaN(ts)) return; - if (item.separate_div === true) { - mainMarkersAll.push({ time: ts, position: 'aboveBar', color: '#03a9f4', shape: 'arrowUp', text: 'SD', size: 0.6 }); + if (Number(item.separate_div) > 0) { + mainMarkersAll.push({ time: ts, position: 'aboveBar', color: '#03a9f4', shape: 'arrowUp', text: `SD${Number(item.separate_div)}`, size: 0.6 }); } if (item.continue_div === true) { mainMarkersAll.push({ time: ts, position: 'belowBar', color: '#ff9800', shape: 'arrowDown', text: 'CD', size: 0.6 }); @@ -3143,8 +3143,8 @@ if (!item || !item.time) return; const ts = Math.floor(new Date(item.time).getTime() / 1000); if (isNaN(ts)) return; - if (item.separate_div === true) { - elementMarkersAll.push({ time: ts, position: 'aboveBar', color: '#9c27b0', shape: 'arrowUp', text: 'SD', size: 0.6 }); + if (Number(item.separate_div) > 0) { + elementMarkersAll.push({ time: ts, position: 'aboveBar', color: '#9c27b0', shape: 'arrowUp', text: `SD${Number(item.separate_div)}`, size: 0.6 }); } if (item.continue_div === true) { elementMarkersAll.push({ time: ts, position: 'belowBar', color: '#4caf50', shape: 'arrowDown', text: 'CD', size: 0.6 });