From 843534a03972fda308ddbb53ea4f79f30a9c2bf0 Mon Sep 17 00:00:00 2001 From: jackyu66git Date: Wed, 28 May 2025 19:45:08 +0800 Subject: [PATCH] Add klc and klu fx strength check --- .DS_Store | Bin 6148 -> 6148 bytes ChanKLC.py | 10 +- ChanKLU.py | 385 ++++++++++++++++++++++++++++ ChanLun.py | 14 +- __pycache__/ChanKLC.cpython-312.pyc | Bin 72352 -> 72824 bytes __pycache__/ChanKLU.cpython-312.pyc | Bin 3698 -> 16082 bytes __pycache__/ChanLun.cpython-312.pyc | Bin 112515 -> 112936 bytes algorithm_comparison_test.py | 223 ++++++++++++++++ realtime_fx_example.py | 220 ++++++++++++++++ strategies/ChanLun_BTC_15.py | 38 +-- web/nginx/logs/access.log | 0 web/nginx/logs/error.log | 18 ++ web/templates/index.html | 2 +- 13 files changed, 885 insertions(+), 25 deletions(-) create mode 100644 algorithm_comparison_test.py create mode 100644 realtime_fx_example.py create mode 100644 web/nginx/logs/access.log create mode 100644 web/nginx/logs/error.log diff --git a/.DS_Store b/.DS_Store index 34a17da7d692251cfd0629a5c729e5b29eadaef3..d4c435210e31ec36b03ab78ef225313466b076f4 100644 GIT binary patch delta 126 zcmZoMXfc=|#>B`mu~2NHo}wrl0|Nsi1A_nqLkUAFLotwyXDFIjxLg+`A;6HtkjRkC zkc}(}R#wW80~9MxDNfEw%FoZ)JejG4bz?&`<7Rdaeh#4Nn-!VAGf(ChvE%?61G0c= JbAZSeW&o(%8=3$B delta 216 zcmZoMXfc=|#>B)qu~2NHo}wrx0|Nsi1A_nqLn=dVPP$=ma(-^X=7WsO8S6n(Yz)N= zl|WpAEDaQg0TlV%d>5Cboctu9B*$0Hd4bO!9CHM+(bS|6R8x=vat3E^0o0bw7K|mV go7p+|Ie_j4y6rpjWPTA#4xqDvh=E~qfXEhR0GGuw@c;k- diff --git a/ChanKLC.py b/ChanKLC.py index 98e3074..74ddc33 100644 --- a/ChanKLC.py +++ b/ChanKLC.py @@ -49,7 +49,13 @@ class ChanKLC(): self.volume_ratio = self.volume_ratio / len(self.klus) self.volume = self.volume / len(self.klus) self.macdhist = self.macdhist / len(self.klus) - + def contain_klu_fx(self): + if len(self.klus) > 0: + for klu in self.klus: + klu.update_realtime_analysis() + if klu.fx_type == self.fx and klu.fx_strength > 1.8: + return True + return False def set_next(self, klc): self.next = klc def set_pre(self, klc): @@ -1221,7 +1227,7 @@ class ChanKLC(): # 获取分型后的几根K线数据 subsequent_klcs = [] temp = self.next - for i in range(5): # 检查后续5根K线 + for i in range(2): # 检查后续5根K线 if temp: subsequent_klcs.append(temp) temp = temp.next if hasattr(temp, 'next') else None diff --git a/ChanKLU.py b/ChanKLU.py index cf62525..2ceb0bc 100644 --- a/ChanKLU.py +++ b/ChanKLU.py @@ -1,3 +1,4 @@ +from ChanEnum import Chan_FX_TYPE class ChanKLU: def __init__(self, time, open, high, low, close, volume): # _time, _close, _open, _high, _low, _extra_info={} @@ -21,9 +22,375 @@ class ChanKLU: self.ma250 = 0 self.rsi = 0 self.volume_ratio = 0 + + # === 新增:实时分型相关属性 === + self.pre = None # 前一根K线 + self.next = None # 后一根K线 + self.fx_type = Chan_FX_TYPE.UNKNOWN # 分型类型:0=无分型,1=顶分型,-1=底分型 + self.fx_strength = 0 # 分型强度:0-100 + self.fx_confirmed = False # 分型是否确认 + + def set_next(self, next): + self.next = next + self.update_realtime_analysis() + #if self.fx_type != Chan_FX_TYPE.UNKNOWN and self.fx_strength > 1: + #print(self.index, self.time, self.fx_type, self.fx_confirmed, self.fx_strength) + def set_pre(self, pre): + self.pre = pre + def detect_realtime_fx(self): + """ + 实时检测K线分型(不等待KLC确认) + 基于原始K线的即时分型识别 + """ + if not self.pre or not self.next: + self.fx_type = Chan_FX_TYPE.UNKNOWN + return False + + # 顶分型检测 + if (self.high > self.pre.high and + self.high > self.next.high): + self.fx_type = Chan_FX_TYPE.TOP + self.fx_confirmed = True + return True + + # 底分型检测 + elif (self.low < self.pre.low and + self.low < self.next.low): + self.fx_type = Chan_FX_TYPE.BOTTOM + self.fx_confirmed = True + return True + + self.fx_type = Chan_FX_TYPE.UNKNOWN + self.fx_confirmed = False + return False + + def calculate_realtime_fx_strength(self): + """ + 用self.pre和self.next实现分型强弱判断(与KLC中cal_fx_strength一致) + + 核心缠论原理: + - 强分型:出现在笔的末端,能够终结当前笔,标志着趋势转折 + - 弱分型:出现在笔的中间,是中继性质,笔还会继续延伸 + + 返回值: + 3: 极强分型(笔终结+强确认) + 2: 强分型(笔终结) + 1: 偏强分型(可能终结笔) + 0: 中性分型 + -1: 偏弱分型(中继特征明显) + -2: 弱分型(明显中继) + -3: 极弱分型(无效分型) + """ + # 检查是否为分型,且有前后K线数据 + if self.fx_type == Chan_FX_TYPE.UNKNOWN: + return 0 + if not self.pre or not self.next: + return 100 + # === 核心判断:分型在笔中的位置 === + + # 1. 检查这个分型是否能够终结当前笔 + is_bi_end = self._check_if_bi_ending_fx() + + # 2. 检查分型的后续走势确认 + post_fx_confirmation = self._check_post_fx_confirmation() + + # 3. 检查分型的标准性和强度 + fx_quality = self._check_fx_quality() + + # === 综合评分 === + base_score = 0 + + # 笔位置是最重要的判断标准 + if is_bi_end == 2: # 强烈确认笔终结 + base_score = 2 + elif is_bi_end == 1: # 可能笔终结 + base_score = 1 + elif is_bi_end == -1: # 明显中继 + base_score = -2 + elif is_bi_end == -2: # 强烈中继特征 + base_score = -3 + else: # 不确定 + base_score = 0 + + # 后续确认调整 + base_score += post_fx_confirmation + + # 分型质量调整 + base_score += fx_quality + + # 限制在-3到3范围内 + final_score = max(-3, min(3, base_score)) + self.fx_strength = final_score + # 转换为0-100分制以保持接口一致性 + #self.fx_strength = int((final_score + 3) * 100 / 6) # -3到3映射到0-100 + if final_score > 1.8: + print(self.time, final_score, is_bi_end, post_fx_confirmation, fx_quality) + #print(self.time, final_score, is_bi_end, post_fx_confirmation, fx_quality) + return self.fx_strength + + def _check_if_bi_ending_fx(self): + """ + 检查分型是否为笔终结分型 + 返回值: + 2: 强烈确认笔终结 + 1: 可能笔终结 + 0: 不确定 + -1: 明显中继 + -2: 强烈中继特征 + """ + # 检查是否有足够的后续数据来判断 + if not self.next or not hasattr(self.next, 'next'): + return 0 + + # 获取分型后的几根K线数据 + subsequent_klus = [] + temp = self.next + for i in range(2): # 检查后续2根K线 + if temp: + subsequent_klus.append(temp) + temp = temp.next if hasattr(temp, 'next') else None + else: + break + + if len(subsequent_klus) < 2: + return 0 + + if self.fx_type == Chan_FX_TYPE.TOP: + return self._check_top_bi_ending(subsequent_klus) + else: # BOTTOM + return self._check_bottom_bi_ending(subsequent_klus) + + def _check_top_bi_ending(self, subsequent_klus): + """检查顶分型是否为笔终结""" + # 强烈笔终结特征: + # 1. 后续K线持续下跌,且跌破关键位置 + # 2. 没有新的更高的高点出现 + + broken_key_levels = 0 + new_highs = 0 + downward_trend = 0 + + # 检查关键价位突破 + first_low = self.pre.low + middle_low = self.low + key_support = min(first_low, middle_low) + + for i, klu in enumerate(subsequent_klus): + # 检查是否跌破关键支撑 + if klu.low < key_support: + broken_key_levels += 1 + + # 检查是否出现新高 + if klu.high > self.high: + new_highs += 1 + + # 检查下跌趋势 + if i > 0 and klu.close < subsequent_klus[i-1].close: + downward_trend += 1 + + # 强烈笔终结:跌破关键位且无新高 + if broken_key_levels >= 1 and new_highs == 0 and downward_trend >= 2: + return 2 + + # 可能笔终结:部分条件满足 + if (broken_key_levels >= 1 and new_highs <= 1) or (new_highs == 0 and downward_trend >= 3): + return 1 + + # 明显中继:出现新高且未跌破关键位 + if new_highs >= 2 and broken_key_levels == 0: + return -2 + + # 中继倾向:出现新高 + if new_highs >= 1: + return -1 + + return 0 + + def _check_bottom_bi_ending(self, subsequent_klus): + """检查底分型是否为笔终结""" + # 强烈笔终结特征: + # 1. 后续K线持续上涨,且突破关键位置 + # 2. 没有新的更低的低点出现 + + broken_key_levels = 0 + new_lows = 0 + upward_trend = 0 + + # 检查关键价位突破 + first_high = self.pre.high + middle_high = self.high + key_resistance = max(first_high, middle_high) + + for i, klu in enumerate(subsequent_klus): + # 检查是否突破关键阻力 + if klu.high > key_resistance: + broken_key_levels += 1 + + # 检查是否出现新低 + if klu.low < self.low: + new_lows += 1 + + # 检查上涨趋势 + if i > 0 and klu.close > subsequent_klus[i-1].close: + upward_trend += 1 + + # 强烈笔终结:突破关键位且无新低 + if broken_key_levels >= 1 and new_lows == 0 and upward_trend >= 2: + return 2 + + # 可能笔终结:部分条件满足 + if (broken_key_levels >= 1 and new_lows <= 1) or (new_lows == 0 and upward_trend >= 3): + return 1 + + # 明显中继:出现新低且未突破关键位 + if new_lows >= 2 and broken_key_levels == 0: + return -2 + + # 中继倾向:出现新低 + if new_lows >= 1: + return -1 + + return 0 + + def _check_post_fx_confirmation(self): + """ + 检查分型后的走势确认 + 返回值:-1到1的调整分数 + """ + if not self.next: + return 0 + + score = 0 + + # 检查第三根K线的确认 + third_klu = self.next + + if self.fx_type == Chan_FX_TYPE.TOP: + # 顶分型:第三根K线应该走弱 + middle_price = (self.high + self.low) / 2 + + if third_klu.close < middle_price: + score += 0.5 + if third_klu.low < self.pre.low: # 跌破第一根K线低点 + score += 0.5 + if third_klu.close < third_klu.open and abs(third_klu.close - third_klu.open) > abs(self.close - self.open) * 0.5: + score += 0.3 # 明显阴线 + + else: # BOTTOM + # 底分型:第三根K线应该走强 + middle_price = (self.high + self.low) / 2 + + if third_klu.close > middle_price: + score += 0.5 + if third_klu.high > self.pre.high: # 突破第一根K线高点 + score += 0.5 + if third_klu.close > third_klu.open and abs(third_klu.close - third_klu.open) > abs(self.close - self.open) * 0.5: + score += 0.3 # 明显阳线 + + return min(1, max(-1, score)) + + def _check_fx_quality(self): + """ + 检查分型本身的质量 + 返回值:-1到1的调整分数 + """ + score = 0 + + # 检查分型的标准性 + if self.fx_type == Chan_FX_TYPE.TOP: + # 高点突出程度 + high_diff1 = (self.high - self.pre.high) / self.high if self.high > 0 else 0 + high_diff2 = (self.high - self.next.high) / self.high if self.high > 0 else 0 + min_diff = min(high_diff1, high_diff2) + + if min_diff > 0.03: # 非常突出 + score += 0.5 + elif min_diff > 0.01: # 比较突出 + score += 0.2 + elif min_diff < 0.003: # 不够突出 + score -= 0.5 + + else: # BOTTOM + # 低点突出程度 + low_diff1 = (self.pre.low - self.low) / self.pre.low if self.pre.low > 0 else 0 + low_diff2 = (self.next.low - self.low) / self.next.low if self.next.low > 0 else 0 + min_diff = min(low_diff1, low_diff2) + + if min_diff > 0.03: # 非常突出 + score += 0.5 + elif min_diff > 0.01: # 比较突出 + score += 0.2 + elif min_diff < 0.003: # 不够突出 + score -= 0.5 + + # 检查量价配合 + avg_volume = self._get_avg_volume(lookback=5) + if avg_volume > 0: + volume_ratio = self.volume / avg_volume + if volume_ratio > 1.5: + score += 0.3 + elif volume_ratio < 0.7: + score -= 0.2 + + return min(1, max(-1, score)) + + def _get_avg_volume(self, lookback=5): + """获取前N根K线平均成交量""" + volumes = [] + temp = self.pre + + for i in range(lookback): + if temp: + volumes.append(temp.volume) + temp = temp.pre if hasattr(temp, 'pre') else None + else: + break + + return sum(volumes) / len(volumes) if volumes else self.volume + + def get_fx_signal(self): + """ + 获取分型交易信号 + 返回: (信号类型, 强度, 建议) + """ + if not self.fx_confirmed: + return ("无信号", 0, "等待分型确认") + + strength_level = "弱" + if self.fx_strength >= 80: + strength_level = "极强" + elif self.fx_strength >= 65: + strength_level = "强" + elif self.fx_strength >= 50: + strength_level = "中等" + + if self.fx_type == Chan_FX_TYPE.TOP: + signal_type = f"{strength_level}顶分型" + if self.fx_strength >= 65: + suggestion = "考虑减仓或止盈" + else: + suggestion = "谨慎观望" + else: + signal_type = f"{strength_level}底分型" + if self.fx_strength >= 65: + suggestion = "考虑建仓或加仓" + else: + suggestion = "谨慎观望" + + return (signal_type, self.fx_strength, suggestion) + + def update_realtime_analysis(self): + """ + 更新实时分析(在每根K线完成时调用) + """ + self.detect_realtime_fx() + if self.fx_confirmed: + self.calculate_realtime_fx_strength() + def set_idx(self, idx): self.idx = idx self.index = idx + def set_indicators(self, item): self.macd = float(item['macd']) if 'macd' in item and item['macd'] else 0 self.signal = float(item['macdsignal']) if 'macdsignal' in item and item['macdsignal'] else 0 @@ -39,6 +406,10 @@ class ChanKLU: self.rsi = float(item['rsi']) if 'rsi' in item and item['rsi'] else 0 self.volume_ratio = float(item['volume_ratio']) if 'volume_ratio' in item and item['volume_ratio'] else 0 + + # 设置指标后更新实时分析 + self.update_realtime_analysis() + def get_feature_data(self): features = dict() features['klu_close'] = self.close @@ -58,4 +429,18 @@ class ChanKLU: features['klu_ma250'] = self.ma250 features['klu_rsi'] = self.rsi features['klu_volume_ratio'] = self.volume_ratio + + # === 新增:实时分型特征 === + # 将枚举转换为数值:UNKNOWN=0, TOP=1, BOTTOM=-1 + if self.fx_type == Chan_FX_TYPE.TOP: + fx_type_value = 1 + elif self.fx_type == Chan_FX_TYPE.BOTTOM: + fx_type_value = -1 + else: + fx_type_value = 0 + + features['klu_fx_type'] = fx_type_value + features['klu_fx_strength'] = self.fx_strength + features['klu_fx_confirmed'] = 1 if self.fx_confirmed else 0 + return features \ No newline at end of file diff --git a/ChanLun.py b/ChanLun.py index 838f985..421b923 100644 --- a/ChanLun.py +++ b/ChanLun.py @@ -67,12 +67,12 @@ class ChanLun(): else: print(bi.start_klc.end_time, bi.dir, bi.is_sure) def check_fx(self, klc): - if klc.pre and klc.next: + if klc.pre and klc.next and klc.next.end_klu: if klc.high > klc.pre.high and klc.high > klc.next.high: klc.set_fx(Chan_FX_TYPE.TOP) #print(klc.start_time, klc.end_time,klc.next.start_time, klc.next.end_time,klc.fx, "TOP") return Chan_FX_TYPE.TOP - if klc.pre and klc.next: + if klc.pre and klc.next and klc.next.end_klu: if klc.low < klc.pre.low and klc.low < klc.next.low: klc.set_fx(Chan_FX_TYPE.BOTTOM) #print(klc.start_time, klc.end_time,klc.next.start_time, klc.next.end_time,klc.fx, "BOTTOM") @@ -149,9 +149,9 @@ class ChanLun(): klc = klc_list[klc_index] if klc.end_klu and klc.end_klu.idx == index: klc_index += 1 - if klc.klc_fx_type == Chan_KLC_FX.TOP1 or klc.klc_fx_type == Chan_KLC_FX.TOP2: + if (klc.klc_fx_type == Chan_KLC_FX.TOP1 or klc.klc_fx_type == Chan_KLC_FX.TOP2) and klc.contain_klu_fx(): fx_list.append(1) - elif klc.klc_fx_type == Chan_KLC_FX.BOTTOM1 or klc.klc_fx_type == Chan_KLC_FX.BOTTOM2: + elif (klc.klc_fx_type == Chan_KLC_FX.BOTTOM1 or klc.klc_fx_type == Chan_KLC_FX.BOTTOM2) and klc.contain_klu_fx(): fx_list.append(-1) else: fx_list.append(0) @@ -235,6 +235,7 @@ class ChanLun(): def get_kl_data(self, dataframe:DataFrame): fields = "time,open,high,low,close,volume" klu_list = [] + last_klu = None for i in range(0, len(dataframe)): item = dataframe.iloc[i] date = item['date'] @@ -258,8 +259,13 @@ class ChanLun(): klu = ChanKLU(time_str, o, h, l, c, v) klu.set_idx(i) klu_list.append(klu) + if last_klu: + klu.set_pre(last_klu) + last_klu.set_next(klu) + last_klu.detect_realtime_fx() if 'macd' in item: klu.set_indicators(item) + last_klu = klu return klu_list def cal_volume_ratio(self, dataframe, window=10): df = dataframe.copy() diff --git a/__pycache__/ChanKLC.cpython-312.pyc b/__pycache__/ChanKLC.cpython-312.pyc index 5e5499430d27872b7e2136fd92367117e99400e2..e8d3dda6d7074f831642098e33fb06f9b864595c 100644 GIT binary patch delta 2783 zcmaKueQ;FO6~OoGS9Y@>Y{+J_8z5{#K6n9<^5HZD5;BpLHcYA^;~-$yb>EU*v)M3r zH-!yJ*3KkB#MWl6^-;w_wH*jLWtcY_rScIv%^xUEl-9r&P*KT5i%ltIG*hAH+zlZz zws&TKd*8k1o^$Rw=ic}Bzo!4{4ZZzsn=Mm=4gP*#=!u1W_Df*zyZVT|TeHzFTY@vT zXk}}+o^OD*rCuB>lIP0>-LSD(mgGYGFOlcrXsKK#7va-dr2z zIX`Recm^&QH)$L>Y=NS)frsxmy7*Vt)lk99Z9j%pe3wl^svA?_2|wU zS_4~w8u_uO7;NT?ch7+r)%x8-ONx%+D2ou5BF1iE8@oyP2Elr9Ov>c9F68ls<7sGT)duAaA^ zDnoalKUL?MMQSS$F+I+Bv59Q64u130JUFHn{^_{3K=?@huyztpAh5vlXkaG6%wfn2 zJ9**ILq^iYDtXtC8+y1hQ~(#%H;3lxs{fCUEYczNw~H1Fm2+`S%4DT9wUb7l;-1rM zrfcuzou`YyqP}!`xnaG~{xS)FO`v$D-Lx_f^{Kv@fv}z+EKb=;?qZml$^rjU)Z)7Dj@JB~xLl^HH$;X=7JF>xGOjnIMap75j-Rd)!BD&%` zd!lof4qzSjVyI6una~5=d$|;js%tMF(Qo=4iM>GEd=W85sc*s`w(%-akxkG4n%Wrd z3kRj|c3;HlP8Eoan0Vu8DJZ;Sv>Fcb4g>R|7<&$Zg|UyEzbmU^{;t$^lN@@MNfj%qsevE9`XKak__7FY zs3l*<4ap=a{uv=f5S1>HEy{|Dsdm!4{u7O?gk(fm%q&3tyWF7CWx}q=h7hMvNg0!w z6|N&vrWXHl-(z9!xxNAp@y6>C6suj={f1@2uJAyFVTbb%Klr%)*~u`7;v=U};WX1c zdm;d@@NEXeUac#gAo>`AXo$bs2st!lRw%NWUV+9 z%fRJy6=6%?Mz1Pg=%5Ue11>%MLjSNZc_-20&ng{V}e3>W58>MF<5>_ks+Nqq3DO-QWT?0Wtyk^lu8Sfz?p%d1pxL8Eq^NR&8}C(%M}`h>*E7?pfFU0-7_ zl^Krt9t-+eI2K|pG#@5Vp@=K5-@x`MI~~vt4FlRNFoE#~?lZPj8FaznffF;~kJ@R6 zahk2RZ`=9}KBjH8MI!&h7W*3tNse@MF3|dmgiC}E2_F$YCR`>o5I%uo)SMIY$9$32 zXcJ;vkojUEzudZweMS;j5Gl9f&%zijalsrIQyy?ZGvp7v<$_vpidu#A8HBNF42rZs zak;TV)+p6(C`eXN#Ht81$!Z9-1n~-3OFjA|VD*Ft3G~iq4->?Z-b_7u&$13e522UP zN$4WHPB=g~Na!aVBD?~H$XBU*lpwCEZ0b1)pA*KFn{LR5kCZtcC^P064LR5B%32Sc zgnGrC2g~mLH%g_<^p3A-#7{OxAE@jGEnPr8`XZY!t!!zP+akeAHlalFpm@fP*|7MU IhA5=|3(A4W00000 delta 2472 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 delta 749 zcmZ`%O=}ZD7=CB6o5XzVYHHfuG@C@49!85=F@`p2@g{i4LGjW{3E4ygiPhQk5Qx}A zkOh?*=HS7r7tvd;{sS+f2yqYM)srPT2qNgbyMdzMz&!hY&oj^LhX>QF_AZ%>fn1rd z)}2$ak!F+(={*Mu*T9A$*yJ_EJire0DGN0MCqOcHX2sE9Rq3lNJo=s|*|AP6lIR1Y zwQpF#PKy`x@d}H0iqq`0ctpnd2?RWv#}T3k3Gpaohw(h;P#lF0#Tz9jo~;*)jMNzA zNvzQ%NIQcz1{^Fe6!^06)-9jBb5ceQV6w32Do>)5wBagGW89L##{4SM1W@D-$vt~H zUb|$|xH%wp!ju6n>0Zv12e6CG{M84W{_V45jq5VURS90KTW{D-`}W;>N9Tqxp3?3$yT|u)OXuRta){HGx*veJdZjS_QYw?w z$86bEK1I#<_wM^&Zi$O!lNjUcWrM2ZCa{#)*nGRfQl4VB8@)j6e;y!#95r#HvYDC7 z>qT45dT*aMonsE4D;FYXk;88gdi7UwqA$6+uWgM*#Bgi%VHW4)5sC;K5?+B{!I3;i z$$h||$3Y2(Fr{fVH(jHuISp$nC$rpwY6Lx4USlL^+rnOaNPpDNXtld%?{}Kpd|Sr+ Uc{&a}3K2qn&?+&eAi?kW17gjMPyhe` diff --git a/__pycache__/ChanLun.cpython-312.pyc b/__pycache__/ChanLun.cpython-312.pyc index 3a91489ab54e546c5c9d75173c1b75aa3673cb50..0ff7fa400416cb803d2284adbab0e9b0e143c48b 100644 GIT binary patch delta 6458 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 delta 5947 zcmaJ_3tW`dwx2aG28NdoARq`Rj|N2$GzBDlVwsr_(u0~bz&MN!4DA^d2mw433HMNe9)8ka$Q{A=phr;!q-<{vjKWneG_S$Q& zwf5Tk^Q~```1gHN z7|-ZGfCp``@YnkrbhCPR2Q9aG{(NwgMTEwHSyV)Vg~x}PMQJ3&a;+sw*T<9?VS#YI z%o4%VUqxFMf01c{F#gURhC!E33pa(4u|d*KzZi(s&(?TNe8y`rVY4+X3!S$5E4nIX zSM-(k8ZUn)%wgGs+$K%8!L(xT^0{pm5xFLko5N@MoZ$`thp#JQtjXv>9p>ehxRL&f zpS`lIc!{f;Pm5aywfsojP+01_8JDNiYIM9R|9-w~NH*l~-9r{=*%jU|afJC~y`fB7 z7o^cN745s8mlt zv;|spro{urj13Si#=Qxq0MN8SOH{pnFFLVf0&#GOZkt<&Dw0>!Q+zn#*~L(IlqMPqa0 zhM{MoQ@f&5Px+<3Rl3u?*?nZ-nej8b#?L%8d}g;HaK*yrg>BL35ZOa-yGbf|59jCHa2#N?6 z5Y!Sh5_Awa5Ui%&+$(yg%U#O$k__urqxgC3-ePB24Wqy*!E#ht<}9^&-OOVRVOG*i zC7E%N)9xzsunjZ-r=mAHz4i*$&PV5_8A6a5&%0{E_>;NGL(bs{Pn>yJQ*w7uNYg$4 zVGeEUw|3%se?I-RdC0e#NL?)dIQP|id$-GEt;8*e{fQb*i?z~mlCrOK`}9pv)JYIc-foW zI(doT0IVO6N1hhndy{j+v3~zKZ+wPgqOeiFQ38mf{!!voLyN6QhL#nRRRh6%UN(OU zJnGvv9{{%V9SfphnD5wv6Ol%8mo@u-Up-h0t^D0(DQ3#4qN{LLvLb$S*`MG97d6$O z_XX8vM@!DS&IddrdOj;J>X&fCW5( zM`0_-CBHl7ttN2bm)vD{WUt-5k}sm+Dkk#Ts%w}X#N2sB_E3VM1Czy5Yas0kTr z5&99efQPk@4ZTLo-5`*MGL}Eq9*5g%YA*=iPh7fV*a3or1pD|G?Q!N{njbpaqxfAg`9wm@?;O_g2G$%78Zn^)0T?Iheb%EBK!901SpIfu*PB0qHa zL73(+AgL)k>qzLy}KmXraSM8lB;eQm8c8f$!xm>GP_(X{YPQqLk7d1oRm z5Xtq&MnSPJ=hz|baG6f>jZUHbF^U|EKyW+}wqweLx~KE4MmCC=tvuy;j&V1tu@T&H zJOy_0)yETn`F0=wNINzMRZw$`)Fd6b#atT7;EpA^pL$vgNKQwV(apz3^UhE1fnEIk zCxhV`pYG%($T&*;?F2gsb|IkgJ4am>Ka*ypKYMsrL>M2|l?Lniysqydmp^eFRHwz8r!7wAGXbVU5=`qxQI;i?4=9nSzZqF*k3uYK+uA-?AlaYGynHdV<@y^sh7;75Pj67>U zCfA=pa@ojQMMD@^qh;5XF_R(aUHYJSFANePSX>H&TqqX_;V=+3b>@Y`CO>Eu7h)jg zo)x&H`ZV^~9cop{{wRCqbL5X~rs*Oh7KVl1wYP)96$?|LpXiK*5k|FnitDl9fP8WXy?Fcj-GeJ*4g)zAvZT$ncPLv&Nod+~wBlF8%V z!tL`4B!QNkF%b1AP0``Y4>NDCa@k7l>=hEWh~MVIVxyO6r*Quctk5i8{vC8;LS`0$ z#dv~d?-7fOAZIuQLeVpODLxG4#*3v7^2p?oEP%$v*v0KNEMJ@|f|R&d$?|Ii4zje7 zsMiVpfp<<1XXX&(-yw{BzutTu>J6(^%x&e)wNb!#d1?*3Vj$2PI zKIrA;`B>}mG)117H&KF&IASytY(|Fq)Y+nnrMe*IuC6Q_R%I*8%NU-; z-bI09k^Fs?&rZ-xgPM_2x0{uD6k|=T%vTp#kZ0jjao-CtY{q_KG}BJsBkFyGY9kIW z?%n>?Yk!ajSG~;sOyUm+juE^}aGc;{g1;c#f`x3l*#82=W~(Pr9?EVKpGJ#6P?H{U zdG~=!t?Mo?-^UK1K=Lw?cX8h?;vE#BD`9ZPJGVtychvDwmx)nlqX_OL7)>ySAX}8I zgrvrkXkYq%>6wm;2i7TuD(1vm&mt9hK1UKo{@iW~!*4U#UgQ>5Q**ww|D`KWwOrl2 zOM%q8`T_eKwPYh;XNWpWpb&h45W&t7`JV(A2>wd&Um~Uj1_j7~XkE)#x0uudQybN` zMhfIlbqFr})rKvXcC>u;T$>ttd}Q`2$yO+PiBy*fk`Yt%)7_PJ)a~6RX)w7nnxFKh%nJrUFjA@HS$ZG}kVY?^RhV&%h`XUSpC$km2FL>Yt-KU8_m^Fk$`w-6_loEiN^mt(=ffXQHDG4 z$m6g0OS-rcFZB~*%4&#H?}Tttju4Nph6xtgLyhHjr=#4<29O|9e6Si45@quK-)_mq z3UeDwxBMMFstdf+4qa~V#!gooOpQ*hrESywRnU8Zsizbd>N(=nr`WUN{1%9UB5`XA zJ|%KQ+E)CtKs>e;28XNn0%@HkkQo?>AD@w8=TU(52;L&tO5h_nMDS;VqXhVApkbdA zd_h1LGW(L?DuKMD!^l%F4e9G${k$DRyqz?puT3_dhPf~maW0YanqNVZ5*jjsT7m|G zrwQaY#~Pwm62#E%>7rwlUiOU$?!fyHGt&Vrfic?tuSb2S!9U;SbbinQd0<>j#{VY# z_d*<87S_Em6221)_d-U-U^Jo_um-DYSt1#XBGp==Mi51J<1N$J3U^tx%YGkQA>Q2! Q^Ftm@f~apbgi`B&04at{#{d8T diff --git a/algorithm_comparison_test.py b/algorithm_comparison_test.py new file mode 100644 index 0000000..43aca62 --- /dev/null +++ b/algorithm_comparison_test.py @@ -0,0 +1,223 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- +""" +KLU与KLC分型强度算法一致性测试 +验证两种算法在相同数据下是否产生一致的结果 +""" + +from ChanKLU import ChanKLU +from ChanKLC import ChanKLC +from ChanEnum import Chan_FX_TYPE, Chan_KLINE_DIR +import pandas as pd +from datetime import datetime, timedelta + +def create_test_data(): + """创建测试用的K线数据""" + test_cases = [ + # 测试用例1:标准顶分型 + { + 'name': '标准顶分型', + 'data': [ + {'open': 100, 'high': 102, 'low': 99, 'close': 101, 'volume': 1000}, # K1 + {'open': 101, 'high': 105, 'low': 100, 'close': 103, 'volume': 1500}, # K2 (顶分型中心) + {'open': 103, 'high': 104, 'low': 98, 'close': 99, 'volume': 1200}, # K3 + ] + }, + # 测试用例2:标准底分型 + { + 'name': '标准底分型', + 'data': [ + {'open': 100, 'high': 102, 'low': 99, 'close': 101, 'volume': 1000}, # K1 + {'open': 101, 'high': 103, 'low': 95, 'close': 97, 'volume': 1500}, # K2 (底分型中心) + {'open': 97, 'high': 104, 'low': 96, 'close': 102, 'volume': 1200}, # K3 + ] + }, + # 测试用例3:强势顶分型(放量+下影线) + { + 'name': '强势顶分型', + 'data': [ + {'open': 100, 'high': 102, 'low': 99, 'close': 101, 'volume': 1000}, # K1 + {'open': 101, 'high': 108, 'low': 100, 'close': 102, 'volume': 2500}, # K2 (强顶分型) + {'open': 102, 'high': 103, 'low': 95, 'close': 96, 'volume': 1800}, # K3 (大阴线确认) + ] + } + ] + return test_cases + +def setup_klu_chain(data_list): + """设置KLU链""" + klus = [] + base_time = datetime.now() + + for i, data in enumerate(data_list): + time_str = (base_time + timedelta(minutes=i)).strftime("%Y-%m-%d %H:%M:%S") + klu = ChanKLU(time_str, data['open'], data['high'], data['low'], data['close'], data['volume']) + klu.set_idx(i) + + # 设置基础技术指标 + indicators = { + 'ma5': data['close'] + (i-1) * 0.1, + 'ma10': data['close'] + (i-1) * 0.05, + 'rsi': 50 + (i % 3 - 1) * 15, + 'macd': (i % 3 - 1) * 0.01, + 'macdhist': (i % 2) * 0.005, + 'volume_ratio': 1.0 + (i % 2) * 0.3 + } + klu.set_indicators(indicators) + klus.append(klu) + + # 建立前后关系 + for i in range(len(klus)): + if i > 0: + klus[i].set_pre(klus[i-1]) + if i < len(klus) - 1: + klus[i].set_next(klus[i+1]) + + return klus + +def setup_klc_chain(data_list): + """设置KLC链(基于KLU)""" + klus = setup_klu_chain(data_list) + klcs = [] + + # 为简化测试,假设每个KLU对应一个KLC(无包含关系处理) + for i, klu in enumerate(klus): + klc = ChanKLC(klu, i, Chan_KLINE_DIR.UP) + klc.set_end_klu(klu) + klcs.append(klc) + + # 建立前后关系 + for i in range(len(klcs)): + if i > 0: + klcs[i].set_pre(klcs[i-1]) + if i < len(klcs) - 1: + klcs[i].set_next(klcs[i+1]) + + # 设置分型类型 + if len(klcs) >= 3: + middle_klc = klcs[1] + if (middle_klc.high > klcs[0].high and middle_klc.high > klcs[2].high): + middle_klc.set_fx(Chan_FX_TYPE.TOP) + elif (middle_klc.low < klcs[0].low and middle_klc.low < klcs[2].low): + middle_klc.set_fx(Chan_FX_TYPE.BOTTOM) + + return klcs + +def compare_algorithms(test_cases): + """对比KLU和KLC算法""" + + print("=" * 80) + print("KLU与KLC分型强度算法一致性测试") + print("=" * 80) + + for case in test_cases: + print(f"\n🔍 测试用例: {case['name']}") + print("-" * 50) + + # 准备数据 + klus = setup_klu_chain(case['data']) + klcs = setup_klc_chain(case['data']) + + if len(klus) >= 3 and len(klcs) >= 3: + middle_klu = klus[1] + middle_klc = klcs[1] + + # KLU分析 + middle_klu.update_realtime_analysis() + klu_fx_type = middle_klu.fx_type + klu_strength = middle_klu.fx_strength + klu_confirmed = middle_klu.fx_confirmed + + # KLC分析 + klc_fx_type = middle_klc.fx + klc_strength_raw = middle_klc.cal_fx_strength() # -3到3 + klc_strength_converted = int((klc_strength_raw + 3) * 100 / 6) # 转换为0-100 + + # 输出对比结果 + print(f"K线数据: {case['data'][1]}") + print(f"\nKLU算法结果:") + print(f" 分型类型: {klu_fx_type}") + print(f" 分型强度: {klu_strength}") + print(f" 是否确认: {klu_confirmed}") + + print(f"\nKLC算法结果:") + print(f" 分型类型: {klc_fx_type}") + print(f" 分型强度(原始): {klc_strength_raw}") + print(f" 分型强度(转换): {klc_strength_converted}") + + # 一致性检查 + type_consistent = (klu_fx_type == klc_fx_type) + strength_diff = abs(klu_strength - klc_strength_converted) + strength_consistent = strength_diff <= 10 # 允许10分以内的差异 + + print(f"\n一致性检查:") + print(f" 分型类型一致: {'✅' if type_consistent else '❌'}") + print(f" 强度差异: {strength_diff}分 {'✅' if strength_consistent else '❌'}") + + if not type_consistent or not strength_consistent: + print(f" ⚠️ 算法结果不一致!") + else: + print(f" ✅ 算法结果一致") + else: + print("❌ 数据不足,无法进行对比") + +def detailed_strength_analysis(): + """详细的强度分析对比""" + print("\n" + "=" * 80) + print("详细强度分析对比") + print("=" * 80) + + # 创建一个明确的强分型案例 + strong_top_data = [ + {'open': 100, 'high': 101, 'low': 99, 'close': 100, 'volume': 1000}, + {'open': 100, 'high': 110, 'low': 99, 'close': 102, 'volume': 3000}, # 强顶分型 + {'open': 102, 'high': 103, 'low': 92, 'close': 93, 'volume': 2000}, # 强确认 + {'open': 93, 'high': 94, 'low': 90, 'close': 91, 'volume': 1500}, # 继续下跌 + {'open': 91, 'high': 92, 'low': 88, 'close': 89, 'volume': 1200}, # 进一步确认 + ] + + klus = setup_klu_chain(strong_top_data) + + if len(klus) >= 5: + target_klu = klus[1] # 目标分型K线 + + print(f"分析目标: 第2根K线 (索引1)") + print(f"K线数据: {strong_top_data[1]}") + + # 更新分析 + target_klu.update_realtime_analysis() + + print(f"\n分型检测结果:") + print(f" 分型类型: {target_klu.fx_type}") + print(f" 分型确认: {target_klu.fx_confirmed}") + print(f" 最终强度: {target_klu.fx_strength}") + + # 显示中间计算过程(需要重新调用以获取详细信息) + if target_klu.fx_confirmed: + print(f"\n强度计算过程:") + is_bi_end = target_klu._check_if_bi_ending_fx() + post_confirmation = target_klu._check_post_fx_confirmation() + fx_quality = target_klu._check_fx_quality() + + print(f" 笔终结判断: {is_bi_end}") + print(f" 后续确认: {post_confirmation}") + print(f" 分型质量: {fx_quality}") + + raw_score = is_bi_end + post_confirmation + fx_quality + final_raw = max(-3, min(3, raw_score)) + converted_score = int((final_raw + 3) * 100 / 6) + + print(f" 原始总分: {raw_score} -> {final_raw}") + print(f" 转换分数: {converted_score}") + +if __name__ == "__main__": + # 运行测试 + test_cases = create_test_data() + compare_algorithms(test_cases) + + # 详细分析 + detailed_strength_analysis() + + print("\n" + "=" * 80) + print("测试完成!") + print("=" * 80) \ No newline at end of file diff --git a/realtime_fx_example.py b/realtime_fx_example.py new file mode 100644 index 0000000..8c95897 --- /dev/null +++ b/realtime_fx_example.py @@ -0,0 +1,220 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- +""" +实时K线分型强弱判断示例 +解决KLC滞后问题,提供即时的分型信号 +""" + +from ChanKLU import ChanKLU +from ChanEnum import Chan_FX_TYPE +import pandas as pd +from datetime import datetime, timedelta + +class RealtimeFxAnalyzer: + """实时分型分析器""" + + def __init__(self): + self.klu_list = [] + self.latest_signals = [] + + def add_kline(self, time, open_price, high, low, close, volume, indicators=None): + """ + 添加新的K线数据并进行实时分析 + + Args: + time: 时间 + open_price, high, low, close, volume: K线数据 + indicators: 技术指标字典 {'macd': xx, 'rsi': xx, 'ma5': xx, ...} + """ + # 创建新的KLU对象 + new_klu = ChanKLU(time, open_price, high, low, close, volume) + + # 设置技术指标 + if indicators: + new_klu.set_indicators(indicators) + + # 设置索引 + new_klu.set_idx(len(self.klu_list)) + + # 建立前后关系链 + if len(self.klu_list) >= 1: + prev_klu = self.klu_list[-1] + new_klu.set_pre(prev_klu) + prev_klu.set_next(new_klu) + + # 如果有足够的数据,设置前一根K线的next关系 + if len(self.klu_list) >= 2: + prev_prev_klu = self.klu_list[-2] + prev_prev_klu.set_next(self.klu_list[-1]) + + self.klu_list.append(new_klu) + + # 实时分析最近的K线分型 + self._analyze_recent_fractals() + + return new_klu + + def _analyze_recent_fractals(self): + """分析最近的分型情况""" + if len(self.klu_list) < 3: + return + + # 检查倒数第二根K线的分型(因为需要左右两根K线确认) + target_idx = len(self.klu_list) - 2 + if target_idx >= 1: + target_klu = self.klu_list[target_idx] + + # 进行实时分型分析 + target_klu.update_realtime_analysis() + + # 如果发现分型,记录信号 + if target_klu.fx_confirmed: + signal = target_klu.get_fx_signal() + signal_info = { + 'time': target_klu.time, + 'price': target_klu.close, + 'signal_type': signal[0], + 'strength': signal[1], + 'suggestion': signal[2], + 'fx_type': target_klu.fx_type + } + + self.latest_signals.append(signal_info) + + # 保持最近20个信号 + if len(self.latest_signals) > 20: + self.latest_signals.pop(0) + + print(f"🔔 分型信号: {signal_info['time']} - {signal_info['signal_type']} " + f"(强度: {signal_info['strength']}) - {signal_info['suggestion']}") + + def get_latest_signal(self): + """获取最新的分型信号""" + return self.latest_signals[-1] if self.latest_signals else None + + def get_current_fx_status(self): + """获取当前分型状态统计""" + if len(self.klu_list) < 10: + return {"status": "数据不足"} + + recent_10 = self.klu_list[-10:] + + top_fx_count = sum(1 for klu in recent_10 if klu.fx_type == Chan_FX_TYPE.TOP) + bottom_fx_count = sum(1 for klu in recent_10 if klu.fx_type == Chan_FX_TYPE.BOTTOM) + + strong_fx_count = sum(1 for klu in recent_10 if klu.fx_strength >= 65) + + return { + "最近10根K线": len(recent_10), + "顶分型数量": top_fx_count, + "底分型数量": bottom_fx_count, + "强分型数量": strong_fx_count, + "最新K线时间": recent_10[-1].time, + "最新信号": self.get_latest_signal() + } + +def simulate_realtime_trading(): + """模拟实时交易场景""" + + print("=== 实时K线分型分析示例 ===\n") + + # 创建分析器 + analyzer = RealtimeFxAnalyzer() + + # 模拟实时K线数据流 + base_time = datetime.now() + base_price = 100.0 + + print("开始接收K线数据...\n") + + for i in range(20): + # 模拟价格波动 + if i < 5: # 上涨阶段 + price_change = 0.5 + elif i < 10: # 下跌阶段 + price_change = -0.8 + elif i < 15: # 震荡阶段 + price_change = 0.3 * ((-1) ** i) + else: # 再次上涨 + price_change = 0.6 + + current_price = base_price + price_change + + # 构造K线数据 + open_price = base_price + high = max(open_price, current_price) + abs(price_change) * 0.2 + low = min(open_price, current_price) - abs(price_change) * 0.2 + close = current_price + volume = 1000 + i * 50 + + # 模拟技术指标 + indicators = { + 'ma5': base_price + (i - 10) * 0.1, + 'ma10': base_price + (i - 10) * 0.05, + 'rsi': 50 + (i % 7 - 3) * 10, + 'macd': (i % 6 - 3) * 0.01, + 'macdhist': (i % 4 - 2) * 0.005, + 'volume_ratio': 1.0 + (i % 3 - 1) * 0.2 + } + + # 添加K线数据 + kline_time = base_time + timedelta(minutes=i) + analyzer.add_kline( + time=kline_time.strftime("%Y-%m-%d %H:%M:%S"), + open_price=open_price, + high=high, + low=low, + close=close, + volume=volume, + indicators=indicators + ) + + base_price = current_price + + # 每5根K线显示一次状态 + if (i + 1) % 5 == 0: + status = analyzer.get_current_fx_status() + print(f"\n--- 第{i+1}根K线后的状态 ---") + for key, value in status.items(): + if key != "最新信号": + print(f"{key}: {value}") + + if "最新信号" in status and status["最新信号"]: + signal = status["最新信号"] + print(f"最新信号: {signal['signal_type']} (强度: {signal['strength']})") + print() + + print("\n=== 所有分型信号汇总 ===") + for signal in analyzer.latest_signals: + print(f"{signal['time']} | {signal['signal_type']} | 强度: {signal['strength']} | {signal['suggestion']}") + +def compare_latency(): + """对比KLC和KLU方法的延迟差异""" + + print("\n=== 延迟对比分析 ===") + print("假设场景:连续包含关系的K线序列") + print("原始K线: K1, K2(包含K1), K3(包含K2), K4(突破), K5, K6") + print() + + print("KLC方法:") + print("- 需要等待K4确认包含关系结束") + print("- KLC1 = [K1+K2+K3], 在K4完成时才确定") + print("- 分型检测: 需要等待KLC1, KLC2, KLC3") + print("- 实际延迟: 可能6-8根原始K线") + print() + + print("KLU实时方法:") + print("- 每根K线完成时立即检测") + print("- K3完成时就能检测K2的分型状态") + print("- 实际延迟: 最多1根K线") + print() + + print("延迟改善: 从6-8根K线缩短到1根K线") + print("时间价值: 在5分钟K线下,可节省25-40分钟的反应时间") + +if __name__ == "__main__": + # 运行模拟 + simulate_realtime_trading() + + # 显示延迟对比 + compare_latency() \ No newline at end of file diff --git a/strategies/ChanLun_BTC_15.py b/strategies/ChanLun_BTC_15.py index 0f89fc4..e3c9961 100644 --- a/strategies/ChanLun_BTC_15.py +++ b/strategies/ChanLun_BTC_15.py @@ -14,16 +14,19 @@ import talib.abstract as ta from pandas import DataFrame from datetime import datetime, timedelta from freqtrade.persistence import Trade -from typing import Optional +from typing import Optional, List, Dict import logging logger = logging.getLogger(__name__) +from freqtrade.optimize.space import Categorical, Dimension, Integer, SKDecimal ### 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 -c ./user_data/Chan/config/ChanLun_BTC_15.json --strategy ChanLun_BTC_15 --export none --strategy-path ./user_data/Chan/strategies --timerange=20250525- # freqtrade backtesting -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 +# freqtrade download-data -c ./user_data/Chan/config/ChanLun_BTC_15.json -t 1m --pairs SOL/USDT:USDT --timerange=20250405- +# freqtrade hyperopt --hyperopt-loss SharpeHyperOptLossDaily --spaces stoploss --strategy ChanLun_BTC_15 --strategy-path ./user_data/Chan/strategies -c ./user_data/Chan/config/ChanLun_BTC_15.json -e 200 --timerange=20250201-20250501 +# freqtrade live-backtest -c ./user_data/Chan/config/ChanLun_BTC_15.json --strategy ChanLun_BTC_15 --strategy-path ./user_data/Chan/strategies --timerange=20250525- # sudo docker compose run --rm chanlun_btc backtesting -c ./user_data/Chan/config/ChanLun_BTC_15.json --strategy ChanLun_BTC_15 --strategy-path ./user_data/Chan/strategies --timerange=20250525- # sudo docker compose run --rm chanlun_btc download-data -c ./user_data/Chan/config/ChanLun_BTC_15.json --pairs BTC/USDT:USDT -t 1m --timerange 20240101- @@ -35,10 +38,10 @@ class ChanLun_BTC_15(IStrategy): # 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 + "0": 0.15, + "240": 0.1, + "480": 0.02, + "960": 0 } # 5m and 15m minimal_roi_1 = { @@ -61,14 +64,13 @@ class ChanLun_BTC_15(IStrategy): "3600": 0 } can_short = True - lev = 50.0 - stoploss = -0.3 + lev = 10 + stoploss = -0.8 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 = 600 time5 = 5 @@ -174,8 +176,8 @@ class ChanLun_BTC_15(IStrategy): dataframe.loc[ ( #(dataframe['state'] == "-30") - (dataframe[state_str].shift(self.time5) > 1.0) & - (dataframe[fx_str].shift(self.time5) == -1) + (dataframe[state_str].shift(self.time5*2) > 0) & + (dataframe[fx_str].shift(self.time5*2) == -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") @@ -185,8 +187,8 @@ class ChanLun_BTC_15(IStrategy): dataframe.loc[ ( #(dataframe['state'] == "-30") - (dataframe[state_str].shift(self.time5) > 1.0) & - (dataframe[fx_str].shift(self.time5) == 1) + (dataframe[state_str].shift(self.time5*2) > 0) & + (dataframe[fx_str].shift(self.time5*2) == 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") @@ -200,8 +202,8 @@ class ChanLun_BTC_15(IStrategy): dataframe.loc[ ( #(dataframe['state']== "30") - (dataframe[state_str].shift(self.time5) > 1.0) & - (dataframe[fx_str].shift(self.time5) == 1) + (dataframe[state_str].shift(self.time5*2) > 0) & + (dataframe[fx_str].shift(self.time5*2) == 1) #(dataframe['resample_{}_state'.format(self.get_ticker_indicator()*self.time30)] == "10") & #(dataframe['resample_{}_state'.format(self.get_ticker_indicator()*self.time60)] == "10") ), @@ -209,8 +211,8 @@ class ChanLun_BTC_15(IStrategy): dataframe.loc[ ( #(dataframe['state']== "30") - (dataframe[state_str].shift(self.time5) > 1.0) & - (dataframe[fx_str].shift(self.time5) == -1) + (dataframe[state_str].shift(self.time5*2) > 0) & + (dataframe[fx_str].shift(self.time5*2) == -1) #(dataframe['resample_{}_state'.format(self.get_ticker_indicator()*self.time30)] == "10") & #(dataframe['resample_{}_state'.format(self.get_ticker_indicator()*self.time60)] == "10") ), diff --git a/web/nginx/logs/access.log b/web/nginx/logs/access.log new file mode 100644 index 0000000..e69de29 diff --git a/web/nginx/logs/error.log b/web/nginx/logs/error.log new file mode 100644 index 0000000..f9c73d6 --- /dev/null +++ b/web/nginx/logs/error.log @@ -0,0 +1,18 @@ +2025/05/27 01:57:54 [notice] 1#1: using the "epoll" event method +2025/05/27 01:57:54 [notice] 1#1: nginx/1.27.5 +2025/05/27 01:57:54 [notice] 1#1: built by gcc 12.2.0 (Debian 12.2.0-14) +2025/05/27 01:57:54 [notice] 1#1: OS: Linux 6.10.14-linuxkit +2025/05/27 01:57:54 [notice] 1#1: getrlimit(RLIMIT_NOFILE): 1048576:1048576 +2025/05/27 01:57:54 [notice] 1#1: start worker processes +2025/05/27 01:57:54 [notice] 1#1: start worker process 20 +2025/05/27 01:57:54 [notice] 1#1: start worker process 21 +2025/05/27 01:57:54 [notice] 1#1: start worker process 22 +2025/05/27 01:57:54 [notice] 1#1: start worker process 23 +2025/05/27 01:57:54 [notice] 1#1: start worker process 24 +2025/05/27 01:57:54 [notice] 1#1: start worker process 25 +2025/05/27 01:57:54 [notice] 1#1: start worker process 26 +2025/05/27 01:57:54 [notice] 1#1: start worker process 27 +2025/05/27 01:57:54 [notice] 1#1: start worker process 28 +2025/05/27 01:57:54 [notice] 1#1: start worker process 29 +2025/05/27 01:57:54 [notice] 1#1: start worker process 30 +2025/05/27 01:57:54 [notice] 1#1: start worker process 31 diff --git a/web/templates/index.html b/web/templates/index.html index 589933c..dcf9ec3 100644 --- a/web/templates/index.html +++ b/web/templates/index.html @@ -2941,7 +2941,7 @@ is_strong_fx: fx.is_strong_fx }); const displayText = `${fx.fx_strength_level} ${fx.fx_strength.toFixed(1)}`; - if (fx.fx_strength < 1) { + if (fx.fx_strength < 1.4) { displayText = '' } console.log('显示文本:', displayText);