ohlcv-indicators 1.9.2 → 1.9.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/ohlcv-indicators.min.js +1 -1
- package/index.js +70 -142
- package/package.json +1 -1
- package/src/moving-averages/bollingerBands.js +42 -80
- package/src/moving-averages/donchianChannel.js +43 -56
- package/src/moving-averages/ema.js +15 -40
- package/src/moving-averages/macd.js +43 -46
- package/src/moving-averages/relativeVolume.js +28 -39
- package/src/moving-averages/sma.js +15 -35
- package/src/oscillators/rsi.js +34 -52
- package/src/oscillators/volumeOscillator.js +32 -36
- package/src/studies/candleStudies.js +156 -168
- package/src/studies/lag.js +27 -0
- package/src/utilities/parsing-utilities.js +143 -125
- package/src/utilities/setIndicatorsFromInputParams.js +1 -3
- package/src/oscillators/orb.js +0 -41
|
@@ -1,8 +1,28 @@
|
|
|
1
|
+
import { rsi } from "../oscillators/rsi.js"
|
|
2
|
+
import { sma } from "../moving-averages/sma.js"
|
|
3
|
+
import { ema } from "../moving-averages/ema.js"
|
|
4
|
+
import { macd } from "../moving-averages/macd.js"
|
|
5
|
+
import { relativeVolume } from "../moving-averages/relativeVolume.js"
|
|
6
|
+
import { donchianChannels } from "../moving-averages/donchianChannel.js"
|
|
7
|
+
import { bollingerBands } from "../moving-averages/bollingerBands.js"
|
|
8
|
+
import { volumeOscillator } from "../oscillators/volumeOscillator.js"
|
|
9
|
+
import { candlesStudies } from "../studies/candleStudies.js"
|
|
10
|
+
import { lag } from "../studies/lag.js"
|
|
11
|
+
|
|
12
|
+
const indicatorFunctions = {
|
|
13
|
+
rsi,
|
|
14
|
+
sma,
|
|
15
|
+
ema,
|
|
16
|
+
macd,
|
|
17
|
+
relativeVolume,
|
|
18
|
+
donchianChannels,
|
|
19
|
+
bollingerBands,
|
|
20
|
+
volumeOscillator,
|
|
21
|
+
candlesStudies,
|
|
22
|
+
lag
|
|
23
|
+
}
|
|
1
24
|
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
//true if first row date starts with yyyy-mm-dd date
|
|
5
|
-
const validateFirstDate = arr => arr[0].hasOwnProperty('date') && typeof arr[0].date === 'string' && /^\d{4}-\d{2}-\d{2}/.test(arr[0].date)
|
|
25
|
+
const validateFirstDate = (arr) => arr[0].hasOwnProperty('date') && typeof arr[0].date === 'string' && /^\d{4}-\d{2}-\d{2}/.test(arr[0].date)
|
|
6
26
|
|
|
7
27
|
export const defaultStudyOptions = {
|
|
8
28
|
midPriceOpenClose: false,
|
|
@@ -14,15 +34,29 @@ export const defaultStudyOptions = {
|
|
|
14
34
|
weekOfTheMonth: false
|
|
15
35
|
}
|
|
16
36
|
|
|
17
|
-
|
|
37
|
+
const getDateInfo = (dateString) => {
|
|
38
|
+
const date = new Date(dateString);
|
|
39
|
+
const day_of_the_week = date.getDay();
|
|
40
|
+
const day_of_the_month = date.getDate();
|
|
41
|
+
const firstDayOfMonth = new Date(date.getFullYear(), date.getMonth(), 1);
|
|
42
|
+
const dayOffset = firstDayOfMonth.getDay();
|
|
43
|
+
const week_of_the_month = Math.ceil((day_of_the_month + dayOffset) / 7);
|
|
44
|
+
|
|
45
|
+
return {
|
|
46
|
+
day_of_the_week,
|
|
47
|
+
day_of_the_month,
|
|
48
|
+
week_of_the_month
|
|
49
|
+
};
|
|
50
|
+
}
|
|
18
51
|
|
|
52
|
+
export const parseOhlcvToVertical = (input, main) => {
|
|
53
|
+
const { len, studyOptions, inputParams } = main
|
|
19
54
|
|
|
20
|
-
if(
|
|
21
|
-
|
|
22
|
-
for(let
|
|
23
|
-
|
|
24
|
-
if(!defaultStudyOptions.hasOwnProperty(k))
|
|
25
|
-
{
|
|
55
|
+
if (studyOptions) {
|
|
56
|
+
const studyOptionKeys = Object.keys(studyOptions)
|
|
57
|
+
for (let i = 0; i < studyOptionKeys.length; i++) {
|
|
58
|
+
const k = studyOptionKeys[i]
|
|
59
|
+
if (!defaultStudyOptions.hasOwnProperty(k)) {
|
|
26
60
|
throw new Error(`Key ${k} not found in defaultStudyOptions. Available options: ${Object.keys(defaultStudyOptions).join(', ')}`)
|
|
27
61
|
}
|
|
28
62
|
}
|
|
@@ -38,170 +72,154 @@ export const parseOhlcvToVertical = (input, len, studyOptions) => {
|
|
|
38
72
|
weekOfTheMonth
|
|
39
73
|
} = studyOptions
|
|
40
74
|
|
|
41
|
-
let highMax
|
|
42
|
-
let lowMin
|
|
43
|
-
let volMax
|
|
44
|
-
let volMin
|
|
45
|
-
|
|
46
|
-
|
|
47
75
|
const numberColsKeys = ['open', 'high', 'low', 'close', 'volume']
|
|
48
|
-
|
|
49
|
-
if(
|
|
50
|
-
{
|
|
51
|
-
numberColsKeys.push('mid_price_open_close')
|
|
52
|
-
}
|
|
53
|
-
|
|
54
|
-
if(midPriceHighLow)
|
|
55
|
-
{
|
|
56
|
-
numberColsKeys.push('mid_price_high_low')
|
|
57
|
-
}
|
|
76
|
+
if (midPriceOpenClose) numberColsKeys.push('mid_price_open_close')
|
|
77
|
+
if (midPriceHighLow) numberColsKeys.push('mid_price_high_low')
|
|
58
78
|
|
|
59
79
|
const numberColsKeysSet = new Set(numberColsKeys)
|
|
60
|
-
const
|
|
80
|
+
const vert = main.verticalOhlcv
|
|
61
81
|
|
|
62
82
|
// Initialize arrays for numerical columns
|
|
63
|
-
for (
|
|
64
|
-
|
|
83
|
+
for (let i = 0; i < numberColsKeys.length; i++) {
|
|
84
|
+
vert[numberColsKeys[i]] = new Array(len)
|
|
65
85
|
}
|
|
66
86
|
|
|
67
87
|
// Extract other keys and initialize their arrays
|
|
68
88
|
const inputKeys = Object.keys(input[0])
|
|
69
89
|
const otherKeys = []
|
|
70
|
-
|
|
71
|
-
|
|
90
|
+
for (let i = 0; i < inputKeys.length; i++) {
|
|
91
|
+
const key = inputKeys[i]
|
|
72
92
|
if (!numberColsKeysSet.has(key)) {
|
|
73
|
-
|
|
93
|
+
vert[key] = new Array(len)
|
|
74
94
|
otherKeys.push(key)
|
|
75
95
|
}
|
|
76
96
|
}
|
|
77
97
|
|
|
78
|
-
|
|
98
|
+
const isValidDate = validateFirstDate(input)
|
|
79
99
|
let prevDateStr
|
|
80
100
|
let sessionIndexCount = 0
|
|
81
101
|
let sessionIntradayIndexCount = 0
|
|
82
|
-
const isValidDate = validateFirstDate(input)
|
|
83
102
|
|
|
84
|
-
if
|
|
85
|
-
{
|
|
86
|
-
if(sessionIndex)
|
|
87
|
-
|
|
88
|
-
verticalOhlcv['session_index'] = new Array(len)
|
|
103
|
+
// Initialize date-related arrays if necessary
|
|
104
|
+
if (isValidDate) {
|
|
105
|
+
if (sessionIndex) {
|
|
106
|
+
main.verticalOhlcv['session_index'] = new Array(len)
|
|
89
107
|
}
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
{
|
|
93
|
-
verticalOhlcv['session_intraday_index'] = new Array(len)
|
|
108
|
+
if (sessionIntradayIndex) {
|
|
109
|
+
main.verticalOhlcv['session_intraday_index'] = new Array(len)
|
|
94
110
|
}
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
{
|
|
98
|
-
verticalOhlcv['day_of_the_week'] = new Array(len)
|
|
111
|
+
if (dayOfTheWeek) {
|
|
112
|
+
main.verticalOhlcv['day_of_the_week'] = new Array(len)
|
|
99
113
|
}
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
{
|
|
103
|
-
verticalOhlcv['day_of_the_month'] = new Array(len)
|
|
114
|
+
if (dayOfTheMonth) {
|
|
115
|
+
main.verticalOhlcv['day_of_the_month'] = new Array(len)
|
|
104
116
|
}
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
{
|
|
108
|
-
verticalOhlcv['week_of_the_month'] = new Array(len)
|
|
117
|
+
if (weekOfTheMonth) {
|
|
118
|
+
main.verticalOhlcv['week_of_the_month'] = new Array(len)
|
|
109
119
|
}
|
|
110
|
-
|
|
120
|
+
|
|
111
121
|
prevDateStr = input[0].date.slice(0, 10)
|
|
112
122
|
}
|
|
113
|
-
|
|
114
|
-
for (let x = 0; x < len; x++) {
|
|
115
|
-
const current = input[x]
|
|
116
123
|
|
|
124
|
+
// Pre-process indicator params
|
|
125
|
+
const indicatorCalls = []
|
|
126
|
+
if (inputParams && typeof inputParams === 'object') {
|
|
127
|
+
for (const key in inputParams) {
|
|
128
|
+
if (key === 'crossPairs') continue
|
|
129
|
+
const technical = inputParams[key]
|
|
130
|
+
if (Array.isArray(technical)) {
|
|
131
|
+
for (let i = 0; i < technical.length; i++) {
|
|
132
|
+
const params = technical[i];
|
|
133
|
+
if (Array.isArray(params)) {
|
|
134
|
+
indicatorCalls.push({
|
|
135
|
+
fn: indicatorFunctions[key],
|
|
136
|
+
args: params
|
|
137
|
+
})
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
}
|
|
117
143
|
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
144
|
+
// Store references for numeric arrays to avoid repeated dictionary lookups
|
|
145
|
+
const vo_open = vert.open
|
|
146
|
+
const vo_high = vert.high
|
|
147
|
+
const vo_low = vert.low
|
|
148
|
+
const vo_close = vert.close
|
|
149
|
+
const vo_volume = vert.volume
|
|
150
|
+
const vo_mid_oc = midPriceOpenClose ? vert.mid_price_open_close : null
|
|
151
|
+
const vo_mid_hl = midPriceHighLow ? vert.mid_price_high_low : null
|
|
152
|
+
|
|
153
|
+
// References for other keys arrays
|
|
154
|
+
const otherArrays = {}
|
|
155
|
+
for (let i = 0; i < otherKeys.length; i++) {
|
|
156
|
+
const k = otherKeys[i]
|
|
157
|
+
otherArrays[k] = vert[k]
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
// Cached date info for the current date
|
|
161
|
+
let cachedDayInfo = getDateInfo(prevDateStr) // initial date info if needed
|
|
162
|
+
for (let x = 0; x < len; x++) {
|
|
163
|
+
const current = input[x]
|
|
123
164
|
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
165
|
+
// Assign OHLCV
|
|
166
|
+
vo_open[x] = current.open
|
|
167
|
+
vo_high[x] = current.high
|
|
168
|
+
vo_low[x] = current.low
|
|
169
|
+
vo_close[x] = current.close
|
|
170
|
+
vo_volume[x] = current.volume
|
|
171
|
+
|
|
172
|
+
// Run indicator functions
|
|
173
|
+
for (let i = 0; i < indicatorCalls.length; i++) {
|
|
174
|
+
const { fn, args } = indicatorCalls[i]
|
|
175
|
+
fn(main, x, ...args)
|
|
127
176
|
}
|
|
128
177
|
|
|
129
|
-
|
|
130
|
-
{
|
|
131
|
-
|
|
178
|
+
// Mid-price calculations
|
|
179
|
+
if (vo_mid_oc) {
|
|
180
|
+
vo_mid_oc[x] = (current.open + current.close) / 2
|
|
132
181
|
}
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
for (const key of otherKeys) {
|
|
136
|
-
verticalOhlcv[key][x] = current[key]
|
|
182
|
+
if (vo_mid_hl) {
|
|
183
|
+
vo_mid_hl[x] = (current.high + current.low) / 2
|
|
137
184
|
}
|
|
138
185
|
|
|
139
|
-
//
|
|
186
|
+
// Other keys
|
|
187
|
+
for (let i = 0; i < otherKeys.length; i++) {
|
|
188
|
+
const k = otherKeys[i]
|
|
189
|
+
otherArrays[k][x] = current[k]
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
// Date processing for session indices and date fields
|
|
140
193
|
if (isValidDate) {
|
|
141
194
|
const thisDateStr = current.date.slice(0, 10)
|
|
142
|
-
|
|
143
195
|
if (thisDateStr !== prevDateStr) {
|
|
144
196
|
prevDateStr = thisDateStr
|
|
145
197
|
sessionIndexCount++
|
|
146
198
|
sessionIntradayIndexCount = 0
|
|
199
|
+
cachedDayInfo = getDateInfo(thisDateStr)
|
|
147
200
|
}
|
|
148
201
|
|
|
149
|
-
|
|
150
|
-
day_of_the_week
|
|
151
|
-
day_of_the_month,
|
|
152
|
-
week_of_the_month
|
|
153
|
-
} = getDateInfo(thisDateStr)
|
|
154
|
-
|
|
155
|
-
if(dayOfTheWeek)
|
|
156
|
-
{
|
|
157
|
-
verticalOhlcv.day_of_the_week[x] = day_of_the_week
|
|
202
|
+
if (dayOfTheWeek) {
|
|
203
|
+
main.verticalOhlcv.day_of_the_week[x] = cachedDayInfo.day_of_the_week
|
|
158
204
|
}
|
|
159
|
-
|
|
160
|
-
if(dayOfTheMonth)
|
|
161
|
-
|
|
162
|
-
verticalOhlcv.day_of_the_month[x] = day_of_the_month
|
|
205
|
+
|
|
206
|
+
if (dayOfTheMonth) {
|
|
207
|
+
main.verticalOhlcv.day_of_the_month[x] = cachedDayInfo.day_of_the_month
|
|
163
208
|
}
|
|
164
|
-
|
|
165
|
-
if(weekOfTheMonth)
|
|
166
|
-
|
|
167
|
-
verticalOhlcv.week_of_the_month[x] = week_of_the_month
|
|
209
|
+
|
|
210
|
+
if (weekOfTheMonth) {
|
|
211
|
+
main.verticalOhlcv.week_of_the_month[x] = cachedDayInfo.week_of_the_month
|
|
168
212
|
}
|
|
169
213
|
|
|
170
|
-
if(sessionIndex)
|
|
171
|
-
|
|
172
|
-
verticalOhlcv.session_index[x] = sessionIndexCount
|
|
214
|
+
if (sessionIndex) {
|
|
215
|
+
main.verticalOhlcv.session_index[x] = sessionIndexCount
|
|
173
216
|
}
|
|
174
|
-
|
|
175
|
-
if(sessionIntradayIndex)
|
|
176
|
-
|
|
177
|
-
verticalOhlcv.session_intraday_index[x] = sessionIntradayIndexCount
|
|
217
|
+
|
|
218
|
+
if (sessionIntradayIndex) {
|
|
219
|
+
main.verticalOhlcv.session_intraday_index[x] = sessionIntradayIndexCount
|
|
178
220
|
}
|
|
179
221
|
|
|
180
222
|
sessionIntradayIndexCount++
|
|
181
223
|
}
|
|
182
224
|
}
|
|
183
|
-
|
|
184
|
-
return verticalOhlcv
|
|
185
225
|
}
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
const getDateInfo = dateString => {
|
|
189
|
-
const date = new Date(dateString);
|
|
190
|
-
|
|
191
|
-
// Get day of the week (0 for Sunday, 1 for Monday, etc.)
|
|
192
|
-
const day_of_the_week = date.getDay()
|
|
193
|
-
|
|
194
|
-
// Get day of the month
|
|
195
|
-
const day_of_the_month = date.getDate();
|
|
196
|
-
|
|
197
|
-
// Calculate the week of the month
|
|
198
|
-
const firstDayOfMonth = new Date(date.getFullYear(), date.getMonth(), 1);
|
|
199
|
-
const dayOffset = firstDayOfMonth.getDay(); // Day of the week for the 1st day of the month
|
|
200
|
-
const week_of_the_month = Math.ceil((day_of_the_month + dayOffset) / 7);
|
|
201
|
-
|
|
202
|
-
return {
|
|
203
|
-
day_of_the_week,
|
|
204
|
-
day_of_the_month,
|
|
205
|
-
week_of_the_month
|
|
206
|
-
};
|
|
207
|
-
}
|
package/src/oscillators/orb.js
DELETED
|
@@ -1,41 +0,0 @@
|
|
|
1
|
-
import { findCrosses } from "../studies/findCrosses.js"
|
|
2
|
-
|
|
3
|
-
export const orb = main => {
|
|
4
|
-
const {inputOhlcv, len} = main
|
|
5
|
-
const cols = getOrbOfTheFirstCandle(inputOhlcv, len)
|
|
6
|
-
return cols
|
|
7
|
-
}
|
|
8
|
-
|
|
9
|
-
const getOrbOfTheFirstCandle = (inputOhlcv, len) => {
|
|
10
|
-
const orb_high = new Array(len).fill(0)
|
|
11
|
-
const orb_low = new Array(len).fill(0)
|
|
12
|
-
let startIndex = 0
|
|
13
|
-
let currentDate = null
|
|
14
|
-
let firstCandleHigh = 0
|
|
15
|
-
let firstCandleLow = 0
|
|
16
|
-
|
|
17
|
-
for (let i = 0; i < len; i++) {
|
|
18
|
-
const current = inputOhlcv[i]
|
|
19
|
-
|
|
20
|
-
if(i === 0){
|
|
21
|
-
if(!current.hasOwnProperty('date'))
|
|
22
|
-
{
|
|
23
|
-
throw Error(`input ohlcv array must have a date property available in candle datapoint`)
|
|
24
|
-
}
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
const date = current.date.split(' ')[0]
|
|
28
|
-
|
|
29
|
-
if (date !== currentDate) {
|
|
30
|
-
currentDate = date;
|
|
31
|
-
firstCandleHigh = current.high
|
|
32
|
-
firstCandleLow = current.low
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
orb_high[startIndex] = firstCandleHigh
|
|
36
|
-
orb_low[startIndex] = firstCandleLow
|
|
37
|
-
startIndex++
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
return {orb_high, orb_low}
|
|
41
|
-
}
|