Passed
Push — master ( 34c628...b145a0 )
by Alec
02:16
created

DataOHLCV::nextResolution()   A

Complexity

Conditions 3
Paths 2

Size

Total Lines 7
Code Lines 4

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 5
CRAP Score 3

Importance

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