@c9up/technical-indicators-napi
Version:
A Rust-based indicator and Charts library compiled to napi.
139 lines (120 loc) • 6.2 kB
JavaScript
import { test } from '@japa/runner'
import pkg from '../../index.js'
const { extractImportantLevels } = pkg
/**
* extractImportantLevels(data: number[]): ImportantLevels
*
* Returns:
* highestResistance: number — max of resistances union all data
* lowestSupport: number — min of supports union all data
* averagePivot: number — mean of detected pivot points (or mean of data if none)
* supports: number[]
* resistances: number[]
*
* Detection uses a sliding window of WINDOW=5: a point is a local max (resistance)
* if it is >= all 5 neighbours on each side; a local min (support) if <= all 5 neighbours.
*/
/** Generate a simple sine-wave price series with a known range */
function buildSineData(count, amplitude = 20, base = 100) {
return Array.from({ length: count }, (_, i) => base + amplitude * Math.sin(i * (2 * Math.PI / 20)))
}
test.group('ExtractImportantLevels', (group) => {
test('returns an object with all required fields', ({ assert }) => {
const data = buildSineData(50)
const result = extractImportantLevels(data)
assert.property(result, 'highestResistance')
assert.property(result, 'lowestSupport')
assert.property(result, 'averagePivot')
assert.property(result, 'supports')
assert.property(result, 'resistances')
})
test('supports and resistances are arrays', ({ assert }) => {
const data = buildSineData(50)
const result = extractImportantLevels(data)
assert.isArray(result.supports)
assert.isArray(result.resistances)
})
test('highestResistance is >= every value in the input data', ({ assert }) => {
const data = buildSineData(60)
const result = extractImportantLevels(data)
const max = Math.max(...data)
assert.isAtLeast(result.highestResistance, max - 0.0001)
})
test('lowestSupport is <= every value in the input data', ({ assert }) => {
const data = buildSineData(60)
const result = extractImportantLevels(data)
const min = Math.min(...data)
assert.isAtMost(result.lowestSupport, min + 0.0001)
})
test('highestResistance >= lowestSupport', ({ assert }) => {
const data = buildSineData(60)
const result = extractImportantLevels(data)
assert.isAtLeast(result.highestResistance, result.lowestSupport)
})
test('averagePivot is a finite number', ({ assert }) => {
const data = buildSineData(50)
const result = extractImportantLevels(data)
assert.isNumber(result.averagePivot)
assert.isTrue(isFinite(result.averagePivot))
})
test('averagePivot lies between lowestSupport and highestResistance', ({ assert }) => {
const data = buildSineData(80)
const result = extractImportantLevels(data)
assert.isAtLeast(result.averagePivot, result.lowestSupport - 0.0001)
assert.isAtMost(result.averagePivot, result.highestResistance + 0.0001)
})
test('sine wave of 60 points produces at least one support and one resistance', ({ assert }) => {
// A full sine wave has clear local maxima and minima beyond the 5-point window
const data = buildSineData(60)
const result = extractImportantLevels(data)
assert.isAbove(result.supports.length, 0, 'should detect at least one support')
assert.isAbove(result.resistances.length, 0, 'should detect at least one resistance')
})
test('all support values are <= highestResistance and >= lowestSupport', ({ assert }) => {
const data = buildSineData(60)
const result = extractImportantLevels(data)
result.supports.forEach((s, i) => {
assert.isAtMost(s, result.highestResistance + 0.0001, `supports[${i}] must be <= highestResistance`)
assert.isAtLeast(s, result.lowestSupport - 0.0001, `supports[${i}] must be >= lowestSupport`)
})
})
test('all resistance values are <= highestResistance and >= lowestSupport', ({ assert }) => {
const data = buildSineData(60)
const result = extractImportantLevels(data)
result.resistances.forEach((r, i) => {
assert.isAtMost(r, result.highestResistance + 0.0001, `resistances[${i}] must be <= highestResistance`)
assert.isAtLeast(r, result.lowestSupport - 0.0001, `resistances[${i}] must be >= lowestSupport`)
})
})
test('monotone increasing data produces no detected local supports or resistances', ({ assert }) => {
// Strictly monotone has no local extrema so both arrays should be empty
const data = Array.from({ length: 30 }, (_, i) => 100 + i)
const result = extractImportantLevels(data)
assert.lengthOf(result.supports, 0)
assert.lengthOf(result.resistances, 0)
})
test('monotone increasing: averagePivot falls back to mean of input data', ({ assert }) => {
const data = Array.from({ length: 30 }, (_, i) => 100 + i)
const mean = data.reduce((acc, v) => acc + v, 0) / data.length
const result = extractImportantLevels(data)
assert.approximately(result.averagePivot, mean, 0.001)
})
test('highestResistance equals max of data for monotone series', ({ assert }) => {
const data = Array.from({ length: 20 }, (_, i) => 50 + i * 2)
const result = extractImportantLevels(data)
const max = Math.max(...data)
assert.approximately(result.highestResistance, max, 0.001)
})
test('lowestSupport equals min of data for monotone series', ({ assert }) => {
const data = Array.from({ length: 20 }, (_, i) => 50 + i * 2)
const result = extractImportantLevels(data)
const min = Math.min(...data)
assert.approximately(result.lowestSupport, min, 0.001)
})
test('single repeated value: highestResistance and lowestSupport equal that value', ({ assert }) => {
const data = Array.from({ length: 20 }, () => 75.5)
const result = extractImportantLevels(data)
assert.approximately(result.highestResistance, 75.5, 0.001)
assert.approximately(result.lowestSupport, 75.5, 0.001)
})
})