From 9cb242f706a15a4fdfa6ba73832ec7d65a51be87 Mon Sep 17 00:00:00 2001 From: jackyu66git Date: Thu, 24 Apr 2025 19:31:01 +0800 Subject: [PATCH] Add Top and Bottom --- ChanKLC.py | 22 ++++++++- ChanLun.py | 47 +++++++++++++------ __pycache__/ChanEnum.cpython-312.pyc | Bin 6292 -> 6292 bytes __pycache__/ChanKLC.cpython-312.pyc | Bin 19938 -> 20885 bytes __pycache__/ChanLun.cpython-312.pyc | Bin 113893 -> 112036 bytes web/app.py | 25 +++++++++-- web/templates/index.html | 65 +++++++++++++++++++++++++++ 7 files changed, 142 insertions(+), 17 deletions(-) diff --git a/ChanKLC.py b/ChanKLC.py index b70bc6c..b5934f4 100644 --- a/ChanKLC.py +++ b/ChanKLC.py @@ -32,7 +32,27 @@ class ChanKLC(): self.distance = 0 self.klc_fx_type = Chan_KLC_FX.UNKNOWN def set_klc_fx_type(self, klc_fx_type): - self.klc_fx_type = klc_fx_type + print(self.start_time, klc_fx_type, self.get_feature_data()['klu_macd'], self.get_feature_data()['klu_macdhist'], self.get_feature_data()['klu_rsi']) + if self.check_klc_fx_type(klc_fx_type): + self.klc_fx_type = klc_fx_type + def check_klc_fx_type(self, klc_fx_type): + if klc_fx_type == Chan_KLC_FX.BOTTOM1: + features = self.cal_klu_features() + if features['klu_macd'] > 0: + print(self.start_time, klc_fx_type, features['klu_macd']) + return False + else: + return True + elif klc_fx_type == Chan_KLC_FX.TOP1: + features = self.get_feature_data() + if features['klu_macd'] < 0: + print(self.start_time, klc_fx_type, features['klu_macd']) + return False + else: + return True + else: + return True + def add_klu(self, klu): self.klus.append(klu) def set_end_klu(self, klu): diff --git a/ChanLun.py b/ChanLun.py index ad46946..90f7e19 100644 --- a/ChanLun.py +++ b/ChanLun.py @@ -551,7 +551,7 @@ class ChanLun(): # A new top found #last_top.set_fx(Chan_FX_TYPE.UNKNOWN) last_top = klc - print(klc.start_time, klc.fx, bi_list[-1].dir, "Last Top Change 1") + #print(klc.start_time, klc.fx, bi_list[-1].dir, "Last Top Change 1") klc.set_klc_fx_type(Chan_KLC_FX.TOP1) #print(klc.start_time, klc.fx, "一类卖点Sell 1") #klc.set_fx(fx) @@ -578,7 +578,7 @@ class ChanLun(): #last_top.set_fx(Chan_FX_TYPE.PTOP) last_top = klc last_bottom = pre_last_bi.start_klc - print(klc.start_time, klc.fx, bi_list[-1].dir, "Last Top Bottom Change 1") + #print(klc.start_time, klc.fx, bi_list[-1].dir, "Last Top Bottom Change 1") klc.set_klc_fx_type(Chan_KLC_FX.TOP2) #print(klc.start_time, last_bi.start_klc.start_time, "New TOP Found reset last bi") klc.set_state("10") @@ -602,7 +602,7 @@ class ChanLun(): bi.add_klc(klc) bi_list.append(bi) last_top = klc - print(klc.start_time, klc.fx, bi_list[-1].dir, "Last Top Change 2") + #print(klc.start_time, klc.fx, bi_list[-1].dir, "Last Top Change 2") klc.set_klc_fx_type(Chan_KLC_FX.TOP2) klc.set_state('30') bi_list[-1].add_klc(klc) @@ -616,7 +616,7 @@ class ChanLun(): last_bi.set_start_klc(klc, Chan_BI_DIR.DOWN) #last_top.set_fx(Chan_FX_TYPE.UNKNOWN) last_top = klc - print(klc.start_time, klc.fx, bi_list[-1].dir, "Last Top Change 3") + #print(klc.start_time, klc.fx, bi_list[-1].dir, "Last Top Change 3") bi_list[-1].add_klc(klc) klc.set_bi(bi_list[-1]) #print(klc.start_time, klc.fx, "笔卖点Sell 3") @@ -637,7 +637,7 @@ class ChanLun(): else: # First temp top and last bottom confirmed last_top = klc - print(klc.start_time, klc.fx, bi_list[-1].dir, "Last Top Change 4") + #print(klc.start_time, klc.fx, bi_list[-1].dir, "Last Top Change 4") bi_list[-1].add_klc(klc) klc.set_bi(bi_list[-1]) #print(klc.start_time, klc.fx, "一类卖点Sell 1") @@ -649,7 +649,7 @@ class ChanLun(): bi_list.append(bi) bi_list[-1].add_klc(klc) klc.set_bi(bi_list[-1]) - print(klc.start_time, klc.fx, bi_list[-1].dir, "Last Top Change 5") + #print(klc.start_time, klc.fx, bi_list[-1].dir, "Last Top Change 5") #print(klc.start_time, 'Create first top') #print(klc.start_time, klc.fx, "笔卖点Sell 1") #klc.fx = Bottom ======================== @@ -669,7 +669,7 @@ class ChanLun(): # A new bottom found #last_bottom.set_fx(Chan_FX_TYPE.UNKNOWN) last_bottom = klc - print(klc.start_time, klc.fx, bi_list[-1].dir, "Last Bottom Change 1") + #print(klc.start_time, klc.fx, bi_list[-1].dir, "Last Bottom Change 1") klc.set_klc_fx_type(Chan_KLC_FX.BOTTOM1) #print(klc.start_time, klc.fx, "一类买点Buy 1") klc.set_state("-10") @@ -697,7 +697,7 @@ class ChanLun(): #last_bottom.set_fx(Chan_FX_TYPE.PBOTTOM) last_bottom = klc last_top = pre_last_bi.start_klc - print(klc.start_time, klc.fx, bi_list[-1].dir, "Last Top Bottom Change 2") + #print(klc.start_time, klc.fx, bi_list[-1].dir, "Last Top Bottom Change 2") klc.set_klc_fx_type(Chan_KLC_FX.BOTTOM2) #print(klc.start_time, last_bi.start_klc.start_time, "New BOTTOM Found reset last bi") klc.set_state("-10") @@ -721,7 +721,7 @@ class ChanLun(): bi.add_klc(klc) bi_list.append(bi) last_bottom = klc - print(klc.start_time, klc.fx, bi_list[-1].dir, "Last Bottom Change 2") + #print(klc.start_time, klc.fx, bi_list[-1].dir, "Last Bottom Change 2") klc.set_klc_fx_type(Chan_KLC_FX.BOTTOM2) klc.set_state('-30') bi_list[-1].add_klc(klc) @@ -735,7 +735,7 @@ class ChanLun(): last_bi.set_start_klc(klc, Chan_BI_DIR.UP) #last_bottom.set_fx(Chan_FX_TYPE.UNKNOWN) last_bottom = klc - print(klc.start_time, klc.fx, bi_list[-1].dir, "Last Bottom Change 3") + #print(klc.start_time, klc.fx, bi_list[-1].dir, "Last Bottom Change 3") bi_list[-1].add_klc(klc) klc.set_bi(bi_list[-1]) #print(klc.start_time, klc.fx, "笔买点Buy 3") @@ -757,7 +757,7 @@ class ChanLun(): else: # First temp bottom and last top confirmed last_bottom = klc - print(klc.start_time, klc.fx, bi_list[-1].dir, "Last Bottom Change 4") + #print(klc.start_time, klc.fx, bi_list[-1].dir, "Last Bottom Change 4") bi_list[-1].add_klc(klc) klc.set_bi(bi_list[-1]) #print(klc.start_time, klc.fx, "一类买点Buy 1") @@ -769,7 +769,7 @@ class ChanLun(): bi_list.append(bi) bi_list[-1].add_klc(klc) klc.set_bi(bi_list[-1]) - print(klc.start_time, klc.fx, bi_list[-1].dir, "Last Bottom Change 5") + #print(klc.start_time, klc.fx, bi_list[-1].dir, "Last Bottom Change 5") #print(klc.start_time, klc.fx, "笔买点Buy 4") #if klc.fx != Chan_FX_TYPE.UNKNOWN: #print(klc.start_time, klc.fx, klc.index) @@ -1661,6 +1661,15 @@ class ChanLun(): else: ax1.text(big_dates_num[text_index], seg.end_bi.end_klc.low-1, seg_macd_div, color='green', fontsize=14, alpha=0.6) big_klc = self.get_klc_list(big_df) + self.cal_bi_list(big_klc) + for klc in big_klc: + if klc.klc_fx_type != Chan_KLC_FX.UNKNOWN: + text_index = klc.end_klu.index + if klc.fx == Chan_FX_TYPE.BOTTOM: + ax1.text(big_dates_num[text_index], klc.low, str(klc.klc_fx_type).replace("Chan_KLC_FX.", ""), color='green', fontsize=6, alpha=1) + else: + ax1.text(big_dates_num[text_index], klc.high, str(klc.klc_fx_type).replace("Chan_KLC_FX.", ""), color='red', fontsize=6, alpha=1) + """ model = xgb.Booster() model.load_model("30m_modelchan_xgb_model.json") for klc in big_klc: @@ -1671,7 +1680,8 @@ class ChanLun(): if predict > 0.35 and klc.fx == Chan_FX_TYPE.TOP: text_index = klc.end_klu.index ax1.text(big_dates_num[text_index], klc.low-1, predict, color='green', fontsize=14, alpha=0.6) - + """ + """ # 绘制大周期买卖点 marker_styles = { @@ -1825,6 +1835,15 @@ class ChanLun(): else: ax2.text(small_dates_num[text_index], seg.end_bi.end_klc.low-1, seg_macd_div, color='green', fontsize=14, alpha=0.8) small_klc = self.get_klc_list(small_df) + self.cal_bi_list(small_klc) + for klc in small_klc: + if klc.klc_fx_type != Chan_KLC_FX.UNKNOWN: + text_index = klc.end_klu.index + if klc.fx == Chan_FX_TYPE.BOTTOM: + ax2.text(small_dates_num[text_index], klc.low, str(klc.klc_fx_type).replace("Chan_KLC_FX.", ""), color='green', fontsize=6, alpha=1) + else: + ax2.text(small_dates_num[text_index], klc.high, str(klc.klc_fx_type).replace("Chan_KLC_FX.", ""), color='red', fontsize=6, alpha=1) + """ model = xgb.Booster() model.load_model("5m_modelchan_xgb_model.json") for klc in small_klc: @@ -1836,6 +1855,8 @@ class ChanLun(): text_index = klc.end_klu.index ax2.text(small_dates_num[text_index], klc.low-1, predict, color='green', fontsize=14, alpha=0.6) """ + + """ # 绘制小周期买卖点 for idx, point in small_buy_sell_points.items(): if idx < 0 or idx >= len(small_df): diff --git a/__pycache__/ChanEnum.cpython-312.pyc b/__pycache__/ChanEnum.cpython-312.pyc index 2f7ebd55a745a290259ce08a9957fb1746631396..0e8de31bdeddbea2654b7c9e6180e118d449cd69 100644 GIT binary patch delta 20 acmbPYIK`0rG%qg~0}wRua&6@9lmGxTYXo@! delta 20 acmbPYIK`0rG%qg~0}x#N#JQ2XQvv`w`vtB5 diff --git a/__pycache__/ChanKLC.cpython-312.pyc b/__pycache__/ChanKLC.cpython-312.pyc index d2c67b5ea4664d78b47a9171a68593a46aa1221f..55ab417951dbf468316ca10f6749217686b10b4f 100644 GIT binary patch delta 1625 zcmbVMZ%kWN6uwV~-mvM_4tAE2bsFx8O^ymS>ly~^!DtS> zs-b{u`lmN$ z-uM%tS=`G#%q+XGyudv`sE>O&b2!BPj*xDcOB+lnLvhkjyjFJGP?0VwU26Z)=zO3; z#!6DgLepq&uFi!Kx{T&X+nn*6wy`b!F(jJd~( zb*Kk>FiNNrYUYK#n6X$>&8QaWJuyG$Nf+F{i~0X@kx+}8Zc@%%5@wE$_lsHrb(ghm zJ^lSXzAD)q3XU^&r}kKnk)vR(R72h0(_2NV*j`la(=GFl3lxh%x-OTC%!_d6Sq3u> z??~`WXP39)Uv-1B#V#;dKxsqnYFR3;JegO%X(&(I@>4cX(&pK+RV;O+O|}(%(sXDo zxM_0VFn_`azUkpllcsj(P?6b=)LrAP6b|&1k1Y+WYy}$19aPJ#pk6ztSzdnW-w}@4UPib zNe3NAivOu6F*B8@&Q$U+m{|bY=~ah_-;us^T)~Hiz=R{kps)foFkl}T)R`z%9;VCr z4!kO@=U>2L6H|z4Ssfk?#WVKMEwW}b9+`--UQ@o&T6RQZ1uZU8!=xGe-g`gYc~_+E z0?~<^Bz@fn*iI_J6Pptum=nK1b198XZ`Qjtq=IdwNA+rqqx7!^cTp#(3jh?7U`P=g zz$t(hdaTi%1<^@8C5=$$>x~C-5hS~x&(pK#bLn1Ft$-agud8$(0?67->I0+{Ts;S% z;696Xxx&V}9xcKWLEO}gN-b% dAMp(nli{iHNE5k5Kk+?dI<3X_9R#Qt{SEwws4D;f delta 837 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 +
+ + +