diff --git a/toolbox/utilities/getYahoo.m b/toolbox/utilities/getYahoo.m index cf9462ef6..cf148179c 100644 --- a/toolbox/utilities/getYahoo.m +++ b/toolbox/utilities/getYahoo.m @@ -96,6 +96,7 @@ % 'candle' = candlestick chart % 'line' = close price only % 'ma' = close price and moving averages +% 'bollinger' = close price with Bollinger Bands (SMA and ±σ bands) % Default is 'candle'. % Example - 'topPanelMode','ma' % Data Types - char | string @@ -229,6 +230,16 @@ % Example - 'msg',false % Data Types - logical % +% WindowSize : window length for Bollinger Bands. Positive scalar integer. +% Default is 20. +% Example - 'WindowSize',15 +% Data Types - double +% +% NumStd : number of standard deviations for Bollinger Bands. Positive scalar. +% Default is 2. +% Example - 'NumStd',2.5 +% Data Types - double +% % Output: % % out : structure array containing the following fields @@ -875,10 +886,6 @@ function localPlotYahoo(TT, tickerNow, LastPeriod, intervalThis, ... % Figure and manual layout figure('Color','w'); -if ~isMATLABReleaseOlderThan("R2025a") - theme(gcf, "light") -end - leftMargin = 0.07; rightMargin = 0.03; bottomMargin = 0.08; @@ -903,6 +910,111 @@ function localPlotYahoo(TT, tickerNow, LastPeriod, intervalThis, ... %% Top panel switch lower(topPanelMode) + case {'bb','bollinger'} + % Bollinger Bands plot in the top panel (independent from 'out') + % Requirements: TT.Close, ax1, x must be available in localPlotYahoo workspace + + % Parameters (change to read from options if desired) + bbWindow = 20; + bbNSig = 2; + + closePrices = TT.Close; + + % Compute Bollinger: use bollinger() if available, otherwise movmean/movstd + if exist('bollinger','file') == 2 + try + [bbMid, bbUp, bbLow] = bollinger(closePrices, bbWindow, bbNSig); + catch + bbMid = movmean(closePrices, bbWindow, 'omitnan'); + bbSigma = movstd(closePrices, bbWindow, 'omitnan'); + bbUp = bbMid + bbNSig .* bbSigma; + bbLow = bbMid - bbNSig .* bbSigma; + end + else + bbMid = movmean(closePrices, bbWindow, 'omitnan'); + bbSigma = movstd(closePrices, bbWindow, 'omitnan'); + bbUp = bbMid + bbNSig .* bbSigma; + bbLow = bbMid - bbNSig .* bbSigma; + end + + % Size safety + bbMid = bbMid(:); bbUp = bbUp(:); bbLow = bbLow(:); + xvec = x(:); + yvec = closePrices(:); + + % Remove NaNs consistently across all vectors + valid = ~isnan(xvec) & ~isnan(yvec); + if nnz(valid) < 2 + % nothing to plot: exit the case + return; + end + xvec = xvec(valid); + yvec = yvec(valid); + % align bands if there are initial NaN elements + if numel(bbMid) ~= numel(closePrices) + bbMid = interp1(find(~isnan(closePrices)), bbMid(~isnan(closePrices)), 1:numel(closePrices), 'linear', NaN)'; + end + bbMid = bbMid(valid); + bbUp = bbUp(valid); + bbLow = bbLow(valid); + + % Set axis and title + hold(ax1,'on'); grid(ax1,'on'); + + % Up/Down colored segments: successive differences + d = [0; diff(yvec)]; + upIdx = d >= 0; + dnIdx = d < 0; + + % Sequential up/down color plotting (no datetime conversion) + if any(upIdx) + plot(ax1, xvec(upIdx), yvec(upIdx), '-', 'Color', [0 0.6 0], 'LineWidth', 1.4); + end + if any(dnIdx) + plot(ax1, xvec(dnIdx), yvec(dnIdx), '-', 'Color', [0.85 0.15 0.15], 'LineWidth', 1.4); + end + + % SMA and bands + plot(ax1, xvec, bbMid, '-','Color',[0 0.4470 0.7410],'LineWidth',1); + plot(ax1, xvec, bbUp, '--','Color',[0.3 0.3 0.3],'LineWidth',0.9); + plot(ax1, xvec, bbLow, '--','Color',[0.3 0.3 0.3],'LineWidth',0.9); + + % Fill band using datetime directly (if supported) + try + xpatch = [xvec; flipud(xvec)]; + ypatch = [bbUp; flipud(bbLow)]; + hPatch = patch('XData', xpatch, 'YData', ypatch, ... + 'FaceColor',[0.6 0.6 0.6], 'FaceAlpha', 0.08, ... + 'EdgeColor','none', 'Parent', ax1); + uistack(hPatch, 'bottom'); + catch + % if MATLAB version does not support patch with datetime, skip fill + end + + % Marker on the last point + plot(ax1, xvec(end), yvec(end), 'o', 'MarkerFaceColor',[0 0.4470 0.7410], 'MarkerEdgeColor','k'); + + % Set limits with a small margin + try + xr = [xvec(1) xvec(end)]; + yrLow = min(bbLow(~isnan(bbLow))); yrHigh = max(bbUp(~isnan(bbUp))); + if isempty(yrLow) || isempty(yrHigh) || isnan(yrLow) || isnan(yrHigh) + yrLow = min(yvec); yrHigh = max(yvec); + end + pad = 0.06 * (yrHigh - yrLow); + ylim(ax1, [yrLow - pad, yrHigh + pad]); + xlim(ax1, xr); + catch + % fallback: autoscale + end + + % Minimal legend (update if you want to remove duplicate entries) + legend(ax1, {'Price up','Price down','SMA','Upper','Lower'}, 'Location', 'best'); + + hold(ax1,'off'); + + % End case + case 'candle' if removeGaps Data = [TT.Open TT.High TT.Low TT.Close]; @@ -1474,4 +1586,4 @@ function showPanelExplanationCommand(topPanelMode, bottomPanelMode, ... intervalOut = priority{idxAllowed}; end -%FScategory:UTI-FIN \ No newline at end of file +%FScategory:UTI-FIN