@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
354 lines • 16.8 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.SupportResistanceDetector = void 0;
const n8n_workflow_1 = require("n8n-workflow");
const ti = __importStar(require("technicalindicators"));
class SupportResistanceDetector {
constructor() {
this.description = {
displayName: 'Support Resistance Detector',
name: 'supportResistanceDetector',
icon: 'file:icon.svg',
group: ['transform'],
version: 1,
description: 'Detect support and resistance levels using pivot points and swing highs/lows',
defaults: {
name: 'Support Resistance Detector',
},
inputs: ["main"],
outputs: ["main"],
properties: [
{
displayName: 'Detection Methods',
name: 'detectionMethods',
type: 'multiOptions',
options: [
{
name: 'Pivot Points',
value: 'pivotPoints',
description: 'Calculate pivot points (PP, R1, R2, R3, S1, S2, S3)',
},
{
name: 'Swing Highs and Lows',
value: 'swingHighsLows',
description: 'Detect swing highs and swing lows for support/resistance levels',
},
{
name: 'Fibonacci Retracement',
value: 'fibonacciRetracement',
description: 'Calculate Fibonacci retracement levels',
},
],
default: ['pivotPoints', 'swingHighsLows'],
noDataExpression: true,
},
{
displayName: 'OHLCV Data',
name: 'ohlcvData',
type: 'string',
default: '={{ $json.ohlcv }}',
description: 'JSON string containing OHLCV data array',
},
{
displayName: 'Swing Period',
name: 'swingPeriod',
type: 'number',
default: 5,
description: 'Period for swing high/low detection',
displayOptions: {
show: {
detectionMethods: ['swingHighsLows'],
},
},
},
{
displayName: 'Fibonacci Start Price',
name: 'fibonacciStart',
type: 'number',
default: 0,
description: 'Starting price for Fibonacci retracement calculation (0 = use lowest low)',
displayOptions: {
show: {
detectionMethods: ['fibonacciRetracement'],
},
},
},
{
displayName: 'Fibonacci End Price',
name: 'fibonacciEnd',
type: 'number',
default: 0,
description: 'Ending price for Fibonacci retracement calculation (0 = use highest high)',
displayOptions: {
show: {
detectionMethods: ['fibonacciRetracement'],
},
},
},
{
displayName: 'Options',
name: 'options',
type: 'collection',
placeholder: 'Add Option',
default: {},
options: [
{
displayName: 'Return Type',
name: 'returnType',
type: 'options',
options: [
{
name: 'Values Only',
value: 'values',
description: 'Return only the calculated values',
},
{
name: 'With Metadata',
value: 'metadata',
description: 'Return values with additional metadata',
},
],
default: 'values',
},
{
displayName: 'Include Current Levels',
name: 'includeCurrent',
type: 'boolean',
default: true,
description: 'Whether to include current support/resistance levels in output',
},
],
},
],
};
}
async execute() {
var _a, _b, _c, _d;
const items = this.getInputData();
const returnData = [];
for (let i = 0; i < items.length; i++) {
try {
const detectionMethods = this.getNodeParameter('detectionMethods', i);
const ohlcvDataString = this.getNodeParameter('ohlcvData', i);
const options = this.getNodeParameter('options', i, {});
let ohlcvData;
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'}`);
}
if (!Array.isArray(ohlcvData) || ohlcvData.length === 0) {
throw new n8n_workflow_1.NodeOperationError(this.getNode(), 'OHLCV data must be a non-empty array');
}
const highs = ohlcvData.map((candle) => candle[1]);
const lows = ohlcvData.map((candle) => candle[2]);
const closes = ohlcvData.map((candle) => candle[3]);
const results = {};
if (detectionMethods.includes('pivotPoints')) {
const pivotPoints = [];
for (let j = 0; j < closes.length; j++) {
const high = highs[j];
const low = lows[j];
const close = closes[j];
const pp = (high + low + close) / 3;
const r1 = 2 * pp - low;
const s1 = 2 * pp - high;
const r2 = pp + (high - low);
const s2 = pp - (high - low);
const r3 = high + 2 * (pp - low);
const s3 = low - 2 * (high - pp);
pivotPoints.push({
index: j,
high,
low,
close,
pp: Number(pp.toFixed(4)),
r1: Number(r1.toFixed(4)),
r2: Number(r2.toFixed(4)),
r3: Number(r3.toFixed(4)),
s1: Number(s1.toFixed(4)),
s2: Number(s2.toFixed(4)),
s3: Number(s3.toFixed(4)),
});
}
results.pivotPoints = {
levels: pivotPoints,
current: pivotPoints[pivotPoints.length - 1] || null,
};
}
if (detectionMethods.includes('swingHighsLows')) {
const swingPeriod = this.getNodeParameter('swingPeriod', i, 5);
const swingHighs = [];
const swingLows = [];
const rollingHighs = ti.highest({ values: highs, period: swingPeriod });
const rollingLows = ti.lowest({ values: lows, period: swingPeriod });
for (let j = swingPeriod - 1; j < highs.length; j++) {
if (highs[j] === rollingHighs[j - (swingPeriod - 1)]) {
let isSwingHigh = true;
for (let k = Math.max(0, j - swingPeriod); k < Math.min(highs.length, j + swingPeriod + 1); k++) {
if (k !== j && highs[k] >= highs[j]) {
isSwingHigh = false;
break;
}
}
if (isSwingHigh) {
swingHighs.push({
index: j,
price: highs[j],
type: 'resistance',
});
}
}
}
for (let j = swingPeriod - 1; j < lows.length; j++) {
if (lows[j] === rollingLows[j - (swingPeriod - 1)]) {
let isSwingLow = true;
for (let k = Math.max(0, j - swingPeriod); k < Math.min(lows.length, j + swingPeriod + 1); k++) {
if (k !== j && lows[k] <= lows[j]) {
isSwingLow = false;
break;
}
}
if (isSwingLow) {
swingLows.push({
index: j,
price: lows[j],
type: 'support',
});
}
}
}
const allLevels = [...swingHighs, ...swingLows].sort((a, b) => a.index - b.index);
const currentSwingHigh = swingHighs.length > 0 ? swingHighs[swingHighs.length - 1] : null;
const currentSwingLow = swingLows.length > 0 ? swingLows[swingLows.length - 1] : null;
results.swingHighsLows = {
swingHighs,
swingLows,
allLevels,
period: swingPeriod,
current: {
latestSwingHigh: currentSwingHigh,
latestSwingLow: currentSwingLow,
nearestResistance: (currentSwingHigh === null || currentSwingHigh === void 0 ? void 0 : currentSwingHigh.price) || null,
nearestSupport: (currentSwingLow === null || currentSwingLow === void 0 ? void 0 : currentSwingLow.price) || null,
},
};
}
if (detectionMethods.includes('fibonacciRetracement')) {
const fibonacciStart = this.getNodeParameter('fibonacciStart', i, 0);
const fibonacciEnd = this.getNodeParameter('fibonacciEnd', i, 0);
const actualStart = fibonacciStart === 0 ? Math.min(...lows) : fibonacciStart;
const actualEnd = fibonacciEnd === 0 ? Math.max(...highs) : fibonacciEnd;
const retracementLevels = ti.fibonacciretracement(actualStart, actualEnd);
const levelNames = [
'0%',
'23.6%',
'38.2%',
'50%',
'61.8%',
'78.6%',
'100%',
'127.2%',
'161.8%',
'261.8%',
'423.6%',
];
const levels = levelNames.map((name, index) => ({
level: name,
price: Number(retracementLevels[index].toFixed(4)),
type: index <= 6 ? 'retracement' : 'extension',
}));
const currentPrice = closes[closes.length - 1];
const nearestRetracement = levels
.filter((level) => level.type === 'retracement')
.reduce((nearest, level) => {
if (!nearest)
return level;
return Math.abs(level.price - currentPrice) < Math.abs(nearest.price - currentPrice)
? level
: nearest;
}, null);
results.fibonacciRetracement = {
startPrice: actualStart,
endPrice: actualEnd,
levels,
retracementLevels: levels.filter((level) => level.type === 'retracement'),
extensionLevels: levels.filter((level) => level.type === 'extension'),
current: {
currentPrice,
nearestRetracementLevel: nearestRetracement,
keyLevels: {
support23_6: ((_a = levels.find((level) => level.level === '23.6%')) === null || _a === void 0 ? void 0 : _a.price) || null,
resistance38_2: ((_b = levels.find((level) => level.level === '38.2%')) === null || _b === void 0 ? void 0 : _b.price) || null,
support50_0: ((_c = levels.find((level) => level.level === '50%')) === null || _c === void 0 ? void 0 : _c.price) || null,
resistance61_8: ((_d = levels.find((level) => level.level === '61.8%')) === null || _d === void 0 ? void 0 : _d.price) || null,
},
},
};
}
const outputData = {
detectionMethods,
results,
};
if (options.returnType === 'metadata') {
outputData.metadata = {
inputLength: ohlcvData.length,
methodsCount: detectionMethods.length,
calculationTimestamp: new Date().toISOString(),
};
}
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];
}
}
exports.SupportResistanceDetector = SupportResistanceDetector;
//# sourceMappingURL=SupportResistanceDetector.node.js.map