From bfbf1781ecbb7d5b53decbf9fa2cdcb4cc6ae8eb Mon Sep 17 00:00:00 2001 From: jackyu66git Date: Sat, 30 Aug 2025 01:07:03 +0800 Subject: [PATCH] coding macd --- ChanEnum.py | 22 ++++++----- ChanKLC.py | 4 +- ChanKLU.py | 8 ++-- ChanMACD.py | 4 +- ChanMACDHistSet.py | 52 ++++++++++++++++++------ ChanMACDUnitTF.py | 36 +++++++---------- strategies/ChanLun_BTC_30.py | 77 +++++++++++++++++------------------- web/nginx/logs/error.log | 75 +++++++++++++++++++++++++++++++++++ 8 files changed, 185 insertions(+), 93 deletions(-) diff --git a/ChanEnum.py b/ChanEnum.py index 8a542a7..34a757c 100644 --- a/ChanEnum.py +++ b/ChanEnum.py @@ -78,21 +78,25 @@ class Chan_KLC_FX(Enum): class Chan_MACD_STATE(Enum): """MACD状态枚举 - 包含所有可能的状态""" # 穿越状态 - CROSS0_UP = auto() # 向上穿越零轴 - CROSS0_DOWN = auto() # 向下穿越零轴 + CROSS0_UP = auto() # 穿零轴后快速向上,能量柱呈现一根比一根长的排列方式 + CROSS0_DOWN = auto() # 穿零轴后快速向下,能量柱呈现一根比一根短的排列方式 + + CROSS_OS = auto() # 穿零轴后缠绕/粘合,黄白线沿着能量柱运行,黄白线在运行的过程中没有释放出反向能量柱 + CROSS_REV = auto() # 穿零轴后倒挂,MACD黄白线在穿零轴的时候与零轴的距离比较近,同时黄白线沿着能量柱运行,在运行的过程中,能量柱衰减导致它跟黄白线之间形成夹角空位,同时黄白线产生交叉并释放反向能量柱。 # 趋势状态 NEAR0 = auto() - NEAR0_1 = auto() # 价格在EMA52附近/价格接触EMA52并马上离开,需要观察离开强度 - NEAR0_2 = auto() # MACD白线接近零轴,价格未到EMA52 - NEAR0_3 = auto() # MACD白线接近零轴和价格接触或短暂击穿EMA52,而MACD黄线不穿零轴 - NEAR0_4 = auto() # MACD黄白线接近零轴和价格在EMA24附近 + NEAR0_52 = auto() # 价格在EMA52附近/价格接触EMA52并马上离开,需要观察离开强度 + NEAR0_DIFF = auto() # MACD白线接近零轴,价格未到EMA52 + NEAR0_PERFECT = auto() # MACD白线接近零轴和价格接触或短暂击穿EMA52,而MACD黄线不穿零轴,完美形态 + NEAR0_24 = auto() # MACD黄白线接近零轴和价格在EMA24附近 # 位置状态 - HIGH = auto() # 高位:MACD黄白线离开零轴到高点,能量柱最大开始减弱 + HIGH = auto() # 高位:MACD黄白线离开能量柱到高点,能量柱最大开始减弱 HIGH_EMPTY = auto() # 高位空:MACD黄白线处于高位,能量柱衰减,与黄白线形成空间夹角 RETURN_ZERO = auto() # 归零轴:能量柱呈现一根比一根短的排列方式 - UP = auto() # 归零轴后的上涨 - DOWN = auto() # 归零轴后的下跌 + RZ_UP = auto() # 归零轴后的零轴上涨 + RZ_DOWN = auto() # 归零轴后的零轴下跌 + # 基础状态 UNKNOWN = auto() # 未知 START = auto() # 开始 diff --git a/ChanKLC.py b/ChanKLC.py index 4b402c0..1972a79 100644 --- a/ChanKLC.py +++ b/ChanKLC.py @@ -78,12 +78,12 @@ class ChanKLC(): self.klc_fx_type = Chan_KLC_FX.BOTTOM4 if self.fx == Chan_FX_TYPE.TOP: #print(self.end_time, self.fx, self.macd, self.macdhist, len(self.klus)) - if self.macdhist < 5 and self.macd > 0 and self.macd > self.pre.macd and self.macd > self.next.macd: + if self.macdhist < 5 and self.macd > 0: self.klc_fx_type = Chan_KLC_FX.TOP5 self.bb_out = True else: if self.fx == Chan_FX_TYPE.BOTTOM: - if self.macdhist > -5 and self.macd < 0 and self.macd < self.pre.macd and self.macd < self.next.macd: + if self.macdhist > -5 and self.macd < 0: self.klc_fx_type = Chan_KLC_FX.BOTTOM5 self.bb_out = True diff --git a/ChanKLU.py b/ChanKLU.py index 352744d..76bf6b3 100644 --- a/ChanKLU.py +++ b/ChanKLU.py @@ -676,12 +676,12 @@ class ChanKLU: # 6) 离开0轴开始上涨或者下跌阶段,高位之前的 if self.macd_state == Chan_MACD_STATE.UNKNOWN: if self.macd > 0 and self.pre: - if (self.pre.macd_state == Chan_MACD_STATE.NEAR0 or self.pre.macd_state == Chan_MACD_STATE.UP) and self.signal > self.pre.signal: - self.macd_state = Chan_MACD_STATE.UP + if (self.pre.macd_state == Chan_MACD_STATE.NEAR0 or self.pre.macd_state == Chan_MACD_STATE.RZ_UP) and self.signal > self.pre.signal: + self.macd_state = Chan_MACD_STATE.RZ_UP return self.macd_state elif self.macd < 0 and self.pre: - if (self.pre.macd_state == Chan_MACD_STATE.NEAR0 or self.pre.macd_state == Chan_MACD_STATE.DOWN) and self.signal < self.pre.signal: - self.macd_state = Chan_MACD_STATE.DOWN + if (self.pre.macd_state == Chan_MACD_STATE.NEAR0 or self.pre.macd_state == Chan_MACD_STATE.RZ_DOWN) and self.signal < self.pre.signal: + self.macd_state = Chan_MACD_STATE.RZ_DOWN return self.macd_state # 7) 其余情况 self.macd_state = Chan_MACD_STATE.UNKNOWN diff --git a/ChanMACD.py b/ChanMACD.py index 22cadb5..bf8f01a 100644 --- a/ChanMACD.py +++ b/ChanMACD.py @@ -81,7 +81,7 @@ class ChanMACD(): else: # 4) UnitTF 状态机:用黄线Signal的归零轴 if last_klu.macd_state == Chan_MACD_STATE.NEAR0 and last_unittf.near0_count > 1: - if klu.macd_state == Chan_MACD_STATE.UP: + if klu.macd_state == Chan_MACD_STATE.RZ_UP: last_unittf.set_end_klu(last_klu, Chan_MACDUNITTF_TYPE.NEAR0) new_dir = Chan_MACDUNITTF_DIR.ABOVE if last_unittf.unittf_dir == Chan_MACDUNITTF_DIR.UNDER else Chan_MACDUNITTF_DIR.UNDER unittf = ChanMACDUnitTF(klu.time, klu, last_unittf, new_dir, Chan_MACDUNITTF_TYPE.NEAR0) @@ -90,7 +90,7 @@ class ChanMACD(): last_unittf.set_next(unittf) last_unittf = unittf last_seg.add_unittf(unittf) - elif klu.macd_state == Chan_MACD_STATE.DOWN: + elif klu.macd_state == Chan_MACD_STATE.RZ_DOWN: last_unittf.set_end_klu(last_klu, Chan_MACDUNITTF_TYPE.NEAR0) new_dir = Chan_MACDUNITTF_DIR.UNDER if last_unittf.unittf_dir == Chan_MACDUNITTF_DIR.ABOVE else Chan_MACDUNITTF_DIR.ABOVE unittf = ChanMACDUnitTF(klu.time, klu, last_unittf, new_dir, Chan_MACDUNITTF_TYPE.NEAR0) diff --git a/ChanMACDHistSet.py b/ChanMACDHistSet.py index a7c04be..138fd2b 100644 --- a/ChanMACDHistSet.py +++ b/ChanMACDHistSet.py @@ -6,30 +6,56 @@ class ChanMACDHistSet(): self.end_time = None self.klu_list = [] self.klu_list.append(start_klu) - self.ref_klu = None self.histset_dir = dir self.next = None self.pre = pre_histset - self.high_klu = start_klu - self.low_klu = start_klu + self.peak_klu = None + self.div_klu = None + self.area = start_klu.macdhist + self.unittf_div = False def set_next(self, next_histset): self.next = next_histset + def set_unittf_div(self, unittf_div): + self.unittf_div = unittf_div def add_klu(self, klu): - self.klu_list.append(klu) klu.set_histset(self) if self.histset_dir == Chan_MACDHISTSET_DIR.ABOVE: - if klu.macdhist > self.high_klu.macdhist: - self.high_klu = klu + if self.peak_klu: + if klu.macdhist > self.peak_klu.macdhist: + self.peak_klu = klu + if klu.macdhist > self.klu_list[-1].macdhist and klu.macdhist < self.peak_klu.macdhist: + self.div_klu = klu else: - if klu.macdhist < self.low_klu.macdhist: - self.low_klu = klu + if len(self.klu_list) > 0: + pre_klu = self.klu_list[-1] + if pre_klu.macdhist < klu.macdhist: + self.peak_klu = klu else: - if klu.macdhist < self.high_klu.macdhist: - self.high_klu = klu + if self.peak_klu: + if klu.macdhist < self.peak_klu.macdhist: + self.peak_klu = klu + if klu.macdhist < self.klu_list[-1].macdhist and klu.macdhist > self.peak_klu.macdhist: + self.div_klu = klu else: - if klu.macdhist > self.low_klu.macdhist: - self.low_klu = klu + if len(self.klu_list) > 0: + pre_klu = self.klu_list[-1] + if pre_klu.macdhist > klu.macdhist: + self.peak_klu = klu + self.klu_list.append(klu) + self.area += klu.macdhist def set_end_klu(self, end_klu): self.add_klu(end_klu) self.end_klu = end_klu - self.end_time = end_klu.time \ No newline at end of file + self.end_time = end_klu.time + def find_peak_from(self, from_klu): + peak_klu = None + if from_klu in self.klu_list: + for i in range(from_klu.index + 1, len(self.klu_list)): + klu = self.klu_list[i] + if peak_klu: + if abs(peak_klu.macdhist) < abs(klu.macdhist): + peak_klu = klu + else: + if abs(self.klu_list[i-1].macdhist) < abs(klu.macdhist): + peak_klu = klu + return peak_klu \ No newline at end of file diff --git a/ChanMACDUnitTF.py b/ChanMACDUnitTF.py index 8e5da82..b9786ee 100644 --- a/ChanMACDUnitTF.py +++ b/ChanMACDUnitTF.py @@ -1,4 +1,4 @@ -from ChanEnum import Chan_MACD_STATE, Chan_MACDUNITTF_DIR +from ChanEnum import Chan_MACD_STATE, Chan_MACDUNITTF_DIR, Chan_MACDHISTSET_DIR class ChanMACDUnitTF(): @@ -16,32 +16,24 @@ class ChanMACDUnitTF(): self.start_type = start_type self.end_type = None self.near0_count = 1 - self.high_klu = start_klu - self.low_klu = start_klu + self.peak_klu = None def set_next(self, next_unittf): self.next = next_unittf def add_histset(self, histset): self.histset_list.append(histset) - histset.set_next(self) def add_klu(self, klu): - if klu: - self.klu_list.append(klu) - klu.set_unittf(self) - if self.unittf_dir == Chan_MACDUNITTF_DIR.ABOVE: - if klu.macdhist > self.high_klu.macdhist: - self.high_klu = klu - else: - if klu.macdhist < self.low_klu.macdhist: - self.low_klu = klu - else: - if klu.macdhist < self.high_klu.macdhist: - self.high_klu = klu - else: - if klu.macdhist > self.low_klu.macdhist: - self.low_klu = klu - if klu.macd_state == Chan_MACD_STATE.NEAR0 and len(self.histset_list) > 1: - #print(self.start_time, klu.time, self.near0_count) - self.near0_count += 1 + self.klu_list.append(klu) + self.cal_peak_div() + def cal_peak_div(self): + if len(self.histset_list) == 1: + self.peak_klu = self.histset_list[0].find_peak_from(self.start_klu) + else: + for histset in self.histset_list: + if histset.peak_klu and self.peak_klu: + if abs(histset.peak_klu.macdhist) > abs(self.peak_klu.macdhist): + self.peak_klu = histset.peak_klu + else: + histset.set_unittf_div(True) def set_end_klu(self, end_klu, end_type): self.add_klu(end_klu) self.end_type = end_type diff --git a/strategies/ChanLun_BTC_30.py b/strategies/ChanLun_BTC_30.py index cb63c6d..fec9f53 100644 --- a/strategies/ChanLun_BTC_30.py +++ b/strategies/ChanLun_BTC_30.py @@ -6,7 +6,6 @@ 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 @@ -21,7 +20,7 @@ logger = logging.getLogger(__name__) # freqtrade plot-dataframe --strategy ChanLun_BTC_30 --datadir user_data/data/binance -c ./user_data/ChanLun_SOL_30.json --timerange=20250309- # freqtrade trade -c ./user_data/Chan/config/ChanLun_BTC_30.json --strategy ChanLun_BTC_30 --strategy-path ./user_data/Chan/strategies -# freqtrade backtesting -c ./user_data/Chan/config/ChanLun_BTC_30.json --strategy ChanLun_BTC_30 --strategy-path ./user_data/Chan/strategies --timerange=20250712- +# freqtrade backtesting -c ./user_data/Chan/config/ChanLun_BTC_30.json --strategy ChanLun_BTC_30 --strategy-path ./user_data/Chan/strategies --timerange=20250820- # freqtrade download-data -c ./user_data/Chan/config/ChanLun_BTC_30.json -t 1m --pairs BTC/USDT:USDT --timerange=20250405- # freqtrade hyperopt --hyperopt-loss SharpeHyperOptLossDaily --spaces roi stoploss --strategy ChanLun_BTC_30 --strategy-path ./user_data/Chan/strategies -c ./user_data/Chan/config/ChanLun_BTC_30.json -e 200 --timerange=20250201-20250401 @@ -76,16 +75,16 @@ class ChanLun_BTC_30(IStrategy): # 关闭分批止盈/仓位调整 position_adjustment_enable = False startup_candle_count = 2880 - + time3 = 3 time5 = 5 time15 = 15 time30 = 30 time60 = 60 + time2h = 120 time4h = 240 - time30 = 15 + time1d = 1440 last_time = datetime.now() chan = ChanLun() - classifier = ChanLunClassifier(None) last_order = None last_trade = None @@ -97,6 +96,7 @@ class ChanLun_BTC_30(IStrategy): 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_2h = resample_to_interval(dataframe, self.get_ticker_indicator() * 120) dataframe_4h = resample_to_interval(dataframe, self.get_ticker_indicator() * 240) #dataframe_1d = self.dp.get_pair_dataframe(pair=metadata['pair'], timeframe='1d') @@ -112,11 +112,14 @@ class ChanLun_BTC_30(IStrategy): dataframe_15 = self.add_indicators(dataframe_15) dataframe_30 = self.add_indicators(dataframe_30) dataframe_60 = self.add_indicators(dataframe_60) + dataframe_2h = self.add_indicators(dataframe_2h) dataframe_4h = self.add_indicators(dataframe_4h) dataframe_1d = self.add_indicators(dataframe_1d) #self.chan.plot_dual(dataframe_5, dataframe_30) #chanpy_state = self.chanpy.get_bsp_state(dataframe_5) #dataframe_5['chanpy_state'] = chanpy_state + state_list = self.chan.get_klc_state_list(dataframe_3) + dataframe_3['state'] = state_list state_list = self.chan.get_klc_state_list(dataframe_5) dataframe_5['state'] = state_list state_list = self.chan.get_klc_state_list(dataframe_15) @@ -125,6 +128,12 @@ class ChanLun_BTC_30(IStrategy): dataframe_30['state'] = state_list state_list = self.chan.get_klc_state_list(dataframe_60) dataframe_60['state'] = state_list + state_list = self.chan.get_klc_state_list(dataframe_2h) + dataframe_2h['state'] = state_list + state_list = self.chan.get_klc_state_list(dataframe_4h) + dataframe_4h['state'] = state_list + state_list = self.chan.get_klc_state_list(dataframe_1d) + dataframe_1d['state'] = state_list #bi_list_1 = self.chan.get_bi_list(dataframe) #bi_list_5 = self.chan.get_bi_list(dataframe_5) #bi_list_15 = self.chan.get_bi_list(dataframe_15) @@ -142,9 +151,11 @@ class ChanLun_BTC_30(IStrategy): dataframe = resampled_merge(dataframe, dataframe_3) dataframe = resampled_merge(dataframe, dataframe_5) dataframe = resampled_merge(dataframe, dataframe_15) - #dataframe = resampled_merge(dataframe, dataframe_30) + dataframe = resampled_merge(dataframe, dataframe_30) dataframe = resampled_merge(dataframe, dataframe_60) - #dataframe = resampled_merge(dataframe, dataframe_4h) + dataframe = resampled_merge(dataframe, dataframe_2h) + dataframe = resampled_merge(dataframe, dataframe_4h) + dataframe = resampled_merge(dataframe, dataframe_1d) return dataframe def print_seg(self, dataframe): klc_list = self.chan.get_klc_list(dataframe) @@ -313,60 +324,44 @@ class ChanLun_BTC_30(IStrategy): logger.info(f"保存开仓时ATR值: {entry_atr}") return None def populate_entry_trend(self, dataframe: DataFrame, metadata: dict) -> DataFrame: + state_str = 'resample_{}_state'.format(self.get_ticker_indicator()*self.time15) + shift_time = self.time15 + dataframe.loc[ + ( + (dataframe[state_str].shift(shift_time) == "-20") + ), + ['enter_long', 'enter_tag']] = (1, 'long_15') + dataframe.loc[ + ( + (dataframe[state_str].shift(shift_time) == "20") + ), + ['enter_short', 'enter_tag']] = (1, 'short_15') state_str = 'resample_{}_state'.format(self.get_ticker_indicator()*self.time30) - fx_str = 'resample_{}_fx'.format(self.get_ticker_indicator()*self.time30) - #chanpy_state_str = 'resample_{}_chanpy_state'.format(self.get_ticker_indicator()*self.time5) shift_time = self.time30 dataframe.loc[ ( (dataframe[state_str].shift(shift_time) == "-20") - #(dataframe['state'] == "-30") - #(dataframe[state_str].shift(shift_time) == "-10") - #(dataframe[fx_str].shift(shift_time) == -1) - #(dataframe[chanpy_state_str].shift(shift_time+30) == 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') + ['enter_long', 'enter_tag']] = (1, 'long_30') dataframe.loc[ ( (dataframe[state_str].shift(shift_time) == "20") - #(dataframe[fx_str].shift(shift_time) == 1) - #(dataframe[chanpy_state_str].shift(shift_time+30) == -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') + ['enter_short', 'enter_tag']] = (1, 'short_30') return dataframe def populate_exit_trend(self, dataframe: DataFrame, metadata: dict) -> DataFrame: - state_str = 'resample_{}_state'.format(self.get_ticker_indicator()*self.time30) - fx_str = 'resample_{}_fx'.format(self.get_ticker_indicator()*self.time30) - #chanpy_state_str = 'resample_{}_chanpy_state'.format(self.get_ticker_indicator()*self.time5) - shift_time = self.time30 + state_str = 'resample_{}_state'.format(self.get_ticker_indicator()*self.time15) + shift_time = self.time15 dataframe.loc[ ( - #(dataframe['state']== "30") (dataframe[state_str].shift(shift_time) == "10") - #(dataframe[fx_str].shift(shift_time) == 1) - #(dataframe[chanpy_state_str].shift(shift_time+30) == -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') + ['exit_long', 'exit_tag']] = (1, 'long_close_15') dataframe.loc[ ( - #(dataframe['state']== "30") (dataframe[state_str].shift(shift_time) == "-10") - #(dataframe[fx_str].shift(shift_time) == -1) - #(dataframe[chanpy_state_str].shift(shift_time+30) == 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') + ['exit_short', 'exit_tag']] = (1, 'short_close_15') 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, diff --git a/web/nginx/logs/error.log b/web/nginx/logs/error.log index 0bd7bd2..f12fa62 100644 --- a/web/nginx/logs/error.log +++ b/web/nginx/logs/error.log @@ -73,3 +73,78 @@ 2025/06/05 02:20:20 [notice] 1#1: worker process 23 exited with code 0 2025/06/05 02:20:20 [notice] 1#1: worker process 30 exited with code 0 2025/06/05 02:20:20 [notice] 1#1: exit +2025/08/29 12:39:31 [notice] 1#1: using the "epoll" event method +2025/08/29 12:39:31 [notice] 1#1: nginx/1.27.5 +2025/08/29 12:39:31 [notice] 1#1: built by gcc 12.2.0 (Debian 12.2.0-14) +2025/08/29 12:39:31 [notice] 1#1: OS: Linux 6.10.14-linuxkit +2025/08/29 12:39:31 [notice] 1#1: getrlimit(RLIMIT_NOFILE): 1048576:1048576 +2025/08/29 12:39:31 [notice] 1#1: start worker processes +2025/08/29 12:39:31 [notice] 1#1: start worker process 20 +2025/08/29 12:39:31 [notice] 1#1: start worker process 21 +2025/08/29 12:39:31 [notice] 1#1: start worker process 22 +2025/08/29 12:39:31 [notice] 1#1: start worker process 23 +2025/08/29 12:39:31 [notice] 1#1: start worker process 24 +2025/08/29 12:39:31 [notice] 1#1: start worker process 25 +2025/08/29 12:39:31 [notice] 1#1: start worker process 26 +2025/08/29 12:39:31 [notice] 1#1: start worker process 27 +2025/08/29 12:39:31 [notice] 1#1: start worker process 28 +2025/08/29 12:39:31 [notice] 1#1: start worker process 29 +2025/08/29 12:39:31 [notice] 1#1: start worker process 30 +2025/08/29 12:39:31 [notice] 1#1: start worker process 31 +2025/08/29 12:39:35 [notice] 1#1: signal 3 (SIGQUIT) received, shutting down +2025/08/29 12:39:35 [notice] 20#20: gracefully shutting down +2025/08/29 12:39:35 [notice] 25#25: gracefully shutting down +2025/08/29 12:39:35 [notice] 22#22: gracefully shutting down +2025/08/29 12:39:35 [notice] 22#22: exiting +2025/08/29 12:39:35 [notice] 21#21: gracefully shutting down +2025/08/29 12:39:35 [notice] 21#21: exiting +2025/08/29 12:39:35 [notice] 23#23: gracefully shutting down +2025/08/29 12:39:35 [notice] 23#23: exiting +2025/08/29 12:39:35 [notice] 24#24: gracefully shutting down +2025/08/29 12:39:35 [notice] 24#24: exiting +2025/08/29 12:39:35 [notice] 20#20: exiting +2025/08/29 12:39:35 [notice] 26#26: gracefully shutting down +2025/08/29 12:39:35 [notice] 26#26: exiting +2025/08/29 12:39:35 [notice] 28#28: gracefully shutting down +2025/08/29 12:39:35 [notice] 28#28: exiting +2025/08/29 12:39:35 [notice] 27#27: gracefully shutting down +2025/08/29 12:39:35 [notice] 27#27: exiting +2025/08/29 12:39:35 [notice] 25#25: exiting +2025/08/29 12:39:35 [notice] 30#30: gracefully shutting down +2025/08/29 12:39:35 [notice] 30#30: exiting +2025/08/29 12:39:35 [notice] 29#29: gracefully shutting down +2025/08/29 12:39:35 [notice] 29#29: exiting +2025/08/29 12:39:35 [notice] 31#31: gracefully shutting down +2025/08/29 12:39:35 [notice] 31#31: exiting +2025/08/29 12:39:35 [notice] 22#22: exit +2025/08/29 12:39:35 [notice] 21#21: exit +2025/08/29 12:39:35 [notice] 23#23: exit +2025/08/29 12:39:35 [notice] 24#24: exit +2025/08/29 12:39:35 [notice] 20#20: exit +2025/08/29 12:39:35 [notice] 26#26: exit +2025/08/29 12:39:35 [notice] 28#28: exit +2025/08/29 12:39:35 [notice] 27#27: exit +2025/08/29 12:39:35 [notice] 25#25: exit +2025/08/29 12:39:35 [notice] 30#30: exit +2025/08/29 12:39:35 [notice] 29#29: exit +2025/08/29 12:39:35 [notice] 31#31: exit +2025/08/29 12:39:35 [notice] 1#1: signal 17 (SIGCHLD) received from 27 +2025/08/29 12:39:35 [notice] 1#1: worker process 21 exited with code 0 +2025/08/29 12:39:35 [notice] 1#1: worker process 26 exited with code 0 +2025/08/29 12:39:35 [notice] 1#1: worker process 27 exited with code 0 +2025/08/29 12:39:35 [notice] 1#1: signal 29 (SIGIO) received +2025/08/29 12:39:35 [notice] 1#1: signal 17 (SIGCHLD) received from 26 +2025/08/29 12:39:35 [notice] 1#1: signal 17 (SIGCHLD) received from 22 +2025/08/29 12:39:35 [notice] 1#1: worker process 20 exited with code 0 +2025/08/29 12:39:35 [notice] 1#1: worker process 22 exited with code 0 +2025/08/29 12:39:35 [notice] 1#1: worker process 23 exited with code 0 +2025/08/29 12:39:35 [notice] 1#1: worker process 24 exited with code 0 +2025/08/29 12:39:35 [notice] 1#1: worker process 25 exited with code 0 +2025/08/29 12:39:35 [notice] 1#1: worker process 28 exited with code 0 +2025/08/29 12:39:35 [notice] 1#1: worker process 29 exited with code 0 +2025/08/29 12:39:35 [notice] 1#1: signal 29 (SIGIO) received +2025/08/29 12:39:35 [notice] 1#1: signal 17 (SIGCHLD) received from 24 +2025/08/29 12:39:35 [notice] 1#1: signal 17 (SIGCHLD) received from 30 +2025/08/29 12:39:35 [notice] 1#1: worker process 30 exited with code 0 +2025/08/29 12:39:35 [notice] 1#1: worker process 31 exited with code 0 +2025/08/29 12:39:35 [notice] 1#1: exit