Creating a Custom Candlestick and MACD Analysis Tool in n8n
I built a compact workflow that turns raw stock charts into candlestick and MACD analysis inside n8n. It pulls OHLCV data, calculates MACD, checks a few candlestick patterns, and writes out tidy results that can feed a dashboard or alert.
Design and data flow
Keep the flow linear: fetch OHLCV, normalise it, calculate indicators, run pattern checks, then emit the result. That keeps the workflow readable and easier to debug.
What data you need
- Time, open, high, low, close, volume for each bar. That is OHLCV.
- A consistent timeframe. Do not mix minute bars with daily bars in the same run.
- A reliable source. Any REST API that returns JSON or CSV will do.
Indicator choices
- I use standard MACD settings: 12, 26, 9. MACD is the difference between a short EMA and a long EMA. The signal line is an EMA of the MACD. The histogram is MACD minus signal. That is straightforward to compute in a Function node.
- The candlestick patterns I check are low-cost and deterministic: bullish engulfing, bearish engulfing, hammer, and shooting star. They are quick to compute and give clear signals when combined with MACD crossovers.
Data flow in n8n
- Trigger node: Cron or Webhook. Cron for scheduled batch runs. Webhook for on-demand checks.
- HTTP Request node: Pull OHLCV from your chosen API. Keep the API key secret. Request only the fields you need.
- Function node: Convert timestamps to ISO, sort bars ascending, and calculate EMAs, MACD, signal, histogram, and basic candlestick booleans.
- IF node: Filter bars with the conditions you care about. For example, bullish engulfing and MACD histogram turning positive on the same bar.
- Output node: Post to an endpoint, save to Google Sheets, or write a JSON file. I tend to push an annotated CSV to an S3-compatible bucket for later charting.
Why this layout works
- It separates fetching, calculation, and decision logic. That makes debugging easier.
- n8n nodes are easy to replace. If the API changes, only the HTTP Request node needs work.
- The Function node keeps the heavy lifting local. That avoids round trips and reduces cost.
Concrete example of checks
- Bullish signal: bullish engulfing + MACD histogram crossing from negative to positive on the same bar.
- Bearish signal: bearish engulfing + MACD histogram crossing from positive to negative.
- Weak signal: MACD histogram with a small absolute value. Log it, but do not act on it.
Step-by-step n8n implementation and verification
I used the steps below. The MACD and EMA logic can go straight into a Function node.
1) Trigger and fetch
- Add a Cron node to run at your chosen cadence.
- Add an HTTP Request node. Point it at a provider that returns OHLCV. Set authentication and query parameters.
- Map the response to the Function node. Pass only the array of bars.
2) Normalise and sort
- In the Function node, coerce timestamps to ISO and sort ascending by time. That avoids off-by-one errors in EMA calculations.
3) EMA and MACD calculation
function ema(prices, period) {
const k = 2 / (period + 1);
let emaPrev = prices[0];
const out = [emaPrev];
for (let i = 1; i < prices.length; i++) {
const v = prices[i] * k + emaPrev * (1 - k);
out.push(v);
emaPrev = v;
}
return out;
}
const closes = items.map(i => i.json.close);
const emaShort = ema(closes, 12);
const emaLong = ema(closes, 26);
const macd = emaShort.map((v, idx) => v - emaLong[idx]);
const signal = ema(macd, 9);
const hist = macd.map((v, idx) => v - (signal[idx] || 0));
- Attach MACD, signal, and histogram back to each bar object.
4) Candlestick pattern checks
- Use deterministic checks. Example for bullish engulfing:
const isBullishEngulfing = (prev, curr) =>
prev.close < prev.open &&
curr.close > curr.open &&
curr.open < prev.close &&
curr.close > prev.open;
- Add booleans for patterns to each bar.
5) Decision logic
- Use an IF node or continue the logic inside the Function node.
- Condition example: current.hist > 0 and previous.hist <= 0 and isBullishEngulfing(prev, curr).
- When true, format an output message with timestamp, symbol, pattern, MACD, signal, and histogram.
6) Output and storage
- Output options: webhook POST, plain text file, CSV, spreadsheet row, or push to a time-series database.
- I favour storing raw annotated OHLCV plus flags. It makes later charting and auditing easier.
Verification steps
- Test with static sample data. Feed known OHLC sequences where the expected MACD values are calculable by hand. Compare results.
- Export the output to CSV and overlay MACD and pattern flags on a charting tool.
- Watch for missing bars, NaN from short input arrays, and timezone mismatches. Add guards in the Function node that skip insufficient data and log the issue.
Practical tips and pitfalls
- Use UTC throughout. Local timezones lead to invisible bugs in intraday data.
- Keep MACD parameters configurable. Different markets may prefer different settings.
- If your data has gaps, fill or skip the gaps deliberately. EMA is sensitive to gaps.
- Rate limits matter: fetch only what you need. Requesting decades of minute data hits limits and costs more.
- For speed, compute EMA iteratively and carry the last EMA forward between runs if you run the workflow often.
Concrete output examples
- Single-row alert: { time: “2025-09-01T09:30:00Z”, symbol: “ABC”, signal: “bullish_engulfing + MACD_cross”, macd: 0.12, signalLine: 0.05, hist: 0.07 }
- Daily CSV: time,open,high,low,close,volume,macd,signal,hist,bullishEngulfing,bearishEngulfing
What the tool delivers
- Automated candlestick MACD analysis on a schedule.
- Simple decision flags for filtering.
- Clean, auditable output for charting or feeding into dashboards.
- Raw annotated data that supports later analysis.
Takeaways
- Keep the Function node small and testable. Small functions are easier to validate.
- Store annotated outputs. It saves hours when you need to show why a signal fired.
- MACD plus a clear candlestick pattern gives conservative and explainable signals.
- Use n8n for orchestration, not charting. Push results to the right tool for visualisation.
That is the setup I use. It runs on simple cron schedules, handles more data without much fuss, and makes candlestick MACD analysis repeatable.

