-
Notifications
You must be signed in to change notification settings - Fork 2
Expand file tree
/
Copy pathMultiTimeframeDataService.php
More file actions
76 lines (62 loc) · 2.67 KB
/
MultiTimeframeDataService.php
File metadata and controls
76 lines (62 loc) · 2.67 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
<?php
namespace Stochastix\Domain\Backtesting\Service;
use Ds\Vector;
use Stochastix\Domain\Common\Enum\OhlcvEnum;
use Stochastix\Domain\Common\Enum\TimeframeEnum;
use Stochastix\Domain\Data\Service\BinaryStorageInterface;
use Symfony\Component\DependencyInjection\Attribute\Autowire;
final readonly class MultiTimeframeDataService implements MultiTimeframeDataServiceInterface
{
public function __construct(
private BinaryStorageInterface $binaryStorage,
#[Autowire('%kernel.project_dir%/data/market')]
private string $baseDataPath,
) {
}
public function loadAndResample(string $symbol, string $exchangeId, Vector $primaryTimestamps, TimeframeEnum $secondaryTimeframe): array
{
$filePath = $this->generateFilePath($exchangeId, $symbol, $secondaryTimeframe->value);
if (!file_exists($filePath)) {
throw new \RuntimeException("Data file for secondary timeframe {$secondaryTimeframe->value} not found at: {$filePath}");
}
$secondaryRecords = iterator_to_array($this->binaryStorage->readRecordsSequentially($filePath));
$emptyVector = new Vector(array_fill(0, count($primaryTimestamps), null));
$emptyData = array_fill_keys(array_column(OhlcvEnum::cases(), 'value'), $emptyVector);
if (empty($secondaryRecords)) {
return $emptyData;
}
$resampledData = [];
foreach (OhlcvEnum::cases() as $case) {
$resampledData[$case->value] = new Vector();
}
$secondaryIndex = 0;
$numSecondaryRecords = count($secondaryRecords);
foreach ($primaryTimestamps as $primaryTs) {
while ($secondaryIndex + 1 < $numSecondaryRecords && $secondaryRecords[$secondaryIndex + 1]['timestamp'] <= $primaryTs) {
++$secondaryIndex;
}
$recordToUse = $secondaryRecords[$secondaryIndex];
if ($primaryTs < $recordToUse['timestamp']) {
foreach (OhlcvEnum::cases() as $case) {
$resampledData[$case->value]->push(null);
}
} else {
foreach (OhlcvEnum::cases() as $case) {
$resampledData[$case->value]->push($recordToUse[$case->value]);
}
}
}
return $resampledData;
}
private function generateFilePath(string $exchangeId, string $symbol, string $timeframe): string
{
$sanitizedSymbol = str_replace('/', '_', $symbol);
return sprintf(
'%s/%s/%s/%s.stchx',
rtrim($this->baseDataPath, '/'),
strtolower($exchangeId),
strtoupper($sanitizedSymbol),
$timeframe
);
}
}