diff --git a/README.md b/README.md index 5c7bc882..80a931c9 100644 --- a/README.md +++ b/README.md @@ -59,7 +59,7 @@ Options: -i, --instrument Trading instrument -from, --date-from From date (yyyy-mm-dd) -to, --date-to To date (yyyy-mm-dd or 'now') (default: "now") - -t, --timeframe Timeframe aggregation (tick, m1, m5, m15, m30, h1, h4, d1, mn1) (default: "d1") + -t, --timeframe Timeframe aggregation (tick, s1, m1, m5, m15, m30, h1, h4, d1, mn1) (default: "d1") -p, --price-type Price type: (bid, ask) (default: "bid") -utc, --utc-offset UTC offset in minutes (default: 0) -v, --volumes Include volumes (default: false) @@ -118,7 +118,7 @@ const { getHistoricalRates } = require('dukascopy-node'); |`dates`|`Object`|true||An object with a date range| |`dates.from`|

`Date`

`String`

`Number`

|true||Date representing the start of the time range. Can be of Date type, string (e.g. `2021-03-04` or `2021-03-04T00:00:00.000Z`), or timestamp integer (e.g. `1614816000000`)| |`dates.to`|

`Date`

`String`

`Number`

|true||Date representing the end of the time range Can be of Date type, string (e.g. `2021-03-04` or `2021-03-04T00:00:00.000Z`), or timestamp integer (e.g. `1614816000000`)| -|`timeframe`|`String`|false|`d1`|Granularity of aggregation of OHLC (open, high, low, close) data. Supported values:
  • `tick` (every single tick/price change)
  • `m1` (1 minute)
  • `m5` (5 minutes)
  • `m15` (15 minutes)
  • `m30` (30 minutes)
  • `h1` (1 hour)
  • `h4` (4 hours)
  • `d1` (1 day)
  • `mn1` (1 month)
| +|`timeframe`|`String`|false|`d1`|Granularity of aggregation of OHLC (open, high, low, close) data. Supported values:
  • `tick` (every single tick/price change)
  • `s1` (1 second)
  • `m1` (1 minute)
  • `m5` (5 minutes)
  • `m15` (15 minutes)
  • `m30` (30 minutes)
  • `h1` (1 hour)
  • `h4` (4 hours)
  • `d1` (1 day)
  • `mn1` (1 month)
| |`priceType`|`String`|false|`bid`|Type of price (offer side). Supported values:
  • `bid`
  • `ask`
| |`format`|`String`|false|`array`|Format of the generated output. Supported values:
  • `array`
  • `json`
  • `csv`
| |`utcOffset`|`Number`|false|`0`|UTC offset in minutes.| diff --git a/src/aggregator/index.ts b/src/aggregator/index.ts index 010cde0f..92f20829 100644 --- a/src/aggregator/index.ts +++ b/src/aggregator/index.ts @@ -1,4 +1,9 @@ -import { getOHLC, getMinuteOHLCfromTicks, getMonthlyOHLCfromDays } from './ohlc'; +import { + getOHLC, + getMinuteOHLCfromTicks, + getMonthlyOHLCfromDays, + getSecondOHLCfromTicks +} from './ohlc'; import { splitArrayInChunks } from '../utils/general'; import { AggregateInput } from './types'; import { Timeframe } from '../config/timeframes'; @@ -30,54 +35,59 @@ export function aggregate({ ); } else { if (fromTimeframe === Timeframe.tick) { - const minuteOHLC = getMinuteOHLCfromTicks(data, priceType, startTs, volumes); - - if (toTimeframe === Timeframe.m1) { - return minuteOHLC; - } - - if (toTimeframe === Timeframe.m5) { - return splitArrayInChunks(minuteOHLC, 5).map((d, i) => - getOHLC({ - input: d, - filterFlats: ignoreFlats, - startTs: startTs + i * 5 * 1000 * 60, - volumes - }) - ); - } - - if (toTimeframe === Timeframe.m15) { - return splitArrayInChunks(minuteOHLC, 15).map((d, i) => - getOHLC({ - input: d, - filterFlats: ignoreFlats, - startTs: startTs + i * 15 * 1000 * 60, - volumes - }) - ); - } - - if (toTimeframe === Timeframe.m30) { - return splitArrayInChunks(minuteOHLC, 30).map((d, i) => - getOHLC({ - input: d, - filterFlats: ignoreFlats, - startTs: startTs + i * 30 * 1000 * 60, - volumes - }) - ); - } - - if (toTimeframe === Timeframe.h1) { - return [minuteOHLC].map((d, i) => - getOHLC({ - input: d, - filterFlats: ignoreFlats, - startTs: startTs + i * 60 * 1000 * 60, - volumes - }) - ); + if (toTimeframe === Timeframe.s1) { + const secondOHLC = getSecondOHLCfromTicks(data, priceType, startTs, volumes); + return secondOHLC; + } else { + const minuteOHLC = getMinuteOHLCfromTicks(data, priceType, startTs, volumes); + + if (toTimeframe === Timeframe.m1) { + return minuteOHLC; + } + + if (toTimeframe === Timeframe.m5) { + return splitArrayInChunks(minuteOHLC, 5).map((d, i) => + getOHLC({ + input: d, + filterFlats: ignoreFlats, + startTs: startTs + i * 5 * 1000 * 60, + volumes + }) + ); + } + + if (toTimeframe === Timeframe.m15) { + return splitArrayInChunks(minuteOHLC, 15).map((d, i) => + getOHLC({ + input: d, + filterFlats: ignoreFlats, + startTs: startTs + i * 15 * 1000 * 60, + volumes + }) + ); + } + + if (toTimeframe === Timeframe.m30) { + return splitArrayInChunks(minuteOHLC, 30).map((d, i) => + getOHLC({ + input: d, + filterFlats: ignoreFlats, + startTs: startTs + i * 30 * 1000 * 60, + volumes + }) + ); + } + + if (toTimeframe === Timeframe.h1) { + return [minuteOHLC].map((d, i) => + getOHLC({ + input: d, + filterFlats: ignoreFlats, + startTs: startTs + i * 60 * 1000 * 60, + volumes + }) + ); + } } } diff --git a/src/aggregator/ohlc.ts b/src/aggregator/ohlc.ts index 7826e7cd..c3e8e468 100644 --- a/src/aggregator/ohlc.ts +++ b/src/aggregator/ohlc.ts @@ -168,6 +168,26 @@ function getMinuteOHLCfromTicks( return ohlc; } +function getSecondOHLCfromTicks( + ticks: number[][], + priceType: PriceType, + startTs: number, + volumes: boolean +): number[][] { + const ticksBySecond = breakdownByInterval( + ticks, + 3600, + d => d.getUTCMinutes() * 60 + d.getUTCSeconds() + ); + const ohlc = ticksBySecond.map((data, i) => + data.length > 0 + ? ticksToOHLC({ ticks: data, priceType, startTs: startTs + i * 1000, volumes }) + : [] + ); + + return ohlc; +} + function getMonthlyOHLCfromDays(dailyCandles: number[][], volumes: boolean): number[][] { const breakdown = breakdownByInterval(dailyCandles, 12, d => d.getUTCMonth()); const ohlc = breakdown.map(data => (data.length > 0 ? getOHLC({ input: data, volumes }) : [])); @@ -179,6 +199,7 @@ export { getOHLC, ticksToOHLC, breakdownByInterval, + getSecondOHLCfromTicks, getMinuteOHLCfromTicks, getMonthlyOHLCfromDays }; diff --git a/src/config/timeframes.ts b/src/config/timeframes.ts index bb55565d..f46f4666 100644 --- a/src/config/timeframes.ts +++ b/src/config/timeframes.ts @@ -3,6 +3,10 @@ export enum Timeframe { * Every single price change. No aggregation of OHLC price data */ tick = 'tick', + /** + * secondly `(1 second)` aggregation of OHLC price data + */ + s1 = 's1', /** * minutely `(1 minute)` aggregation of OHLC price data */ diff --git a/src/dates-normaliser/index.ts b/src/dates-normaliser/index.ts index 11c97512..9d5f075a 100644 --- a/src/dates-normaliser/index.ts +++ b/src/dates-normaliser/index.ts @@ -28,6 +28,7 @@ export function normaliseDates({ startYearForDailyCandles } = instrumentMetaData[instrument]; + // start for tick and second timeframes let minFromIsoDate = startHourForTicks; if ( diff --git a/src/url-generator/index.ts b/src/url-generator/index.ts index 24d0794f..a12db62a 100644 --- a/src/url-generator/index.ts +++ b/src/url-generator/index.ts @@ -75,7 +75,9 @@ function getDateLimit(startDate: Date, endDate: Date, timeframe: TimeframeType): if ( timeframe === Timeframe.tick || + timeframe === Timeframe.s1 || timeframe === Timeframe.m1 || + timeframe === Timeframe.m5 || timeframe === Timeframe.m15 || timeframe === Timeframe.m30 ) { diff --git a/src/utils/range.ts b/src/utils/range.ts index 848ea343..e5948630 100644 --- a/src/utils/range.ts +++ b/src/utils/range.ts @@ -16,7 +16,8 @@ const rangeInferMap: RangeInferMap = { m15: ['day', 'hour'], m5: ['day', 'hour'], m1: ['day', 'hour'], - tick: ['hour'] + tick: ['hour'], + s1: ['hour'] }; function getLowerRange(range: TimeRange): TimeRange {