@quantform/core 0.3.214 → 0.3.220

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.
Files changed (103) hide show
  1. package/dist/adapter/paper/model/paper-margin-model.d.ts +2 -2
  2. package/dist/adapter/paper/model/paper-margin-model.js +2 -2
  3. package/dist/adapter/paper/model/paper-margin-model.js.map +1 -1
  4. package/dist/adapter/paper/model/paper-model.d.ts +4 -4
  5. package/dist/adapter/paper/model/paper-model.js +26 -12
  6. package/dist/adapter/paper/model/paper-model.js.map +1 -1
  7. package/dist/adapter/paper/model/paper-spot-model.d.ts +3 -2
  8. package/dist/adapter/paper/model/paper-spot-model.js +2 -2
  9. package/dist/adapter/paper/model/paper-spot-model.js.map +1 -1
  10. package/dist/indicator/atr.d.ts +1 -2
  11. package/dist/indicator/atr.js +4 -1
  12. package/dist/indicator/atr.js.map +1 -1
  13. package/dist/indicator/donchian.d.ts +7 -6
  14. package/dist/indicator/donchian.js +6 -15
  15. package/dist/indicator/donchian.js.map +1 -1
  16. package/dist/indicator/ema.d.ts +1 -10
  17. package/dist/indicator/ema.js +13 -32
  18. package/dist/indicator/ema.js.map +1 -1
  19. package/dist/indicator/ema.spec.js +4 -23
  20. package/dist/indicator/ema.spec.js.map +1 -1
  21. package/dist/indicator/envelope.js +4 -8
  22. package/dist/indicator/envelope.js.map +1 -1
  23. package/dist/indicator/index.d.ts +1 -1
  24. package/dist/indicator/index.js +1 -1
  25. package/dist/indicator/index.js.map +1 -1
  26. package/dist/indicator/macd.js +3 -12
  27. package/dist/indicator/macd.js.map +1 -1
  28. package/dist/indicator/min-max.d.ts +4 -11
  29. package/dist/indicator/min-max.js +12 -29
  30. package/dist/indicator/min-max.js.map +1 -1
  31. package/dist/indicator/rma.d.ts +1 -10
  32. package/dist/indicator/rma.js +13 -32
  33. package/dist/indicator/rma.js.map +1 -1
  34. package/dist/indicator/sma.d.ts +1 -10
  35. package/dist/indicator/sma.js +11 -22
  36. package/dist/indicator/sma.js.map +1 -1
  37. package/dist/indicator/sma.spec.js +1 -1
  38. package/dist/indicator/sma.spec.js.map +1 -1
  39. package/dist/indicator/swma.d.ts +1 -9
  40. package/dist/indicator/swma.js +11 -26
  41. package/dist/indicator/swma.js.map +1 -1
  42. package/dist/indicator/tma.d.ts +1 -2
  43. package/dist/indicator/tma.js +4 -1
  44. package/dist/indicator/tma.js.map +1 -1
  45. package/dist/indicator/tma.spec.js +1 -1
  46. package/dist/indicator/tma.spec.js.map +1 -1
  47. package/dist/indicator/truerange.d.ts +1 -1
  48. package/dist/indicator/truerange.js +3 -1
  49. package/dist/indicator/truerange.js.map +1 -1
  50. package/dist/indicator/truerange.spec.js +2 -2
  51. package/dist/indicator/truerange.spec.js.map +1 -1
  52. package/dist/indicator/window.d.ts +3 -0
  53. package/dist/indicator/window.js +22 -0
  54. package/dist/indicator/window.js.map +1 -0
  55. package/dist/indicator/wma.d.ts +1 -11
  56. package/dist/indicator/wma.js +13 -33
  57. package/dist/indicator/wma.js.map +1 -1
  58. package/dist/indicator/wma.spec.js +1 -1
  59. package/dist/indicator/wma.spec.js.map +1 -1
  60. package/dist/session/session-descriptor.d.ts +0 -1
  61. package/dist/session/session-optimizer.js.map +1 -1
  62. package/dist/session/session.d.ts +4 -4
  63. package/dist/session/session.js +13 -13
  64. package/dist/session/session.js.map +1 -1
  65. package/dist/store/event/store-candle.event.d.ts +2 -0
  66. package/dist/store/event/store-candle.event.js +12 -1
  67. package/dist/store/event/store-candle.event.js.map +1 -1
  68. package/dist/store/store.d.ts +2 -1
  69. package/dist/store/store.js +9 -0
  70. package/dist/store/store.js.map +1 -1
  71. package/dist/tsconfig.tsbuildinfo +1 -1
  72. package/package.json +1 -1
  73. package/src/adapter/paper/model/paper-margin-model.ts +5 -5
  74. package/src/adapter/paper/model/paper-model.ts +35 -18
  75. package/src/adapter/paper/model/paper-spot-model.ts +5 -5
  76. package/src/indicator/atr.ts +9 -4
  77. package/src/indicator/donchian.ts +26 -26
  78. package/src/indicator/ema.spec.ts +4 -23
  79. package/src/indicator/ema.ts +15 -42
  80. package/src/indicator/envelope.ts +8 -14
  81. package/src/indicator/index.ts +1 -1
  82. package/src/indicator/macd.ts +8 -19
  83. package/src/indicator/min-max.ts +15 -37
  84. package/src/indicator/rma.ts +15 -43
  85. package/src/indicator/sma.spec.ts +1 -1
  86. package/src/indicator/sma.ts +13 -29
  87. package/src/indicator/swma.ts +14 -35
  88. package/src/indicator/tma.spec.ts +1 -1
  89. package/src/indicator/tma.ts +9 -4
  90. package/src/indicator/truerange.spec.ts +2 -2
  91. package/src/indicator/truerange.ts +15 -10
  92. package/src/indicator/window.ts +27 -0
  93. package/src/indicator/wma.spec.ts +1 -1
  94. package/src/indicator/wma.ts +16 -44
  95. package/src/session/session-descriptor.ts +0 -2
  96. package/src/session/session-optimizer.ts +1 -1
  97. package/src/session/session.ts +19 -14
  98. package/src/store/event/store-candle.event.ts +15 -0
  99. package/src/store/store.ts +10 -0
  100. package/dist/indicator/window-indicator.d.ts +0 -10
  101. package/dist/indicator/window-indicator.js +0 -28
  102. package/dist/indicator/window-indicator.js.map +0 -1
  103. package/src/indicator/window-indicator.ts +0 -35
@@ -1,5 +1,5 @@
1
1
  import { tap } from 'rxjs/operators';
2
- import { Component, InstrumentSelector, Order, Orderbook } from '../../../domain';
2
+ import { Component, InstrumentSelector, Order, Orderbook, Trade } from '../../../domain';
3
3
  import { PaperAdapter } from '..';
4
4
  import { Set } from 'typescript-collections';
5
5
 
@@ -15,35 +15,31 @@ export abstract class PaperModel {
15
15
  protected abstract onOrderCompleted(
16
16
  order: Order,
17
17
  averageExecutionRate: number,
18
- orderbook: Orderbook
18
+ timestamp: number
19
19
  );
20
20
 
21
21
  protected abstract onOrderCanceled(order: Order);
22
22
 
23
23
  private intercept(component: Component) {
24
24
  if (component instanceof Orderbook) {
25
- this.orderbook(component);
25
+ this.pendingOf(component.instrument).forEach(it =>
26
+ this.simulateOrderOnOrderbook(it, component)
27
+ );
28
+ } else if (component instanceof Trade) {
29
+ this.pendingOf(component.instrument).forEach(it =>
30
+ this.simulateOrderOnTrade(it, component)
31
+ );
26
32
  }
27
33
  }
28
34
 
29
- protected orderbook(component: Orderbook) {
30
- const pending = this.pendingOf(component.instrument);
31
-
32
- if (pending.size() == 0) {
33
- return;
34
- }
35
-
36
- pending.forEach(it => this.simulateOrder(it, component));
37
- }
38
-
39
- private simulateOrder(order: Order, orderbook: Orderbook) {
35
+ private simulateOrderOnOrderbook(order: Order, orderbook: Orderbook) {
40
36
  if (order.type == 'MARKET') {
41
37
  if (order.side == 'BUY' && orderbook.bestAskRate) {
42
38
  this.pendingOf(order.instrument).remove(order);
43
- this.onOrderCompleted(order, orderbook.bestAskRate, orderbook);
39
+ this.onOrderCompleted(order, orderbook.bestAskRate, orderbook.timestamp);
44
40
  } else if (order.side == 'SELL' && orderbook.bestBidRate) {
45
41
  this.pendingOf(order.instrument).remove(order);
46
- this.onOrderCompleted(order, orderbook.bestBidRate, orderbook);
42
+ this.onOrderCompleted(order, orderbook.bestBidRate, orderbook.timestamp);
47
43
  }
48
44
 
49
45
  return;
@@ -52,10 +48,31 @@ export abstract class PaperModel {
52
48
  if (order.type == 'LIMIT') {
53
49
  if (order.side == 'BUY' && order.rate >= orderbook.bestAskRate) {
54
50
  this.pendingOf(order.instrument).remove(order);
55
- this.onOrderCompleted(order, orderbook.bestAskRate, orderbook);
51
+ this.onOrderCompleted(order, orderbook.bestAskRate, orderbook.timestamp);
56
52
  } else if (order.side == 'SELL' && order.rate <= orderbook.bestBidRate) {
57
53
  this.pendingOf(order.instrument).remove(order);
58
- this.onOrderCompleted(order, orderbook.bestBidRate, orderbook);
54
+ this.onOrderCompleted(order, orderbook.bestBidRate, orderbook.timestamp);
55
+ }
56
+
57
+ return;
58
+ }
59
+ }
60
+
61
+ private simulateOrderOnTrade(order: Order, trade: Trade) {
62
+ if (order.type == 'MARKET') {
63
+ this.pendingOf(order.instrument).remove(order);
64
+ this.onOrderCompleted(order, trade.rate, trade.timestamp);
65
+
66
+ return;
67
+ }
68
+
69
+ if (order.type == 'LIMIT') {
70
+ if (order.side == 'BUY' && order.rate > trade.rate) {
71
+ this.pendingOf(order.instrument).remove(order);
72
+ this.onOrderCompleted(order, trade.rate, trade.timestamp);
73
+ } else if (order.side == 'SELL' && order.rate < trade.rate) {
74
+ this.pendingOf(order.instrument).remove(order);
75
+ this.onOrderCompleted(order, trade.rate, trade.timestamp);
59
76
  }
60
77
 
61
78
  return;
@@ -28,7 +28,7 @@ export class PaperSpotModel extends PaperModel {
28
28
  this.adapter.store.dispatch(new OrderPendingEvent(order.id, timestamp));
29
29
  }
30
30
 
31
- onOrderCompleted(order: Order, averageExecutionRate: number, orderbook: Orderbook) {
31
+ onOrderCompleted(order: Order, averageExecutionRate: number, timestamp: timestamp) {
32
32
  const instrument =
33
33
  this.adapter.store.snapshot.universe.instrument[order.instrument.toString()];
34
34
  const transacted = {
@@ -53,10 +53,10 @@ export class PaperSpotModel extends PaperModel {
53
53
  }
54
54
 
55
55
  this.adapter.store.dispatch(
56
- ...this.caluclateUnfreezAllocation(order, orderbook.timestamp),
57
- new OrderFilledEvent(order.id, averageExecutionRate, orderbook.timestamp),
58
- new BalanceTransactEvent(instrument.base, transacted.base, orderbook.timestamp),
59
- new BalanceTransactEvent(instrument.quote, transacted.quote, orderbook.timestamp)
56
+ ...this.caluclateUnfreezAllocation(order, timestamp),
57
+ new OrderFilledEvent(order.id, averageExecutionRate, timestamp),
58
+ new BalanceTransactEvent(instrument.base, transacted.base, timestamp),
59
+ new BalanceTransactEvent(instrument.quote, transacted.quote, timestamp)
60
60
  );
61
61
  }
62
62
 
@@ -1,14 +1,19 @@
1
1
  import { Observable } from 'rxjs';
2
- import { share } from 'rxjs/operators';
2
+ import { share, map } from 'rxjs/operators';
3
3
  import { Candle } from '../domain';
4
- import { rma, RMA } from './rma';
4
+ import { rma } from './rma';
5
5
  import { truerange } from './truerange';
6
6
 
7
7
  export function atr<T>(length: number, fn: (it: T) => Candle) {
8
- return function(source: Observable<T>): Observable<RMA> {
8
+ return function (source: Observable<T>): Observable<[T, number]> {
9
9
  return source.pipe(
10
10
  truerange(fn),
11
- rma(length, it => it),
11
+ rma(length, ([, tr]) => tr),
12
+ map(([[it], truerange]) => {
13
+ const tuple: [T, number] = [it, truerange];
14
+
15
+ return tuple;
16
+ }),
12
17
  share()
13
18
  );
14
19
  };
@@ -1,36 +1,36 @@
1
- import { Observable } from 'rxjs';
2
- import { filter, map, share } from 'rxjs/operators';
1
+ import { Observable, withLatestFrom } from 'rxjs';
2
+ import { map, share } from 'rxjs/operators';
3
+ import { minMax } from './min-max';
3
4
  import { Candle } from '../domain';
4
- import { MinMax } from './min-max';
5
5
 
6
6
  export function donchian<T>(length: number, fn: (it: T) => Candle) {
7
- return function (source: Observable<T>): Observable<{
8
- timestamp: number;
9
- upper: number;
10
- middle: number;
11
- lower: number;
12
- }> {
13
- const min = new MinMax(length);
14
- const max = new MinMax(length);
15
- let timestamp = 0;
7
+ return function (source: Observable<T>): Observable<
8
+ [
9
+ T,
10
+ {
11
+ upper: number;
12
+ lower: number;
13
+ }
14
+ ]
15
+ > {
16
+ source = source.pipe(share());
16
17
 
17
18
  return source.pipe(
18
- filter(it => {
19
- const candle = fn(it);
19
+ withLatestFrom(
20
+ source.pipe(minMax(length, it => fn(it).low)),
21
+ source.pipe(minMax(length, it => fn(it).high))
22
+ ),
23
+ map(([it, low, high]) => {
24
+ const tuple: [
25
+ T,
26
+ {
27
+ upper: number;
28
+ lower: number;
29
+ }
30
+ ] = [it, { lower: low[1].min, upper: high[1].max }];
20
31
 
21
- timestamp = candle.timestamp;
22
-
23
- min.append(candle.low);
24
- max.append(candle.high);
25
-
26
- return min.isCompleted && max.isCompleted;
32
+ return tuple;
27
33
  }),
28
- map(() => ({
29
- timestamp,
30
- upper: max.max,
31
- middle: (min.min + max.max) / 2,
32
- lower: min.min
33
- })),
34
34
  share()
35
35
  );
36
36
  };
@@ -6,32 +6,13 @@ describe('ema tests', () => {
6
6
  let value;
7
7
 
8
8
  from([
9
- 212.8,
10
- 214.06,
11
- 213.89,
12
- 214.66,
13
- 213.95,
14
- 213.95,
15
- 214.55,
16
- 214.02,
17
- 214.51,
18
- 213.75,
19
- 214.22,
20
- 213.43,
21
- 214.21,
22
- 213.66,
23
- 215.03,
24
- 216.89,
25
- 216.66,
26
- 216.32,
27
- 214.98,
28
- 214.96,
29
- 215.05,
30
- 215.19
9
+ 212.8, 214.06, 213.89, 214.66, 213.95, 213.95, 214.55, 214.02, 214.51, 213.75,
10
+ 214.22, 213.43, 214.21, 213.66, 215.03, 216.89, 216.66, 216.32, 214.98, 214.96,
11
+ 215.05, 215.19
31
12
  ])
32
13
  .pipe(ema(20, it => it))
33
14
  .subscribe({
34
- next: it => (value = it.value),
15
+ next: ([, it]) => (value = it),
35
16
  complete: () => {
36
17
  expect(parseFloat(value.toFixed(4))).toBe(214.6336);
37
18
  done();
@@ -1,52 +1,25 @@
1
1
  import { Observable } from 'rxjs';
2
- import { filter, map, share } from 'rxjs/operators';
3
- import { SMA } from './sma';
4
- import { WindowIndicator } from './window-indicator';
5
-
6
- export class EMA extends WindowIndicator<number> {
7
- private _alpha = 0;
8
- private _sma: SMA;
9
- private _value = null;
10
-
11
- get value(): number {
12
- return this._value;
13
- }
14
-
15
- constructor(capacity: number) {
16
- super(capacity);
17
-
18
- this._alpha = 2.0 / (capacity + 1);
19
- this._sma = new SMA(capacity);
20
- }
21
-
22
- calculate(added: number) {
23
- this._sma.append(added);
24
-
25
- if (!this._sma.isCompleted) {
26
- return;
27
- }
28
-
29
- if (!this.value) {
30
- this._value = this._sma.value;
31
- } else {
32
- //this._value = (added - this._value) * this._alpha + this._value;
33
-
34
- this._value = this._alpha * added + (1.0 - this._alpha) * this._value;
35
- }
36
- }
37
- }
2
+ import { map, share } from 'rxjs/operators';
3
+ import { sma } from './sma';
38
4
 
39
5
  export function ema<T>(length: number, fn: (it: T) => number) {
40
- return function(source: Observable<T>): Observable<EMA> {
41
- const indicator = new EMA(length);
6
+ return function (source: Observable<T>): Observable<[T, number]> {
7
+ const alpha = 2.0 / (length + 1);
8
+ let value: number = null;
42
9
 
43
10
  return source.pipe(
44
- filter(it => {
45
- indicator.append(fn(it));
11
+ sma(length, fn),
12
+ map(([it, sma]) => {
13
+ if (!value) {
14
+ value = sma;
15
+ } else {
16
+ value = alpha * fn(it) + (1.0 - alpha) * value;
17
+ }
18
+
19
+ const tuple: [T, number] = [it, value];
46
20
 
47
- return indicator.isCompleted;
21
+ return tuple;
48
22
  }),
49
- map(() => indicator),
50
23
  share()
51
24
  );
52
25
  };
@@ -1,23 +1,17 @@
1
1
  import { Observable } from 'rxjs';
2
- import { filter, map, share } from 'rxjs/operators';
3
- import { SMA } from './sma';
2
+ import { map, share } from 'rxjs/operators';
3
+ import { sma } from './sma';
4
4
 
5
5
  export function envelope<T>(length: number, percent: number, valueFn: (it: T) => number) {
6
- return function(source: Observable<T>): Observable<{ min: number; max: number }> {
7
- const indicator = new SMA(length);
8
-
6
+ return function (source: Observable<T>): Observable<{ min: number; max: number }> {
9
7
  return source.pipe(
10
- filter(it => {
11
- indicator.append(valueFn(it));
12
-
13
- return indicator.isCompleted;
14
- }),
15
- map(it => {
16
- const offset = (indicator.value * percent) / 100;
8
+ sma(length, valueFn),
9
+ map(([it, sma]) => {
10
+ const offset = (sma * percent) / 100;
17
11
 
18
12
  return {
19
- min: indicator.value - offset,
20
- max: indicator.value + offset
13
+ min: sma - offset,
14
+ max: sma + offset
21
15
  };
22
16
  }),
23
17
  share()
@@ -10,7 +10,7 @@ export * from './swma';
10
10
  export * from './tma';
11
11
  export * from './trailing';
12
12
  export * from './truerange';
13
- export * from './window-indicator';
13
+ export * from './window';
14
14
  export * from './wma';
15
15
  export * from './donchian';
16
16
  export * from './atr';
@@ -1,6 +1,6 @@
1
- import { Observable } from 'rxjs';
2
- import { filter, map, share } from 'rxjs/operators';
3
- import { EMA } from './ema';
1
+ import { Observable, withLatestFrom } from 'rxjs';
2
+ import { map, share } from 'rxjs/operators';
3
+ import { ema } from './ema';
4
4
 
5
5
  export function macd<T>(
6
6
  fast: number,
@@ -8,24 +8,13 @@ export function macd<T>(
8
8
  length: number,
9
9
  fn: (it: T) => number
10
10
  ) {
11
- return function(source: Observable<T>): Observable<number> {
12
- const ema = {
13
- fast: new EMA(fast),
14
- slow: new EMA(slow),
15
- macd: new EMA(length)
16
- };
11
+ return function (source: Observable<T>): Observable<number> {
12
+ source = source.pipe(share());
17
13
 
18
14
  return source.pipe(
19
- filter(it => {
20
- const value = fn(it);
21
-
22
- ema.fast.append(value);
23
- ema.slow.append(value);
24
- ema.macd.append(ema.fast.value - ema.slow.value);
25
-
26
- return ema.macd.isCompleted;
27
- }),
28
- map(_ => ema.macd.value),
15
+ withLatestFrom(source.pipe(ema(fast, fn)), source.pipe(ema(slow, fn))),
16
+ ema(length, it => it[1][1] - it[2][1]),
17
+ map(([it, macd]) => macd),
29
18
  share()
30
19
  );
31
20
  };
@@ -1,46 +1,24 @@
1
1
  import { Observable } from 'rxjs';
2
2
  import { filter, map, share } from 'rxjs/operators';
3
- import { RingBuffer } from './ring-buffer';
4
- import { WindowIndicator } from './window-indicator';
5
-
6
- export class MinMax extends WindowIndicator<number> {
7
- private _min = 0;
8
- private _max = 0;
9
-
10
- get min(): number {
11
- return this._min;
12
- }
13
-
14
- get max(): number {
15
- return this._max;
16
- }
17
-
18
- constructor(capacity: number) {
19
- super(capacity);
20
- }
21
-
22
- calculate(added: number, removed: number, buffer: RingBuffer<number>) {
23
- this._min = added;
24
- this._max = added;
25
-
26
- buffer.forEach(it => {
27
- this._min = Math.min(it, this._min);
28
- this._max = Math.max(it, this._max);
29
- });
30
- }
31
- }
3
+ import { window } from './window';
32
4
 
33
5
  export function minMax<T>(length: number, fn: (it: T) => number) {
34
- return function(source: Observable<T>): Observable<MinMax> {
35
- const indicator = new MinMax(length);
36
-
6
+ return function (source: Observable<T>): Observable<[T, { min: number; max: number }]> {
37
7
  return source.pipe(
38
- filter(it => {
39
- indicator.append(fn(it));
40
-
41
- return indicator.isCompleted;
8
+ window(length, fn),
9
+ filter(([, buffer]) => buffer.isFull),
10
+ map(([it, buffer]) => {
11
+ let min = 0;
12
+ let max = 0;
13
+
14
+ buffer.forEach(it => {
15
+ min = Math.min(it, min);
16
+ max = Math.max(it, max);
17
+ });
18
+ const tuple: [T, { min: number; max: number }] = [it, { min, max }];
19
+
20
+ return tuple;
42
21
  }),
43
- map(() => indicator),
44
22
  share()
45
23
  );
46
24
  };
@@ -1,52 +1,24 @@
1
1
  import { Observable } from 'rxjs';
2
- import { filter, map, share } from 'rxjs/operators';
3
- import { SMA } from '.';
4
- import { WindowIndicator } from './window-indicator';
5
-
6
- export class RMA extends WindowIndicator<number> {
7
- private _alpha = 0;
8
- private _sma: SMA;
9
- private _value = null;
10
-
11
- get value(): number {
12
- return this._value;
13
- }
14
-
15
- constructor(capacity: number) {
16
- super(capacity);
17
-
18
- this._alpha = 1.0 / capacity;
19
- this._sma = new SMA(capacity);
20
- }
21
-
22
- calculate(added: number) {
23
- this._sma.append(added);
24
-
25
- if (!this._sma.isCompleted) {
26
- return;
27
- }
28
-
29
- if (!this.value) {
30
- this._value = this._sma.value;
31
- } else {
32
- //this._value = (added - this._value) * this._alpha + this._value;
33
-
34
- this._value = this._alpha * added + (1.0 - this._alpha) * this._value;
35
- }
36
- }
37
- }
2
+ import { map, share } from 'rxjs/operators';
3
+ import { sma } from '.';
38
4
 
39
5
  export function rma<T>(length: number, fn: (it: T) => number) {
40
- return function(source: Observable<T>): Observable<RMA> {
41
- const indicator = new RMA(length);
6
+ return function (source: Observable<T>): Observable<[T, number]> {
7
+ const alpha = 1.0 / length;
8
+ let value: number = null;
42
9
 
43
10
  return source.pipe(
44
- filter(it => {
45
- indicator.append(fn(it));
46
-
47
- return indicator.isCompleted;
11
+ sma(length, fn),
12
+ map(([it, sma]) => {
13
+ if (!value) {
14
+ value = sma;
15
+ } else {
16
+ value = alpha * fn(it) + (1.0 - alpha) * value;
17
+ }
18
+
19
+ const tuple: [T, number] = [it, value];
20
+ return tuple;
48
21
  }),
49
- map(() => indicator),
50
22
  share()
51
23
  );
52
24
  };
@@ -9,7 +9,7 @@ describe('sma tests', () => {
9
9
  from([18136.17, 18092.15, 18082.39, 18065.4, 18046.47])
10
10
  .pipe(sma(5, it => it))
11
11
  .subscribe({
12
- next: it => (value = it.value),
12
+ next: ([, it]) => (value = it),
13
13
  complete: () => {
14
14
  expect(parseFloat(value.toFixed(2))).toBe(18084.52);
15
15
  done();
@@ -1,39 +1,23 @@
1
- import { Observable } from 'rxjs';
1
+ import { Observable, tap } from 'rxjs';
2
2
  import { filter, map, share } from 'rxjs/operators';
3
- import { RingBuffer } from './ring-buffer';
4
- import { WindowIndicator } from './window-indicator';
5
-
6
- export class SMA extends WindowIndicator<number> {
7
- private _accmulated = 0;
8
- private _value: number;
9
-
10
- get value(): number {
11
- return this._value;
12
- }
13
-
14
- constructor(capacity: number) {
15
- super(capacity);
16
- }
17
-
18
- calculate(added: number, removed: number, buffer: RingBuffer<number>) {
19
- this._accmulated += added;
20
- this._accmulated -= removed;
21
-
22
- this._value = this._accmulated / buffer.size;
23
- }
24
- }
3
+ import { window } from './window';
25
4
 
26
5
  export function sma<T>(length: number, fn: (it: T) => number) {
27
- return function(source: Observable<T>): Observable<SMA> {
28
- const indicator = new SMA(length);
6
+ return function (source: Observable<T>): Observable<[T, number]> {
7
+ let accumulated = 0;
29
8
 
30
9
  return source.pipe(
31
- filter(it => {
32
- indicator.append(fn(it));
10
+ window(length, fn),
11
+ tap(([, , added, removed]) => {
12
+ accumulated += added;
13
+ accumulated -= removed;
14
+ }),
15
+ filter(([, buffer]) => buffer.isFull),
16
+ map(([it, buffer]) => {
17
+ const tuple: [T, number] = [it, accumulated / buffer.size];
33
18
 
34
- return indicator.isCompleted;
19
+ return tuple;
35
20
  }),
36
- map(_ => indicator),
37
21
  share()
38
22
  );
39
23
  };
@@ -1,44 +1,23 @@
1
1
  import { Observable } from 'rxjs';
2
2
  import { filter, map, share } from 'rxjs/operators';
3
- import { RingBuffer } from './ring-buffer';
4
- import { WindowIndicator } from './window-indicator';
5
-
6
- export class SWMA extends WindowIndicator<number> {
7
- private _value;
8
-
9
- get value(): number {
10
- return this._value;
11
- }
12
-
13
- constructor() {
14
- super(4);
15
- }
16
-
17
- calculate(added: number, removed: number, buffer: RingBuffer<number>) {
18
- if (!this.isCompleted) {
19
- return;
20
- }
21
-
22
- const x3 = buffer.at(this.capacity - (3 + 1));
23
- const x2 = buffer.at(this.capacity - (2 + 1));
24
- const x1 = buffer.at(this.capacity - (1 + 1));
25
- const x0 = buffer.at(this.capacity - (0 + 1));
26
-
27
- this._value = (x3 * 1) / 6 + (x2 * 2) / 6 + (x1 * 2) / 6 + (x0 * 1) / 6;
28
- }
29
- }
3
+ import { window } from './window';
30
4
 
31
5
  export function swma<T>(fn: (it: T) => number) {
32
- return function(source: Observable<T>): Observable<SWMA> {
33
- const indicator = new SWMA();
34
-
6
+ return function (source: Observable<T>): Observable<[T, number]> {
35
7
  return source.pipe(
36
- filter(it => {
37
- indicator.append(fn(it));
38
-
39
- return indicator.isCompleted;
8
+ window(4, fn),
9
+ filter(([, buffer]) => buffer.isFull),
10
+ map(([it, buffer]) => {
11
+ const x3 = buffer.at(buffer.capacity - (3 + 1));
12
+ const x2 = buffer.at(buffer.capacity - (2 + 1));
13
+ const x1 = buffer.at(buffer.capacity - (1 + 1));
14
+ const x0 = buffer.at(buffer.capacity - (0 + 1));
15
+
16
+ const value = (x3 * 1) / 6 + (x2 * 2) / 6 + (x1 * 2) / 6 + (x0 * 1) / 6;
17
+
18
+ const tuple: [T, number] = [it, value];
19
+ return tuple;
40
20
  }),
41
- map(_ => indicator),
42
21
  share()
43
22
  );
44
23
  };
@@ -9,7 +9,7 @@ describe('tma tests', () => {
9
9
  from([18086.36, 18116.15, 18127.81, 18136.17, 18092.15, 18082.39, 18065.4, 18046.47])
10
10
  .pipe(tma(4, it => it))
11
11
  .subscribe({
12
- next: it => (value = it.value),
12
+ next: ([, it]) => (value = it),
13
13
  complete: () => {
14
14
  expect(parseFloat(value.toFixed(2))).toBe(18090.98);
15
15
  done();
@@ -1,13 +1,18 @@
1
1
  import { Observable } from 'rxjs';
2
- import { share } from 'rxjs/operators';
3
- import { SWMA, swma } from './swma';
2
+ import { share, map } from 'rxjs/operators';
3
+ import { swma } from './swma';
4
4
  import { wma } from './wma';
5
5
 
6
6
  export function tma<T>(length: number, fn: (it: T) => number) {
7
- return function(source: Observable<T>): Observable<SWMA> {
7
+ return function (source: Observable<T>): Observable<[T, number]> {
8
8
  return source.pipe(
9
9
  wma(length, fn),
10
- swma(it => it.value),
10
+ swma(([, it]) => it),
11
+ map(([[it], swma]) => {
12
+ const tuple: [T, number] = [it, swma];
13
+
14
+ return tuple;
15
+ }),
11
16
  share()
12
17
  );
13
18
  };