From e3f089dd1d354756d6f5197e9629fe0f0cfe6d4f Mon Sep 17 00:00:00 2001 From: tab888 Date: Sat, 7 Feb 2026 10:13:41 +0800 Subject: [PATCH] ADD more indictaors, BB,RSI and Z-score --- Trading Indicator Interpretation.csv | 15 +++ ...dicator Interpretation.csv:Zone.Identifier | Bin 0 -> 25 bytes __pycache__/engine.cpython-313.pyc | Bin 34471 -> 36057 bytes app.py | 5 + engine.py | 40 +++++- templates/index.html | 114 ++++++++++++------ 6 files changed, 134 insertions(+), 40 deletions(-) create mode 100644 Trading Indicator Interpretation.csv create mode 100644 Trading Indicator Interpretation.csv:Zone.Identifier diff --git a/Trading Indicator Interpretation.csv b/Trading Indicator Interpretation.csv new file mode 100644 index 0000000..7ab4be6 --- /dev/null +++ b/Trading Indicator Interpretation.csv @@ -0,0 +1,15 @@ +Indicator,Visual Style,What it Means,Suggested Action +Z-Score (60/120),Red Heatmap,Price is +2 standard deviations above the mean (Statistically overstretched).,SELL / TRIM (Take profits) +Z-Score (60/120),Green Heatmap,Price is -2 standard deviations below the mean (Statistically depressed).,BUY / ACCUMULATE +RSI,Red Bold Text,RSI is > 70. Momentum is extremely high/overbought.,CAUTION / HOLD (Don't buy new) +RSI,Green Bold Text,RSI is < 30. Momentum is washed out/oversold.,WATCH FOR BOUNCE +BB %B,Red Bold Text,Price is above the Upper Bollinger Band (>100%).,SELL / HEDGE +BB %B,Green Bold Text,Price is below the Lower Bollinger Band (<0%).,WATCH FOR REVERSAL +52W Range,Red Bar,Price is trading near its 1-year high.,RESISTANCE AREA +52W Range,Green Bar,Price is trading near its 1-year low.,SUPPORT AREA +Z-Score,Red Heatmap,Statistically 2σ above the mean.,SELL / TRIM (Take profits) +Z-Score,Green Heatmap,Statistically 2σ below the mean.,BUY / ACCUMULATE +RSI / BB,Red Text,Overbought (>70) or outside Upper Band.,CAUTION (Stop buying) +RSI / BB,Green Text,Oversold (<30) or below Lower Band.,WATCH (Look for entry) +Dist. to EMA,Percentage %,"How far the price is ""floating"" above/below its average.",MEAN REVERSION: High % usually leads to a pull-back to the line. +KD (K/D),Value Pair,K > D: Bullish momentum. K < D: Bearish momentum.,"THE TRIGGER: Look for ""K"" crossing ""D"" to enter or exit." \ No newline at end of file diff --git a/Trading Indicator Interpretation.csv:Zone.Identifier b/Trading Indicator Interpretation.csv:Zone.Identifier new file mode 100644 index 0000000000000000000000000000000000000000..d6c1ec682968c796b9f5e9e080cc6f674b57c766 GIT binary patch literal 25 dcma!!%Fjy;DN4*MPD?F{<>dl#JyUFr831@K2x-5+p zl&sjysR11PMhr#|P3FDDxM-``A& z_d7)A?@umibv$mq;$<2yx zTh(h;7F z_YDVpaCTGbb{4Xc`k(<_^ZZlHmdmEQ7}6~B?;yzA_%t7&9f)SPdN zsZq~xdPQIYe)3JeE%z_=;ii4}>7HZ)NH%t+Q>mmll)#-4F`fyi@dS0q>{wEar^n=~ z^0yFAjNUhvM;JoffdgVZ8H;4o_=ZY}#}2xOaGnOlmNyd>8Ad}ta5Nu1pxCXZM^eKx z`Rw@mka;@K>QP>Ps+b9BBxdN9PA!~*c5m8oWvT^(> z1bs#ks8B}9hUFkZiP3}BNt-9oU>Ti}wXyUmF^f;r5pq>3lF16OR63K$R8WJTKKXne zyTGX*N@RsILME2R33)p)^=-{9Yu}^QWX{r<&+<&kWWCHz4M(%rd&*h$3M8*r|hLXjBchef~?*g1A zzve%1Vh=P+X`23q`OZb^L@4L zTfrFwOh5z-z5gF7-ERiZ0wxSQN7gBAj7PEwypgE;>+NhbDvZRkvKnWSvieNh zR#|f<*u0f|rGGcQ*rWZylhxVCmeF)7l1-+P*_W}M=9i7pm2EBzM@ENa)0%!VN;m#` zLkbMavdem!YB)Wd5VNO-u^UR>34ry)r^wCz7UU*>>bId*^3Q&Ms11DW0A)@CJwoME zB1EIIc36Ok*^`1YADb$FSJ2lGMkA?m_9tL)2VfUq3vnNP($)>4(otw;27XBV&y!mt zc5>oqtBO^VA054wTdHJg?UQUtZ=UX)>iF2O?XEj8r<&V-OMj=cz=b6@KY6HR^%t#8 zlC^2k8Z27dBx~ComaID`zgDu<7i~ew7F@Ko6m2^s+m3}=$+l~<>q{M84 z97Y^^xNIPA5BviiB-f60t9lXn+p#fpn?#TQ84Z#BgPYOs=JDW775d}6H*!XWKANA2 zeWX)uP|c4HJCQjLYw|8^gU5g&a!uTUI!Q@<6XnQwI*umDCuz6-I}kPrm?rGV7IcoZ zj-+%jIMYDKkq7yD@`sT<=&$qomrRC@7$V<*gc*>sfKz}`z!*grK27$#@_ENNl>-{w z33jEHuTx3Z#1hFAy~1As^I1TSA_M=7=;fnaJ$QnQooz!`$UA3W$h`+4HGm12R8f^X z@wZeBRO30Yo&*0yz;^*t0Oh^ls>0J0a%DUg5#q@cC-Aqy_AP+&)~`}2;91`&a4F}! zz%X$JFTxBd!1uwE0ci@bi9*(9li5_Fd<`qFdI^GF2fP9J0pK!V29Sk;p9B62xB|AT z0ImU`Kjn*R7Nj=;Kc$eXPT<5#LhNKB_9AA%`ZK^!z-3adq)04# zDv}a#dJIp1>*s*C0BehW9n>3$d5XbrgLoXux(O0ARjwY52+L`b;wb(FIHt+%yj%Y( z5Z?j(lKdqk S8Q|l1^)U^y|D6FUv-}UVgX>8E delta 1654 zcmZ9MYfM{Z7{}kYEzp+UwgTlgDY&^*17xrf+{A&KguyZ{r)3*0Er+(yZcyN~w7Uvy zTsC3q_UMdROpICL)R?#svM-C#s2_|mK@*qQeQ_W82Ezn3VtoGv1a^}C&j0zp&-31U ze)~Z6$qkih*=W==@mzj79&V1`H|=aKGK)|xe#=e`V_xnxdCl)JC-1g1!8S&gNC9=U zdbpV$w0eJ~7?ZS;sY%8ttYF<+40r$4wW({`bwrJ z!jZs0Lb)OWB}+_B85Qbp^@;L2ddXdC%7KL~PpX6cGBuyhqqy76FVZh=Ux!UJE|^f6 zjtosG+#5=&`vwvYjbcY(F))S%^t14fPzzZ=1-vDJBrkVlDpn|6pB#<(0&*;<6w)P6 zG0k~uxRsVY`*Jg5a#{aCe=rgo?pHEUI}H3uGo6*(LSJ`g^B3q*r#*cy2HZsr-WRhg zAq#*)fv80k*t_VKw@zhL(XZb7rWH+^E^pDq@}8U5?z%=JT}SErt~r{NYjSM{i++(A zbo#{{c7jWMY2Bd5@*ljB=DQnt1r_=e{0_bAf5Mw69w_A>&E5@suHyG*TY4|3_=DLW z!;f{UzxeFEXdbr&Fp)V-dIe~rpJUa$ob{G?G8b8dxo2^Nh4SU*=c^ku=ggOPB208$@K)vFj zp{uKhd_s$}Du4_mho;mW znitFeVZtD0@@S%{I<=5tG@b`802hHvz-1tY$*cnZ0&l<@q0-6xV^<*00i!?yxC&tP z$~k0@Lrnrx0!e+35*+gRLqY#pB^~ZHV1nM6%s!6uRNe%pfz2bFL3jgr3+P0@Z$n{+ zl16a@WPfZ}?(>Bsapfi&Vze|_ka-*O7C>a1^73P|@u?NHxQ&tNTxT8p*MK9yQGsNJ z&lec*`+UkP7@`Hf1iTO21zrd0fme}|I@kt;z#U3X@8b<*n5o^J3lm=fr2;DT4@}(` k9lTUYJq^y}^3Uk{%>KgT>0H0Wz!i_dqv5uv3`~vk9|qfb^#A|> diff --git a/app.py b/app.py index 4e68cb6..e52247b 100644 --- a/app.py +++ b/app.py @@ -12,6 +12,11 @@ app = Flask(__name__) cache = Cache(app, config={'CACHE_TYPE': 'SimpleCache'}) # The CSV is in the root directory (same level as app.py) CSV_PATH = os.path.join(os.path.dirname(__file__), 'instruments.csv') +# This tells Flask it is behind a proxy and to trust the HTTPS headers from Synology +@app.before_request +def before_request(): + if request.headers.get('X-Forwarded-Proto') == 'https': + request.environ['wsgi.url_scheme'] = 'https' @app.route('/settings') def settings(): diff --git a/engine.py b/engine.py index 3b06c0a..9cb179d 100644 --- a/engine.py +++ b/engine.py @@ -375,7 +375,39 @@ class DataEngine: prev_close = float(df.iloc[-2]['close']) change_pct = ((last_close - prev_close) / prev_close) * 100 count = len(df) + # --- NEW INDICATORS START --- + + # Bollinger Bands %B (20-day) + bb_pct = "N/A" + if count >= 20: + from ta.volatility import BollingerBands + indicator_bb = BollingerBands(close=df['close'], window=20, window_dev=2) + m_avg = indicator_bb.bollinger_mavg().iloc[-1] + h_band = indicator_bb.bollinger_hband().iloc[-1] + l_band = indicator_bb.bollinger_lband().iloc[-1] + # Calculate %B: where is price relative to bands? + if (h_band - l_band) != 0: + bb_pct = round((last_close - l_band) / (h_band - l_band), 2) + # RSI (14-day) + rsi_val = "N/A" + if count >= 14: + from ta.momentum import RSIIndicator + rsi_val = round(RSIIndicator(close=df['close'], window=14).rsi().iloc[-1], 1) + + # Z-Score Helper Function + def get_z_score(window): + if count >= window: + rolling_mean = df['close'].rolling(window=window).mean() + rolling_std = df['close'].rolling(window=window).std() + z = (last_close - rolling_mean.iloc[-1]) / rolling_std.iloc[-1] + return round(z, 2) + return "N/A" + + z60 = get_z_score(60) + z120 = get_z_score(120) + + # Existing EMA Logic (Distance % from EMA) def get_ema_offset(window): if count >= window: from ta.trend import EMAIndicator @@ -396,18 +428,22 @@ class DataEngine: return { "symbol": self.symbol, - "last_date": formatted_date, # <--- New field added + "last_date": formatted_date, "last_close": round(last_close, 2), "change_pct": round(change_pct, 2), "low_52": round(float(df.tail(252)['close'].min()), 2), "high_52": round(float(df.tail(252)['close'].max()), 2), + "bb_pct": bb_pct, + "rsi": rsi_val, + "z60": z60, + "z120": z120, "last_ema20": get_ema_offset(20), "last_ema50": get_ema_offset(50), "last_ema100": get_ema_offset(100), "last_ema200": get_ema_offset(200), "kd_values": f"{int(k_val)}/{int(d_val)}" if k_val != "N/A" else "N/A" } - + class StrategyEngine: """ Handles financial strategy simulations and backtesting. diff --git a/templates/index.html b/templates/index.html index a129710..7d6cc57 100644 --- a/templates/index.html +++ b/templates/index.html @@ -50,6 +50,7 @@ } .table { + /*table-layout: fixed; /* table-layout: auto; */ /* Default - let it be auto for data safety */ width: 100%; @@ -82,6 +83,21 @@ border-bottom: 1px solid #f1f1f1; padding: 10px 8px; } + /* Price Trend Colors */ + .text-up { color: #28a745 !important; } /* Success Green */ + .text-down { color: #dc3545 !important; } /* Danger Red */ + + /* Table Cell Styling */ + #tableBody td { + padding: 8px 4px; + font-size: 0.85rem; + white-space: nowrap; + transition: all 0.2s ease; + } + + /* Custom heatmaps for Z-score */ + .bg-oversold { background-color: #d1e7dd !important; color: #0f5132 !important; } + .bg-overbought { background-color: #f8d7da !important; color: #842029 !important; } /* 3. Sticky Column Logic (Instrument) */ .table td:first-child, @@ -210,7 +226,9 @@
-
Portfolio Signals
+
+ Portfolio Signals +
Updating...
+ ${rsiVal} + ${bbDisplay} + ${z60Val} + ${z120Val} ${typeof formatEma === 'function' ? formatEma(item.last_ema20) : item.last_ema20} ${typeof formatEma === 'function' ? formatEma(item.last_ema50) : item.last_ema50} ${typeof formatEma === 'function' ? formatEma(item.last_ema100) : item.last_ema100} @@ -366,18 +411,11 @@ `; }); - // 5. Final Injection tbody.innerHTML = htmlContent; } catch (error) { console.error("Fetch error:", error); - if (syncDisplay) { - syncDisplay.innerText = "OFFLINE"; - syncDisplay.classList.add('text-danger'); - } - if (tbody) { - tbody.innerHTML = 'Error loading summary. Check server logs.'; - } + if (tbody) tbody.innerHTML = 'Error loading data.'; } finally { if (loading) loading.style.display = 'none'; }