ohlcv-indicators 1.9.2 → 1.9.4
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 +49 -75
- package/src/moving-averages/donchianChannel.js +47 -56
- package/src/moving-averages/ema.js +15 -40
- package/src/moving-averages/macd.js +30 -37
- package/src/moving-averages/relativeVolume.js +31 -39
- package/src/moving-averages/sma.js +15 -35
- package/src/oscillators/rsi.js +38 -50
- package/src/oscillators/volumeOscillator.js +34 -34
- package/src/studies/candleStudies.js +140 -167
- package/src/studies/findCrosses.js +95 -117
- package/src/studies/lag.js +27 -0
- package/src/utilities/parsing-utilities.js +197 -144
- package/src/utilities/setIndicatorsFromInputParams.js +1 -3
- package/src/oscillators/orb.js +0 -41
|
@@ -1,28 +1,62 @@
|
|
|
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
|
-
midPriceOpenClose:
|
|
9
|
-
midPriceHighLow:
|
|
10
|
-
sessionIndex:
|
|
11
|
-
sessionIntradayIndex:
|
|
12
|
-
dayOfTheWeek:
|
|
13
|
-
dayOfTheMonth:
|
|
14
|
-
weekOfTheMonth:
|
|
28
|
+
midPriceOpenClose: true,
|
|
29
|
+
midPriceHighLow: true,
|
|
30
|
+
sessionIndex: true,
|
|
31
|
+
sessionIntradayIndex: true,
|
|
32
|
+
dayOfTheWeek: true,
|
|
33
|
+
dayOfTheMonth: true,
|
|
34
|
+
weekOfTheMonth: true
|
|
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);
|
|
18
44
|
|
|
45
|
+
return {
|
|
46
|
+
day_of_the_week,
|
|
47
|
+
day_of_the_month,
|
|
48
|
+
week_of_the_month
|
|
49
|
+
};
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
export const parseOhlcvToVertical = (input, main, startIndex = 0) => {
|
|
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,189 @@ 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
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
const otherKeys = []
|
|
80
|
+
const vert = main.verticalOhlcv
|
|
81
|
+
|
|
82
|
+
let isValidDate = false
|
|
83
|
+
if (startIndex === 0) {
|
|
84
|
+
// Fresh initialization
|
|
85
|
+
// Initialize arrays for numerical columns
|
|
86
|
+
for (let i = 0; i < numberColsKeys.length; i++) {
|
|
87
|
+
vert[numberColsKeys[i]] = new Array(len)
|
|
88
|
+
}
|
|
70
89
|
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
90
|
+
// Extract other keys and initialize their arrays
|
|
91
|
+
const inputKeys = Object.keys(input[0])
|
|
92
|
+
const otherKeys = []
|
|
93
|
+
for (let i = 0; i < inputKeys.length; i++) {
|
|
94
|
+
const key = inputKeys[i]
|
|
95
|
+
if (!numberColsKeysSet.has(key)) {
|
|
96
|
+
vert[key] = new Array(len)
|
|
97
|
+
otherKeys.push(key)
|
|
98
|
+
}
|
|
75
99
|
}
|
|
76
|
-
|
|
100
|
+
main.otherKeys = otherKeys
|
|
77
101
|
|
|
102
|
+
isValidDate = validateFirstDate(input)
|
|
78
103
|
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
104
|
+
if (isValidDate) {
|
|
105
|
+
if (sessionIndex) {
|
|
106
|
+
vert['session_index'] = new Array(len)
|
|
107
|
+
}
|
|
108
|
+
if (sessionIntradayIndex) {
|
|
109
|
+
vert['session_intraday_index'] = new Array(len)
|
|
110
|
+
}
|
|
111
|
+
if (dayOfTheWeek) {
|
|
112
|
+
vert['day_of_the_week'] = new Array(len)
|
|
113
|
+
}
|
|
114
|
+
if (dayOfTheMonth) {
|
|
115
|
+
vert['day_of_the_month'] = new Array(len)
|
|
116
|
+
}
|
|
117
|
+
if (weekOfTheMonth) {
|
|
118
|
+
vert['week_of_the_month'] = new Array(len)
|
|
119
|
+
}
|
|
83
120
|
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
121
|
+
main.prevDateStr = input[0].date.slice(0, 10)
|
|
122
|
+
main.sessionIndexCount = 0
|
|
123
|
+
main.sessionIntradayIndexCount = 0
|
|
124
|
+
main.cachedDayInfo = getDateInfo(main.prevDateStr)
|
|
125
|
+
} else {
|
|
126
|
+
// If no valid date, ensure these are null
|
|
127
|
+
main.prevDateStr = null
|
|
128
|
+
main.sessionIndexCount = null
|
|
129
|
+
main.sessionIntradayIndexCount = null
|
|
130
|
+
main.cachedDayInfo = null
|
|
89
131
|
}
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
132
|
+
} else {
|
|
133
|
+
// startIndex > 0
|
|
134
|
+
// We assume arrays and keys are already set
|
|
135
|
+
if (!main.otherKeys) {
|
|
136
|
+
throw new Error("otherKeys not found in main. Ensure you run with startIndex=0 first to initialize.")
|
|
94
137
|
}
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
{
|
|
98
|
-
|
|
99
|
-
}
|
|
100
|
-
|
|
101
|
-
if(dayOfTheMonth)
|
|
102
|
-
{
|
|
103
|
-
verticalOhlcv['day_of_the_month'] = new Array(len)
|
|
138
|
+
|
|
139
|
+
// Validate that state variables needed are present
|
|
140
|
+
if (!main.hasOwnProperty('prevDateStr')) {
|
|
141
|
+
throw new Error("prevDateStr missing in main. Make sure to store state after initial run.")
|
|
104
142
|
}
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
143
|
+
|
|
144
|
+
// If we had a valid date initially, we continue with date computations
|
|
145
|
+
isValidDate = main.prevDateStr !== null
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
// Pre-process indicator params
|
|
149
|
+
const indicatorCalls = []
|
|
150
|
+
if (inputParams && typeof inputParams === 'object') {
|
|
151
|
+
for (const key in inputParams) {
|
|
152
|
+
if (key === 'crossPairs') continue
|
|
153
|
+
const technical = inputParams[key]
|
|
154
|
+
if (Array.isArray(technical)) {
|
|
155
|
+
for (let i = 0; i < technical.length; i++) {
|
|
156
|
+
const params = technical[i];
|
|
157
|
+
if (Array.isArray(params)) {
|
|
158
|
+
indicatorCalls.push({
|
|
159
|
+
fn: indicatorFunctions[key],
|
|
160
|
+
args: params
|
|
161
|
+
})
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
}
|
|
109
165
|
}
|
|
110
|
-
|
|
111
|
-
prevDateStr = input[0].date.slice(0, 10)
|
|
112
166
|
}
|
|
113
|
-
|
|
114
|
-
for (let x = 0; x < len; x++) {
|
|
115
|
-
const current = input[x]
|
|
116
167
|
|
|
168
|
+
// Store references for numeric arrays to avoid repeated dictionary lookups
|
|
169
|
+
const vo_open = vert.open
|
|
170
|
+
const vo_high = vert.high
|
|
171
|
+
const vo_low = vert.low
|
|
172
|
+
const vo_close = vert.close
|
|
173
|
+
const vo_volume = vert.volume
|
|
174
|
+
const vo_mid_oc = midPriceOpenClose ? vert.mid_price_open_close : null
|
|
175
|
+
const vo_mid_hl = midPriceHighLow ? vert.mid_price_high_low : null
|
|
176
|
+
|
|
177
|
+
// References for other keys arrays
|
|
178
|
+
const otherArrays = {}
|
|
179
|
+
for (let i = 0; i < main.otherKeys.length; i++) {
|
|
180
|
+
const k = main.otherKeys[i]
|
|
181
|
+
otherArrays[k] = vert[k]
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
// For date/session logic, use previously stored state if startIndex > 0
|
|
185
|
+
let prevDateStr = main.prevDateStr
|
|
186
|
+
let sessionIndexCount = main.sessionIndexCount
|
|
187
|
+
let sessionIntradayIndexCount = main.sessionIntradayIndexCount
|
|
188
|
+
let cachedDayInfo = main.cachedDayInfo
|
|
117
189
|
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
verticalOhlcv.close[x] = current.close
|
|
122
|
-
verticalOhlcv.volume[x] = current.volume
|
|
190
|
+
// Compute indicators and fill data from startIndex to len
|
|
191
|
+
for (let x = startIndex; x < len; x++) {
|
|
192
|
+
const current = input[x]
|
|
123
193
|
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
194
|
+
// Assign OHLCV
|
|
195
|
+
vo_open[x] = current.open
|
|
196
|
+
vo_high[x] = current.high
|
|
197
|
+
vo_low[x] = current.low
|
|
198
|
+
vo_close[x] = current.close
|
|
199
|
+
vo_volume[x] = current.volume
|
|
200
|
+
|
|
201
|
+
// Run indicator functions
|
|
202
|
+
for (let i = 0; i < indicatorCalls.length; i++) {
|
|
203
|
+
const { fn, args } = indicatorCalls[i]
|
|
204
|
+
fn(main, x, ...args)
|
|
127
205
|
}
|
|
128
206
|
|
|
129
|
-
|
|
130
|
-
{
|
|
131
|
-
|
|
207
|
+
// Mid-price calculations
|
|
208
|
+
if (vo_mid_oc) {
|
|
209
|
+
vo_mid_oc[x] = (current.open + current.close) / 2
|
|
132
210
|
}
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
for (const key of otherKeys) {
|
|
136
|
-
verticalOhlcv[key][x] = current[key]
|
|
211
|
+
if (vo_mid_hl) {
|
|
212
|
+
vo_mid_hl[x] = (current.high + current.low) / 2
|
|
137
213
|
}
|
|
138
214
|
|
|
139
|
-
//
|
|
215
|
+
// Other keys
|
|
216
|
+
for (let i = 0; i < main.otherKeys.length; i++) {
|
|
217
|
+
const k = main.otherKeys[i]
|
|
218
|
+
otherArrays[k][x] = current[k]
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
// Date processing for session indices and date fields
|
|
140
222
|
if (isValidDate) {
|
|
141
223
|
const thisDateStr = current.date.slice(0, 10)
|
|
142
|
-
|
|
143
224
|
if (thisDateStr !== prevDateStr) {
|
|
144
225
|
prevDateStr = thisDateStr
|
|
145
226
|
sessionIndexCount++
|
|
146
227
|
sessionIntradayIndexCount = 0
|
|
228
|
+
cachedDayInfo = getDateInfo(thisDateStr)
|
|
147
229
|
}
|
|
148
230
|
|
|
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
|
|
231
|
+
if (dayOfTheWeek) {
|
|
232
|
+
vert.day_of_the_week[x] = cachedDayInfo.day_of_the_week
|
|
158
233
|
}
|
|
159
|
-
|
|
160
|
-
if(dayOfTheMonth)
|
|
161
|
-
|
|
162
|
-
verticalOhlcv.day_of_the_month[x] = day_of_the_month
|
|
234
|
+
|
|
235
|
+
if (dayOfTheMonth) {
|
|
236
|
+
vert.day_of_the_month[x] = cachedDayInfo.day_of_the_month
|
|
163
237
|
}
|
|
164
|
-
|
|
165
|
-
if(weekOfTheMonth)
|
|
166
|
-
|
|
167
|
-
verticalOhlcv.week_of_the_month[x] = week_of_the_month
|
|
238
|
+
|
|
239
|
+
if (weekOfTheMonth) {
|
|
240
|
+
vert.week_of_the_month[x] = cachedDayInfo.week_of_the_month
|
|
168
241
|
}
|
|
169
242
|
|
|
170
|
-
if(sessionIndex)
|
|
171
|
-
|
|
172
|
-
verticalOhlcv.session_index[x] = sessionIndexCount
|
|
243
|
+
if (sessionIndex) {
|
|
244
|
+
vert.session_index[x] = sessionIndexCount
|
|
173
245
|
}
|
|
174
|
-
|
|
175
|
-
if(sessionIntradayIndex)
|
|
176
|
-
|
|
177
|
-
verticalOhlcv.session_intraday_index[x] = sessionIntradayIndexCount
|
|
246
|
+
|
|
247
|
+
if (sessionIntradayIndex) {
|
|
248
|
+
vert.session_intraday_index[x] = sessionIntradayIndexCount
|
|
178
249
|
}
|
|
179
250
|
|
|
180
251
|
sessionIntradayIndexCount++
|
|
181
252
|
}
|
|
182
253
|
}
|
|
183
254
|
|
|
184
|
-
|
|
255
|
+
// Store current state so that we can continue from here in subsequent calls
|
|
256
|
+
main.prevDateStr = prevDateStr
|
|
257
|
+
main.sessionIndexCount = sessionIndexCount
|
|
258
|
+
main.sessionIntradayIndexCount = sessionIntradayIndexCount
|
|
259
|
+
main.cachedDayInfo = cachedDayInfo
|
|
185
260
|
}
|
|
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
|
-
}
|