DataOHLCV::dump()   A
last analyzed

Complexity

Conditions 4
Paths 3

Size

Total Lines 22
Code Lines 18

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 20

Importance

Changes 0
Metric Value
eloc 18
dl 0
loc 22
ccs 0
cts 0
cp 0
rs 9.6666
c 0
b 0
f 0
cc 4
nc 3
nop 0
crap 20
1
<?php
2
/**
3
 * Date: 31.10.18
4
 * Time: 15:20
5
 */
6
declare(strict_types=1);
7
8
namespace AlecRabbit;
9
10
use function AlecRabbit\Helpers\bounds;
11
use AlecRabbit\Structures\Trade;
12
use BCMathExtended\BC;
13
14
class DataOHLCV
15
{
16
    protected const DEFAULT_PERIOD_MULTIPLIER = 3;
17
    protected const MAX_PERIOD_MULTIPLIER = 100;
18
    protected const DEFAULT_SIZE = 500;
19
    protected const MAX_SIZE = 1440;
20
    protected const MIN_SIZE = 10;
21
    protected const RESOLUTIONS = RESOLUTIONS;
22
    /** @var array */
23
    protected $current = [];
24
    /** @var array */
25
    protected $timestamps = [];
26
    /** @var array */
27
    protected $opens = [];
28
    /** @var array */
29
    protected $highs = [];
30
    /** @var array */
31
    protected $lows = [];
32
    /** @var array */
33
    protected $closes = [];
34
    /** @var array */
35
    protected $volumes = [];
36
    /** @var array */
37
    protected $proxies = [];
38
    /** @var int */
39
    private $size;
40
    /** @var int */
41
    private $coefficient;
42
    /** @var string */
43
    private $pair;
44
45
    /**
46
     * DataOHLCV constructor.
47
     * @param string $pair
48
     * @param int|null $size
49
     * @param int|null $coefficient
50
     */
51 97
    public function __construct(
52
        string $pair,
53
        ?int $size = null,
54
        ?int $coefficient = null
55
    ) {
56 97
        $this->size =
57 97
            (int)bounds(
58 97
                $size ?? static::DEFAULT_SIZE,
59 97
                static::MIN_SIZE,
60 97
                static::MAX_SIZE
61
            );
62 97
        $this->pair = $pair;
63 97
        $this->coefficient = $coefficient ?? 1;
64 97
    }
65
66
    /**
67
     * @return int
68
     */
69 9
    public function getSize(): int
70
    {
71 9
        return $this->size;
72
    }
73
74 1
    public function hasPeriods(int $resolution, int $periods, int $multiplier = null): bool
75
    {
76
        $multiplier =
77 1
            (int)bounds($multiplier ?? self::DEFAULT_PERIOD_MULTIPLIER, 1, self::MAX_PERIOD_MULTIPLIER);
78
79
        return
80 1
            isset($this->timestamps[$resolution])
81 1
            && (\count($this->timestamps[$resolution]) >= ($periods * $multiplier));
82
    }
83
84 13
    public function addTrade(Trade $trade): void
85
    {
86 13
        $this->addOHLCV(
87 13
            $trade->timestamp,
88 13
            $trade->price,
89 13
            $trade->price,
90 13
            $trade->price,
91 13
            $trade->price,
92 13
            $trade->amount,
93 13
            $trade->side
94
        );
95 13
    }
96
97 13
    public function addOHLCV(
98
        int $timestamp,
99
        float $open,
100
        float $high,
101
        float $low,
102
        float $close,
103
        float $volume,
104
        int $side,
105
        int $resolution = RESOLUTION_01MIN
106
    ): void {
107 13
        $ts = base_timestamp($timestamp, $resolution);
108 13
        if (isset($this->current[$resolution])) {
109 13
            if ($ts > $this->current[$resolution]['timestamp']) {
110 13
                $this->updateLast($resolution);
111 13
                $this->setCurrent($resolution, $ts, $open, $high, $low, $close, $volume);
112 13
            } elseif ($ts === $this->current[$resolution]['timestamp']) {
113 13
                $this->updateCurrent($resolution, $high, $low, $close, $volume);
114 1
            } elseif ($ts < $this->current[$resolution]['timestamp']) {
115 1
                throw new \RuntimeException(
116
                    'Incoming data are in unsorted order. Current timestamp is greater then incoming data\'s.' .
117 1
                    ' (' . $ts . ' < ' . $this->current[$resolution]['timestamp'] . ')'
118
                );
119
            }
120 13
            $this->trim($resolution);
121
        } else {
122 13
            $this->setCurrent($resolution, $ts, $open, $high, $low, $close, $volume);
123
        }
124
125 13
        if ($nextResolution = $this->nextResolution($resolution)) {
126 13
            $this->addOHLCV($timestamp, $open, $high, $low, $close, $volume, $side, $nextResolution);
127
        }
128 13
    }
129
130 13
    private function updateLast(int $resolution): void
131
    {
132 13
        $this->timestamps[$resolution][] = $this->current[$resolution]['timestamp'];
133 13
        $this->opens[$resolution][] = $this->current[$resolution]['opens'];
134 13
        $this->highs[$resolution][] = $this->current[$resolution]['high'];
135 13
        $this->lows[$resolution][] = $this->current[$resolution]['low'];
136 13
        $this->closes[$resolution][] = $this->current[$resolution]['close'];
137 13
        $this->volumes[$resolution][] = $this->current[$resolution]['volume'];
138 13
    }
139
140 13
    private function setCurrent(
141
        int $resolution,
142
        int $timestamp,
143
        float $open,
144
        float $high,
145
        float $low,
146
        float $close,
147
        float $volume
148
    ): void {
149 13
        $this->current[$resolution]['timestamp'] = $timestamp;
150 13
        $this->current[$resolution]['opens'] = $open;
151 13
        $this->current[$resolution]['high'] = $high;
152 13
        $this->current[$resolution]['low'] = $low;
153 13
        $this->current[$resolution]['close'] = $close;
154 13
        $this->current[$resolution]['volume'] = $volume;
155 13
    }
156
157 13
    private function updateCurrent(
158
        int $resolution,
159
        float $high,
160
        float $low,
161
        float $close,
162
        float $volume
163
    ): void {
164 13
        if ($high > $this->current[$resolution]['high']) {
165 11
            $this->current[$resolution]['high'] = $high;
166
        }
167 13
        if ($low < $this->current[$resolution]['low']) {
168 12
            $this->current[$resolution]['low'] = $low;
169
        }
170
171 13
        $this->current[$resolution]['close'] = $close;
172 13
        $this->current[$resolution]['volume'] =
173 13
            (float)BC::add((string)$this->current[$resolution]['volume'], (string)$volume, NORMAL_SCALE);
174 13
    }
175
176 13
    private function trim(int $resolution): void
177
    {
178 13
        if (isset($this->timestamps[$resolution]) && (\count($this->timestamps[$resolution]) > $this->size)) {
179 1
            unset_first($this->timestamps[$resolution]);
180 1
            unset_first($this->opens[$resolution]);
181 1
            unset_first($this->highs[$resolution]);
182 1
            unset_first($this->lows[$resolution]);
183 1
            unset_first($this->closes[$resolution]);
184 1
            unset_first($this->volumes[$resolution]);
185
        }
186 13
    }
187
188 24
    private function nextResolution(int $resolution): ?int
189
    {
190 24
        $key = array_search($resolution, static::RESOLUTIONS, true);
191 24
        if ($key !== false && array_key_exists(++$key, static::RESOLUTIONS)) {
192 23
            return static::RESOLUTIONS[$key];
193
        }
194 14
        return null;
195
    }
196
197
    /**
198
     * @param int $resolution
199
     * @return array
200
     */
201 11
    public function getTimestamps(int $resolution): array
202
    {
203
        return
204 11
            $this->timestamps[$resolution] ?? [];
205
    }
206
207
    /**
208
     * @param int $resolution
209
     * @param bool $useCoefficient
210
     * @return array
211
     */
212 1
    public function getOpens(int $resolution, bool $useCoefficient = false): array
213
    {
214
        return
215 1
            $this->mulArr(
216 1
                $this->opens[$resolution] ?? [],
217 1
                $useCoefficient
218
            );
219
    }
220
221 7
    private function mulArr(array $values, bool $useCoefficient): array
222
    {
223 7
        if ($useCoefficient && $this->coefficient !== 1) {
224 1
            $values = array_map(
225
                function ($v) {
226
                    return
227 1
                        $v * $this->coefficient;
228 1
                },
229 1
                $values
230
            );
231
        }
232
233 7
        return $values;
234
    }
235
236
    /**
237
     * @param int $resolution
238
     * @param bool $useCoefficient
239
     * @return array
240
     */
241 1
    public function getHighs(int $resolution, bool $useCoefficient = false): array
242
    {
243
        return
244 1
            $this->mulArr(
245 1
                $this->highs[$resolution] ?? [],
246 1
                $useCoefficient
247
            );
248
    }
249
250
    /**
251
     * @param int $resolution
252
     * @param bool $useCoefficient
253
     * @return null|float
254
     */
255 1
    public function getLastHigh(int $resolution, bool $useCoefficient = false): ?float
256
    {
257
        return
258 1
            $this->lastElement($this->highs[$resolution] ?? [], $useCoefficient);
259
    }
260
261
    /**
262
     * @param array $element
263
     * @param bool $useCoefficient
264
     * @return null|float
265
     */
266 3
    private function lastElement(array $element, bool $useCoefficient = false): ?float
267
    {
268 3
        if (false !== $lastElement = end($element)) {
269
            return
270 3
                $this->mul(
271 3
                    $lastElement,
272 3
                    $useCoefficient
273
                );
274
        }
275 3
        return null;
276
    }
277
278 8
    private function mul(float $value, bool $useCoefficient): float
279
    {
280 8
        if ($useCoefficient && $this->coefficient !== 1) {
281 2
            $value *= $this->coefficient;
282
        }
283 8
        return $value;
284
    }
285
286
    /**
287
     * @param int $resolution
288
     * @param bool $useCoefficient
289
     * @return array
290
     */
291 1
    public function getLows(int $resolution, bool $useCoefficient = false): array
292
    {
293
        return
294 1
            $this->mulArr(
295 1
                $this->lows[$resolution] ?? [],
296 1
                $useCoefficient
297
            );
298
    }
299
300
    /**
301
     * @param int $resolution
302
     * @param bool $useCoefficient
303
     * @return null|float
304
     */
305 1
    public function getLastLow(int $resolution, bool $useCoefficient = false): ?float
306
    {
307
        return
308 1
            $this->lastElement($this->lows[$resolution] ?? [], $useCoefficient);
309
    }
310
311
    /**
312
     * @param int $resolution
313
     * @param bool $useCoefficient
314
     * @return array
315
     */
316 1
    public function getCloses(int $resolution, bool $useCoefficient = false): array
317
    {
318
        return
319 1
            $this->mulArr(
320 1
                $this->closes[$resolution] ?? [],
321 1
                $useCoefficient
322
            );
323
    }
324
325
    /**
326
     * @param int $resolution
327
     * @param bool $useCoefficient
328
     * @return null|float
329
     */
330 1
    public function getLastClose(int $resolution, bool $useCoefficient = false): ?float
331
    {
332
        return
333 1
            $this->lastElement($this->closes[$resolution] ?? [], $useCoefficient);
334
    }
335
336
    /**
337
     * @param int $resolution
338
     * @return array
339
     */
340 1
    public function getVolumes(int $resolution): array
341
    {
342
        return
343 1
            $this->volumes[$resolution] ?? [];
344
    }
345
346
    /**
347
     * @codeCoverageIgnore
348
     */
349
    public function dump(): void
350
    {
351
        if (\defined('APP_DEBUG') && APP_DEBUG) {
352
            $result = [];
353
            $pair = $this->getPair();
354
            foreach (static::RESOLUTIONS as $resolution) {
355
                $count = \count($this->timestamps[$resolution] ?? []);
356
                $result[] =
357
                    sprintf(
358
                        '%s [%s] %s %s %s %s %s %s %s',
359
                        $this->current[$resolution]['timestamp'],
360
                        RESOLUTION_ALIASES[$resolution],
361
                        $count,
362
                        $pair,
363
                        $this->current[$resolution]['opens'],
364
                        $this->current[$resolution]['high'],
365
                        $this->current[$resolution]['low'],
366
                        $this->current[$resolution]['close'],
367
                        $this->current[$resolution]['volume']
368
                    );
369
            }
370
            dump($result);
371
        }
372
    }
373
374
    /**
375
     * @return string
376
     */
377 57
    public function getPair(): string
378
    {
379 57
        return $this->pair;
380
    }
381
}
382