|
| 1 | +"use strict"; |
| 2 | +var __importStar = (this && this.__importStar) || function (mod) { |
| 3 | + if (mod && mod.__esModule) return mod; |
| 4 | + var result = {}; |
| 5 | + if (mod != null) for (var k in mod) if (Object.hasOwnProperty.call(mod, k)) result[k] = mod[k]; |
| 6 | + result["default"] = mod; |
| 7 | + return result; |
| 8 | +}; |
| 9 | +var __importDefault = (this && this.__importDefault) || function (mod) { |
| 10 | + return (mod && mod.__esModule) ? mod : { "default": mod }; |
| 11 | +}; |
| 12 | +Object.defineProperty(exports, "__esModule", { value: true }); |
| 13 | +// packages |
| 14 | +const fs = __importStar(require("fs")); |
| 15 | +// parser dataa |
| 16 | +const parser_data_1 = __importDefault(require("./parser_data")); |
| 17 | +class HSTReader { |
| 18 | + /** |
| 19 | + * @param {string} filename Reads headers of specified file and stores into class variables. |
| 20 | + */ |
| 21 | + constructor(filename) { |
| 22 | + // check if file exists |
| 23 | + if (!fs.existsSync(filename)) { |
| 24 | + throw new Error(`Can't find this file: ${filename}`); |
| 25 | + } |
| 26 | + // open file and store file descriptor as variable |
| 27 | + this.fd = fs.openSync(filename, 'r'); |
| 28 | + // verify filesize is larger than header size |
| 29 | + this.filesize = fs.statSync(filename).size; |
| 30 | + if (this.filesize < 148) { |
| 31 | + throw new Error(`Damn, that's a small file. Too small.`); |
| 32 | + } |
| 33 | + // set up buffers |
| 34 | + const version = Buffer.alloc(4); |
| 35 | + const symbol = Buffer.alloc(12); |
| 36 | + const period = Buffer.alloc(4); |
| 37 | + const start = Buffer.alloc(4); |
| 38 | + // scoped byte offset |
| 39 | + let byteOffset = 0; |
| 40 | + // read version number |
| 41 | + fs.readSync(this.fd, version, 0, 4, byteOffset); |
| 42 | + // read into symbol buffer (skip copyright header [+64 bytes]) |
| 43 | + fs.readSync(this.fd, symbol, 0, 12, byteOffset += 64 + 4); |
| 44 | + // read period |
| 45 | + fs.readSync(this.fd, period, 0, 4, byteOffset += 12); |
| 46 | + // read start date from first candle |
| 47 | + fs.readSync(this.fd, start, 0, 4, 148); |
| 48 | + // convert to js types and store in class variables |
| 49 | + this.version = version.readInt32LE(0); |
| 50 | + this.symbol = symbol.toString("utf8"); |
| 51 | + this.period = period.readInt32LE(0); |
| 52 | + this.start = new Date(start.readInt32LE(0) * 1000); |
| 53 | + this.endOfFile = false; |
| 54 | + // set byte offset to end of header-block |
| 55 | + this.candleByteSize = this.version === 400 ? 44 : 60; |
| 56 | + this.byteOffset = 148 - this.candleByteSize; |
| 57 | + this.candleNumber = 0; |
| 58 | + } |
| 59 | + /** |
| 60 | + * @return {boolean} checks if parser supports file version |
| 61 | + */ |
| 62 | + isValidFormat() { |
| 63 | + return Object.keys(parser_data_1.default).indexOf(this.version.toString()) !== -1; |
| 64 | + } |
| 65 | + /** |
| 66 | + * @return {Candle} returns the next candle in the file |
| 67 | + */ |
| 68 | + getNextCandle() { |
| 69 | + if (this.byteOffset + this.candleByteSize <= this.filesize) { |
| 70 | + this.byteOffset += this.candleByteSize; |
| 71 | + this.candleNumber += 1; |
| 72 | + this.endOfFile = false; |
| 73 | + } |
| 74 | + else { |
| 75 | + this.endOfFile = true; |
| 76 | + } |
| 77 | + return this.readCandle(); |
| 78 | + } |
| 79 | + /** |
| 80 | + * @return {Candle} returns the previous candle in the file |
| 81 | + */ |
| 82 | + getPrevCandle() { |
| 83 | + if (this.byteOffset - this.candleByteSize >= 148) { |
| 84 | + this.byteOffset -= this.candleByteSize; |
| 85 | + this.candleNumber -= 1; |
| 86 | + this.endOfFile = false; |
| 87 | + } |
| 88 | + return this.readCandle(); |
| 89 | + } |
| 90 | + /** |
| 91 | + * @param {number} candleNumber finds candle from number |
| 92 | + * @return {Candle} returns specified candle |
| 93 | + */ |
| 94 | + getCandleNumber(candleNumber) { |
| 95 | + this.candleNumber = candleNumber; |
| 96 | + this.byteOffset = 148 + (candleNumber * this.candleByteSize); |
| 97 | + return this.readCandle(); |
| 98 | + } |
| 99 | + /** |
| 100 | + * @param {Date} date finds candle at specified date and time |
| 101 | + * @param {Date=} startDate optional: due to missing candles, we try to find based on most likely position |
| 102 | + * @param {number=} i optional: count attempts to limit search area |
| 103 | + * @return {Candle} returns the candle if found |
| 104 | + */ |
| 105 | + getCandleAt(date, startDate = this.start, i = 0) { |
| 106 | + const candleOffset = (date.getTime() - startDate.getTime()) / (60000 * this.period); |
| 107 | + let candle = this.getCandleNumber(this.candleNumber + candleOffset); |
| 108 | + if (candle.timestamp.getTime() == date.getTime()) { |
| 109 | + return candle; |
| 110 | + } |
| 111 | + else if (i < 50 && candle.timestamp.getTime()) { |
| 112 | + return this.getCandleAt(date, candle.timestamp, i + 1); |
| 113 | + } |
| 114 | + else if (i < 50) { |
| 115 | + candle = this.getCandleNumber(Math.floor(this.candleNumber / 2)); |
| 116 | + return this.getCandleAt(date, candle.timestamp, i + 1); |
| 117 | + } |
| 118 | + else { |
| 119 | + throw new Error("Could not find candle"); |
| 120 | + } |
| 121 | + } |
| 122 | + /** |
| 123 | + * @return {Candle} returns candle at byte position |
| 124 | + */ |
| 125 | + readCandle() { |
| 126 | + if (this.byteOffset < 148) { |
| 127 | + this.byteOffset = 148; |
| 128 | + } |
| 129 | + const parserFunctions = { |
| 130 | + date: (buffer) => { |
| 131 | + let timestamp = buffer.readInt32LE(0); |
| 132 | + timestamp = timestamp < 9999999999 ? timestamp * 1000 : timestamp; |
| 133 | + return new Date(timestamp); |
| 134 | + }, |
| 135 | + double: (buffer) => buffer.readDoubleLE(0), |
| 136 | + i32: (buffer) => buffer.readInt32LE(0), |
| 137 | + undefined: (buffer) => undefined, |
| 138 | + }; |
| 139 | + const candleData = {}; |
| 140 | + Object.entries(parser_data_1.default[this.version]).forEach(([key, data]) => { |
| 141 | + if (typeof data.value !== "undefined") { |
| 142 | + candleData[key] = data.value; |
| 143 | + } |
| 144 | + else { |
| 145 | + candleData[key] = Buffer.alloc(data.size || 0); |
| 146 | + fs.readSync(this.fd, candleData[key], 0, data.size || 0, this.byteOffset + (data.position || 0)); |
| 147 | + candleData[key] = parserFunctions[data.type || "undefined"](candleData[key]); |
| 148 | + } |
| 149 | + }); |
| 150 | + return candleData; |
| 151 | + } |
| 152 | +} |
| 153 | +exports.default = HSTReader; |
| 154 | +module.exports = { |
| 155 | + HSTReader, |
| 156 | +}; |
0 commit comments