@dendaio/n8n-nodes-collection
Version:
🚀 Comprehensive n8n node collection for financial analysis and automation. Features 55+ technical indicators (RSI, MACD, Bollinger Bands), 32+ candlestick patterns (Doji, Hammer, Engulfing), automated trading strategies, RSS feed processing, and OAuth2 v
491 lines • 22.4 kB
JavaScript
;
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
var desc = Object.getOwnPropertyDescriptor(m, k);
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
desc = { enumerable: true, get: function() { return m[k]; } };
}
Object.defineProperty(o, k2, desc);
}) : (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
o[k2] = m[k];
}));
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
Object.defineProperty(o, "default", { enumerable: true, value: v });
}) : function(o, v) {
o["default"] = v;
});
var __importStar = (this && this.__importStar) || (function () {
var ownKeys = function(o) {
ownKeys = Object.getOwnPropertyNames || function (o) {
var ar = [];
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
return ar;
};
return ownKeys(o);
};
return function (mod) {
if (mod && mod.__esModule) return mod;
var result = {};
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
__setModuleDefault(result, mod);
return result;
};
})();
Object.defineProperty(exports, "__esModule", { value: true });
exports.CandlestickPatternDetector = void 0;
const n8n_workflow_1 = require("n8n-workflow");
const technicalindicators = __importStar(require("technicalindicators"));
class CandlestickPatternDetector {
constructor() {
this.description = {
displayName: 'Candlestick Pattern Detector',
name: 'candlestickPatternDetector',
icon: 'file:icon.svg',
group: ['transform'],
version: 1,
description: 'Detect candlestick patterns using technical analysis',
defaults: {
name: 'Candlestick Pattern Detector',
},
inputs: ["main"],
outputs: ["main"],
properties: [
{
displayName: 'Patterns',
name: 'patterns',
type: 'multiOptions',
options: [
{
name: 'Abandoned Baby',
value: 'abandonedbaby',
description: 'Strong reversal signal',
},
{
name: 'Bearish Engulfing',
value: 'bearishengulfingpattern',
description: 'Strong bearish reversal signal',
},
{
name: 'Bearish Hammer Stick',
value: 'bearishhammerstick',
description: 'Bearish hammer candlestick',
},
{
name: 'Bearish Harami',
value: 'bearishharami',
description: 'Potential bearish reversal',
},
{
name: 'Bearish Harami Cross',
value: 'bearishharamicross',
description: 'Bearish reversal with doji',
},
{
name: 'Bearish Inverted Hammer',
value: 'bearishinvertedhammerstick',
},
{
name: 'Bearish Marubozu',
value: 'bearishmarubozu',
description: 'Strong bearish trend continuation',
},
{
name: 'Bearish Spinning Top',
value: 'bearishspinningtop',
description: 'Indecision with bearish bias',
},
{
name: 'Bullish Engulfing',
value: 'bullishengulfingpattern',
description: 'Strong bullish reversal signal',
},
{
name: 'Bullish Hammer Stick',
value: 'bullishhammerstick',
description: 'Bullish hammer candlestick',
},
{
name: 'Bullish Harami',
value: 'bullishharami',
description: 'Potential bullish reversal',
},
{
name: 'Bullish Harami Cross',
value: 'bullishharamicross',
description: 'Bullish reversal with doji',
},
{
name: 'Bullish Inverted Hammer',
value: 'bullishinvertedhammerstick',
},
{
name: 'Bullish Marubozu',
value: 'bullishmarubozu',
description: 'Strong bullish trend continuation',
},
{
name: 'Bullish Spinning Top',
value: 'bullishspinningtop',
description: 'Indecision with bullish bias',
},
{
name: 'Dark Cloud Cover',
value: 'darkcloudcover',
description: 'Bearish reversal pattern',
},
{
name: 'Doji',
value: 'doji',
description: 'Indicates indecision, potential trend reversal',
},
{
name: 'Downside Tasuki Gap',
value: 'downsidetasukigap',
description: 'Bearish continuation pattern',
},
{
name: 'Dragonfly Doji',
value: 'dragonflydoji',
description: 'Bullish doji pattern',
},
{
name: 'Evening Doji Star',
value: 'eveningdojistar',
description: 'Bearish reversal with doji',
},
{
name: 'Evening Star',
value: 'eveningstar',
description: 'Strong bearish reversal pattern',
},
{
name: 'Gravestone Doji',
value: 'gravestonedoji',
description: 'Bearish doji pattern',
},
{
name: 'Hammer Pattern',
value: 'hammerpattern',
description: 'Bullish reversal signal at bottoms',
},
{
name: 'Hammer Pattern (Unconfirmed)',
value: 'hammerpatternunconfirmed',
description: 'Unconfirmed hammer pattern',
},
{
name: 'Hanging Man',
value: 'hangingman',
description: 'Bearish reversal signal at tops',
},
{
name: 'Hanging Man (Unconfirmed)',
value: 'hangingmanunconfirmed',
description: 'Unconfirmed hanging man pattern',
},
{
name: 'Morning Doji Star',
value: 'morningdojistar',
description: 'Bullish reversal with doji',
},
{
name: 'Morning Star',
value: 'morningstar',
description: 'Strong bullish reversal pattern',
},
{
name: 'Shooting Star',
value: 'shootingstar',
description: 'Bearish reversal signal at tops',
},
{
name: 'Shooting Star (Unconfirmed)',
value: 'shootingstarunconfirmed',
description: 'Unconfirmed shooting star pattern',
},
],
default: ['doji', 'hammerpattern', 'bullishengulfingpattern', 'bearishengulfingpattern'],
noDataExpression: true,
},
{
displayName: 'OHLCV Data',
name: 'ohlcvData',
type: 'string',
default: '',
description: 'OHLCV data as JSON array [[open, high, low, close, volume], ...]',
displayOptions: {
show: {
'/dataSource': ['manual'],
},
},
},
{
displayName: 'Data Source',
name: 'dataSource',
type: 'options',
options: [
{
name: 'Manual Input',
value: 'manual',
description: 'Enter OHLCV data manually',
},
{
name: 'From Input Data',
value: 'input',
description: 'Use OHLCV data from previous node',
},
],
default: 'manual',
},
{
displayName: 'Input Data Field',
name: 'inputDataField',
type: 'string',
default: 'ohlcv',
description: 'Field name containing OHLCV data in input items',
displayOptions: {
show: {
'/dataSource': ['input'],
},
},
},
{
displayName: 'Options',
name: 'options',
type: 'collection',
placeholder: 'Add Option',
default: {},
options: [
{
displayName: 'Return Type',
name: 'returnType',
type: 'options',
options: [
{
name: 'Pattern Results Only',
value: 'patterns',
description: 'Return only pattern detection results',
},
{
name: 'With OHLCV Data',
value: 'withData',
description: 'Return patterns with original OHLCV data',
},
{
name: 'With Metadata',
value: 'metadata',
description: 'Return patterns with calculation metadata',
},
],
default: 'patterns',
},
{
displayName: 'Include Pattern Strength',
name: 'includeStrength',
type: 'boolean',
default: false,
description: 'Whether to include pattern strength indicators (where available)',
},
{
displayName: 'Minimum Data Points',
name: 'minDataPoints',
type: 'number',
default: 5,
description: 'Minimum number of data points required for pattern detection',
},
],
},
],
};
}
async execute() {
const items = this.getInputData();
const returnData = [];
for (let i = 0; i < items.length; i++) {
try {
const patterns = this.getNodeParameter('patterns', i);
const dataSource = this.getNodeParameter('dataSource', i);
const options = this.getNodeParameter('options', i, {});
let ohlcvData;
if (dataSource === 'manual') {
const ohlcvDataString = this.getNodeParameter('ohlcvData', i);
try {
ohlcvData = JSON.parse(ohlcvDataString);
}
catch (error) {
throw new n8n_workflow_1.NodeOperationError(this.getNode(), `Failed to parse OHLCV data: ${error instanceof Error ? error.message : 'Unknown error'}`);
}
}
else {
const inputDataField = this.getNodeParameter('inputDataField', i);
const inputData = items[i].json[inputDataField];
if (!inputData) {
throw new n8n_workflow_1.NodeOperationError(this.getNode(), `Input data field '${inputDataField}' not found in item ${i}`);
}
if (typeof inputData === 'string') {
try {
ohlcvData = JSON.parse(inputData);
}
catch (error) {
throw new n8n_workflow_1.NodeOperationError(this.getNode(), `Failed to parse OHLCV data from input field: ${error instanceof Error ? error.message : 'Unknown error'}`);
}
}
else {
ohlcvData = inputData;
}
}
if (!Array.isArray(ohlcvData) || ohlcvData.length === 0) {
throw new n8n_workflow_1.NodeOperationError(this.getNode(), 'OHLCV data must be a non-empty array');
}
const minDataPoints = options.minDataPoints || 5;
if (ohlcvData.length < minDataPoints) {
throw new n8n_workflow_1.NodeOperationError(this.getNode(), `Insufficient data points. Required: ${minDataPoints}, Provided: ${ohlcvData.length}`);
}
const opens = ohlcvData.map((candle) => candle[0]);
const highs = ohlcvData.map((candle) => candle[1]);
const lows = ohlcvData.map((candle) => candle[2]);
const closes = ohlcvData.map((candle) => candle[3]);
const volumes = ohlcvData.map((candle) => candle[4] || 0);
const inputData = {
open: opens,
high: highs,
low: lows,
close: closes,
volume: volumes,
};
const patternResults = {};
const detectedPatterns = [];
const patternLocations = {};
for (const pattern of patterns) {
try {
const patternFunction = technicalindicators[pattern];
if (typeof patternFunction === 'function') {
const result = patternFunction(inputData);
patternResults[pattern] = result;
if (typeof result === 'boolean' && result) {
detectedPatterns.push(pattern);
patternLocations[pattern] = {
start: ohlcvData.length - 1,
end: ohlcvData.length - 1,
location: 'last',
};
}
else if (Array.isArray(result) && result.length > 0) {
const locations = CandlestickPatternDetector.findPatternLocations(result, pattern);
if (locations.length > 0) {
detectedPatterns.push(pattern);
patternLocations[pattern] = {
locations,
totalOccurrences: locations.length,
};
}
}
}
else {
patternResults[pattern] = {
error: `Pattern function '${pattern}' not found`,
};
}
}
catch (error) {
patternResults[pattern] = {
error: error instanceof Error ? error.message : 'Unknown error',
};
}
}
const outputData = {
detectedPatterns,
patternResults,
patternLocations,
totalPatternsChecked: patterns.length,
patternsDetected: detectedPatterns.length,
};
if (options.returnType === 'withData') {
outputData.ohlcvData = ohlcvData;
}
if (options.returnType === 'metadata') {
outputData.metadata = {
inputLength: ohlcvData.length,
patternsChecked: patterns,
detectedPatterns,
calculationTimestamp: new Date().toISOString(),
dataSource,
};
}
if (options.includeStrength) {
outputData.patternStrength = CandlestickPatternDetector.calculatePatternStrength(detectedPatterns, inputData);
}
returnData.push({
json: outputData,
});
}
catch (error) {
if (this.continueOnFail()) {
returnData.push({
json: {
error: error instanceof Error ? error.message : 'Unknown error occurred',
},
});
continue;
}
throw error;
}
}
return [returnData];
}
static findPatternLocations(result, pattern) {
const locations = [];
for (let i = 0; i < result.length; i++) {
if (result[i] === true) {
let patternLength = 1;
if (['morningstar', 'eveningstar', 'morningdojistar', 'eveningdojistar', 'abandonedbaby'].includes(pattern)) {
patternLength = 3;
}
else if (['bullishengulfingpattern', 'bearishengulfingpattern', 'darkcloudcover', 'downsidetasukigap'].includes(pattern)) {
patternLength = 2;
}
else if (['bullishharami', 'bearishharami', 'bullishharamicross', 'bearishharamicross'].includes(pattern)) {
patternLength = 2;
}
const start = Math.max(0, i - patternLength + 1);
const end = i;
locations.push({
start,
end,
patternLength,
location: `${start}-${end}`,
candleIndex: i,
});
}
}
return locations;
}
static calculatePatternStrength(detectedPatterns, inputData) {
const strength = {};
const { open, high, low, close } = inputData;
const lastIndex = close.length - 1;
const bodySize = Math.abs(close[lastIndex] - open[lastIndex]);
const totalRange = high[lastIndex] - low[lastIndex];
const bodyRatio = totalRange > 0 ? bodySize / totalRange : 0;
strength.bodyRatio = bodyRatio;
strength.bodySize = bodySize;
strength.totalRange = totalRange;
strength.patternCount = detectedPatterns.length;
strength.strongPatterns = detectedPatterns.filter((pattern) => [
'morningstar',
'eveningstar',
'bullishengulfingpattern',
'bearishengulfingpattern',
'abandonedbaby',
].includes(pattern)).length;
if (inputData.volume && inputData.volume.length > 0) {
const avgVolume = inputData.volume.slice(-5).reduce((a, b) => a + b, 0) / 5;
const currentVolume = inputData.volume[lastIndex];
strength.volumeRatio = avgVolume > 0 ? currentVolume / avgVolume : 1;
}
return strength;
}
}
exports.CandlestickPatternDetector = CandlestickPatternDetector;
//# sourceMappingURL=CandlestickPatternDetector.node.js.map