Skip to content

Commit

Permalink
fix: statistics all time prices (#1100)
Browse files Browse the repository at this point in the history
  • Loading branch information
alexbarnsley authored Jan 9, 2025
1 parent 59c7a98 commit 91a4b0e
Show file tree
Hide file tree
Showing 7 changed files with 94 additions and 41 deletions.
6 changes: 3 additions & 3 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ jobs:
uses: shivammathur/setup-php@v2
with:
php-version: "8.3"
extensions: mbstring, dom, fileinfo, intl, gd, imagick, bcmath, soap, zip, sqlite, pcov
extensions: mbstring, dom, fileinfo, intl, gd, imagick, bcmath, soap, zip, sqlite, pcov, gmp
coverage: pcov

- name: Cache dependencies
Expand Down Expand Up @@ -66,7 +66,7 @@ jobs:
uses: shivammathur/setup-php@v2
with:
php-version: "8.3"
extensions: mbstring, dom, fileinfo, intl, gd, imagick, bcmath, soap, zip, sqlite, pcov
extensions: mbstring, dom, fileinfo, intl, gd, imagick, bcmath, soap, zip, sqlite, pcov, gmp
coverage: pcov

- name: Install
Expand Down Expand Up @@ -106,7 +106,7 @@ jobs:
uses: shivammathur/setup-php@v2
with:
php-version: "8.3"
extensions: mbstring, dom, fileinfo, intl, gd, imagick, bcmath, soap, zip, sqlite, pcov
extensions: mbstring, dom, fileinfo, intl, gd, imagick, bcmath, soap, zip, sqlite, pcov, gmp
coverage: pcov

- name: Cache dependencies
Expand Down
4 changes: 2 additions & 2 deletions .github/workflows/deps.yml
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ jobs:
uses: shivammathur/setup-php@v2
with:
php-version: "8.3"
extensions: mbstring, dom, fileinfo, intl, gd, imagick, bcmath, soap, zip, sqlite
extensions: mbstring, dom, fileinfo, intl, gd, imagick, bcmath, soap, zip, sqlite, gmp
coverage: none

- name: Update PHP dependencies
Expand Down Expand Up @@ -66,7 +66,7 @@ jobs:
uses: shivammathur/setup-php@v2
with:
php-version: "8.3"
extensions: mbstring, dom, fileinfo, intl, gd, imagick, bcmath, soap, zip, sqlite
extensions: mbstring, dom, fileinfo, intl, gd, imagick, bcmath, soap, zip, sqlite, gmp
coverage: none

- name: Install PHP dependencies
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/ui.yml
Original file line number Diff line number Diff line change
Expand Up @@ -116,7 +116,7 @@ jobs:
uses: shivammathur/setup-php@v2
with:
php-version: "8.3"
extensions: mbstring, dom, fileinfo, intl, gd, imagick, bcmath, soap, zip, sqlite, pcov
extensions: mbstring, dom, fileinfo, intl, gd, imagick, bcmath, soap, zip, sqlite, pcov, gmp
coverage: pcov

- name: Cache dependencies
Expand Down
66 changes: 43 additions & 23 deletions app/Console/Commands/CacheMarketDataStatistics.php
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,8 @@ public function handle(MarketDataProvider $marketDataProvider, StatisticsCache $
$this->cacheMarketCapStats($currency, $allTimeData, $cache);
});

$this->cacheAllTimePrices($currencies, $cache, $crypto);

$this->dispatchEvent(MarketData::class);
}

Expand All @@ -70,41 +72,59 @@ private function cachePriceStats(string $currency, array $allTimeData, array $da
$prices = collect($allTimeData['prices'])
->map(fn ($item) => ['timestamp' => $item[0], 'value' => $item[1]]);

$pricesSorted = $prices->sortBy('value');
$this->cache52WeekPriceStats($currency, $prices, $cache);
$this->cacheDailyPriceStats($currency, $dailyData, $cache);
}

/** @var array{timestamp: int, value: float|null} $priceAtl */
$priceAtl = $pricesSorted->first();
/** @var array{timestamp: int, value: float|null} $priceAth */
$priceAth = $pricesSorted->last();
private function cacheAllTimePrices(Collection $currencies, StatisticsCache $statisticsCache, CryptoDataCache $cryptoCache): void
{
$priceData = $cryptoCache->getPriceData(Network::currency());

if ($priceAtl['value'] !== null) {
if (! $this->hasChanges) {
$existingValue = $cache->getPriceAtl($currency) ?? [];
if (Arr::get($existingValue, 'timestamp') !== $priceAtl['timestamp'] / 1000) {
foreach ($currencies as $currency) {
/** @var float|null $priceAtl */
$priceAtl = Arr::get($priceData, 'market_data.atl.'.strtolower($currency));

/** @var string|null $priceAtlDate */
$priceAtlDate = Arr::get($priceData, 'market_data.atl_date.'.strtolower($currency));

$priceAtlTimestamp = null;
if ($priceAtlDate !== null) {
$priceAtlTimestamp = Carbon::parse($priceAtlDate)->getTimestamp();
}

/** @var float|null $priceAth */
$priceAth = Arr::get($priceData, 'market_data.ath.'.strtolower($currency));

/** @var string|null $priceAtlDate */
$priceAthDate = Arr::get($priceData, 'market_data.ath_date.'.strtolower($currency));

$priceAthTimestamp = null;
if ($priceAthDate !== null) {
$priceAthTimestamp = Carbon::parse($priceAthDate)->getTimestamp();
}

if ($priceAtl !== null && $priceAtlTimestamp !== null) {
$existingValue = $statisticsCache->getPriceAtl($currency) ?? [];
if (Arr::get($existingValue, 'timestamp') !== $priceAtlTimestamp) {
$this->hasChanges = true;
} elseif (Arr::get($existingValue, 'value') !== $priceAtl['value']) {
} elseif (Arr::get($existingValue, 'value') !== floatval($priceAtl)) {
$this->hasChanges = true;
}
}

$cache->setPriceAtl($currency, $priceAtl['timestamp'] / 1000, $priceAtl['value']);
}
$statisticsCache->setPriceAtl($currency, $priceAtlTimestamp, $priceAtl);
}

if ($priceAth['value'] !== null) {
if (! $this->hasChanges) {
$existingValue = $cache->getPriceAth($currency) ?? [];
if (Arr::get($existingValue, 'timestamp') !== $priceAth['timestamp'] / 1000) {
if ($priceAth !== null && $priceAthTimestamp !== null) {
$existingValue = $statisticsCache->getPriceAth($currency) ?? [];
if (Arr::get($existingValue, 'timestamp') !== $priceAthTimestamp) {
$this->hasChanges = true;
} elseif (Arr::get($existingValue, 'value') !== $priceAth['value']) {
} elseif (Arr::get($existingValue, 'value') !== floatval($priceAth)) {
$this->hasChanges = true;
}
}

$cache->setPriceAth($currency, $priceAth['timestamp'] / 1000, $priceAth['value']);
$statisticsCache->setPriceAth($currency, $priceAthTimestamp, $priceAth);
}
}

$this->cache52WeekPriceStats($currency, $prices, $cache);
$this->cacheDailyPriceStats($currency, $dailyData, $cache);
}

private function cache52WeekPriceStats(string $currency, Collection $data, StatisticsCache $cache): void
Expand Down
28 changes: 19 additions & 9 deletions app/Services/Cache/CryptoDataCache.php
Original file line number Diff line number Diff line change
Expand Up @@ -45,11 +45,6 @@ public function setPrices(string $currency, Collection $prices): Collection
return $prices;
}

public function getCache(): TaggedCache
{
return Cache::tags('crypto_compare');
}

// Add caches for volume in all currencies
public function getVolume(string $currency): ?string
{
Expand All @@ -61,14 +56,14 @@ public function setVolume(string $currency, string $volume): void
$this->put(sprintf('volume/%s', $currency), $volume);
}

public function setHistoricalFullResponse(string $source, string $target, array $values): void
public function getPriceData(string $currency): array
{
$this->put(sprintf('historical_full/all_time/%s/%s', $source, $target), $values);
return $this->get(sprintf('price_data/%s', $currency), []);
}

public function setHistoricalHourlyFullResponse(string $source, string $target, array $values): void
public function setPriceData(string $currency, array $data): void
{
$this->put(sprintf('historical_full/hourly/%s/%s', $source, $target), $values);
$this->put(sprintf('price_data/%s', $currency), $data);
}

/**
Expand All @@ -79,11 +74,26 @@ public function getHistoricalFullResponse(string $source, string $target): array
return $this->get(sprintf('historical_full/all_time/%s/%s', $source, $target), []);
}

public function setHistoricalFullResponse(string $source, string $target, array $values): void
{
$this->put(sprintf('historical_full/all_time/%s/%s', $source, $target), $values);
}

/**
* @return array{prices: array{0:int, 1:float}[], market_caps: array{0:int, 1:float}[], total_volumes: array{0:int, 1:float}[]}|array{}
*/
public function getHistoricalHourlyFullResponse(string $source, string $target): array
{
return $this->get(sprintf('historical_full/hourly/%s/%s', $source, $target), []);
}

public function setHistoricalHourlyFullResponse(string $source, string $target, array $values): void
{
$this->put(sprintf('historical_full/hourly/%s/%s', $source, $target), $values);
}

public function getCache(): TaggedCache
{
return Cache::tags('crypto_compare');
}
}
2 changes: 2 additions & 0 deletions app/Services/MarketDataProviders/CoinGecko.php
Original file line number Diff line number Diff line change
Expand Up @@ -102,6 +102,8 @@ public function priceAndPriceChange(string $baseCurrency, Collection $targetCurr

try {
$data = Http::get('https://api.coingecko.com/api/v3/coins/'.Str::lower($baseCurrency))->json();

(new CryptoDataCache())->setPriceData($baseCurrency, $data);
} catch (\Throwable) {
//
}
Expand Down
27 changes: 24 additions & 3 deletions tests/Unit/Console/Commands/CacheMarketDataStatisticsTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@
use App\Facades\Network;
use App\Services\Cache\CryptoDataCache;
use App\Services\Cache\StatisticsCache;
use Carbon\Carbon;
use Illuminate\Support\Arr;
use Illuminate\Support\Facades\Config;
use Illuminate\Support\Facades\Event;

Expand All @@ -25,13 +27,24 @@
$currency = 'USD';
$crypto->setHistoricalFullResponse(Network::currency(), $currency, json_decode(file_get_contents(base_path('tests/fixtures/coingecko/historical_all.json')), true));
$crypto->setHistoricalHourlyFullResponse(Network::currency(), $currency, json_decode(file_get_contents(base_path('tests/fixtures/coingecko/historical_all.json')), true));
$crypto->setPriceData(Network::currency(), json_decode(file_get_contents(base_path('tests/fixtures/coingecko/coin.json')), true));

$priceData = $crypto->getPriceData(Network::currency());

$this->artisan('explorer:cache-market-data-statistics');

expect($cache->getPriceRangeDaily($currency))->toBe(['low' => 0.0339403, 'high' => 10.2219]);
expect($cache->getPriceRange52($currency))->toBe(['low' => 0.23108521034764695, 'high' => 1.795718158629526]);
expect($cache->getPriceAth($currency))->toBe(['timestamp' => 1515542400, 'value' => 10.2219]);
expect($cache->getPriceAtl($currency))->toBe(['timestamp' => 1490140800, 'value' => 0.0339403]);

expect($cache->getPriceAth($currency))->toBe([
'timestamp' => Carbon::parse(Arr::get($priceData, 'market_data.ath_date.usd'))->timestamp,
'value' => Arr::get($priceData, 'market_data.ath.usd'),
]);

expect($cache->getPriceAtl($currency))->toBe([
'timestamp' => Carbon::parse(Arr::get($priceData, 'market_data.atl_date.usd'))->timestamp,
'value' => Arr::get($priceData, 'market_data.atl.usd'),
]);

expect($cache->getVolumeAtl($currency))->toBe(['timestamp' => 1688774400, 'value' => 40548.95038391039]);
expect($cache->getVolumeAth($currency))->toBe(['timestamp' => 1698710400, 'value' => 443956833.91000223]);
Expand Down Expand Up @@ -94,12 +107,12 @@
Event::fake();

Config::set('arkscan.networks.development.canBeExchanged', true);
$cache = new StatisticsCache();
$crypto = new CryptoDataCache();

$currency = 'USD';
$crypto->setHistoricalFullResponse(Network::currency(), $currency, json_decode(file_get_contents(base_path('tests/fixtures/coingecko/historical_all.json')), true));
$crypto->setHistoricalHourlyFullResponse(Network::currency(), $currency, json_decode(file_get_contents(base_path('tests/fixtures/coingecko/historical_all.json')), true));
$crypto->setPriceData(Network::currency(), json_decode(file_get_contents(base_path('tests/fixtures/coingecko/coin.json')), true));

$this->artisan('explorer:cache-market-data-statistics');

Expand All @@ -122,6 +135,7 @@
$currency = 'USD';
$crypto->setHistoricalFullResponse(Network::currency(), $currency, json_decode(file_get_contents(base_path('tests/fixtures/coingecko/historical_all.json')), true));
$crypto->setHistoricalHourlyFullResponse(Network::currency(), $currency, json_decode(file_get_contents(base_path('tests/fixtures/coingecko/historical_all.json')), true));
$crypto->setPriceData(Network::currency(), json_decode(file_get_contents(base_path('tests/fixtures/coingecko/coin.json')), true));

$this->artisan('explorer:cache-market-data-statistics');

Expand Down Expand Up @@ -154,6 +168,7 @@
$currency = 'USD';
$crypto->setHistoricalFullResponse(Network::currency(), $currency, json_decode(file_get_contents(base_path('tests/fixtures/coingecko/historical_all.json')), true));
$crypto->setHistoricalHourlyFullResponse(Network::currency(), $currency, json_decode(file_get_contents(base_path('tests/fixtures/coingecko/historical_all.json')), true));
$crypto->setPriceData(Network::currency(), json_decode(file_get_contents(base_path('tests/fixtures/coingecko/coin.json')), true));

$this->artisan('explorer:cache-market-data-statistics');

Expand Down Expand Up @@ -186,6 +201,7 @@
$currency = 'USD';
$crypto->setHistoricalFullResponse(Network::currency(), $currency, json_decode(file_get_contents(base_path('tests/fixtures/coingecko/historical_all.json')), true));
$crypto->setHistoricalHourlyFullResponse(Network::currency(), $currency, json_decode(file_get_contents(base_path('tests/fixtures/coingecko/historical_all.json')), true));
$crypto->setPriceData(Network::currency(), json_decode(file_get_contents(base_path('tests/fixtures/coingecko/coin.json')), true));

$this->artisan('explorer:cache-market-data-statistics');

Expand Down Expand Up @@ -220,6 +236,7 @@
$currency = 'USD';
$crypto->setHistoricalFullResponse(Network::currency(), $currency, json_decode(file_get_contents(base_path('tests/fixtures/coingecko/historical_all.json')), true));
$crypto->setHistoricalHourlyFullResponse(Network::currency(), $currency, json_decode(file_get_contents(base_path('tests/fixtures/coingecko/historical_all.json')), true));
$crypto->setPriceData(Network::currency(), json_decode(file_get_contents(base_path('tests/fixtures/coingecko/coin.json')), true));

$this->artisan('explorer:cache-market-data-statistics');

Expand Down Expand Up @@ -252,6 +269,7 @@
$currency = 'USD';
$crypto->setHistoricalFullResponse(Network::currency(), $currency, json_decode(file_get_contents(base_path('tests/fixtures/coingecko/historical_all.json')), true));
$crypto->setHistoricalHourlyFullResponse(Network::currency(), $currency, json_decode(file_get_contents(base_path('tests/fixtures/coingecko/historical_all.json')), true));
$crypto->setPriceData(Network::currency(), json_decode(file_get_contents(base_path('tests/fixtures/coingecko/coin.json')), true));

$this->artisan('explorer:cache-market-data-statistics');

Expand Down Expand Up @@ -284,6 +302,7 @@
$currency = 'USD';
$crypto->setHistoricalFullResponse(Network::currency(), $currency, json_decode(file_get_contents(base_path('tests/fixtures/coingecko/historical_all.json')), true));
$crypto->setHistoricalHourlyFullResponse(Network::currency(), $currency, json_decode(file_get_contents(base_path('tests/fixtures/coingecko/historical_all.json')), true));
$crypto->setPriceData(Network::currency(), json_decode(file_get_contents(base_path('tests/fixtures/coingecko/coin.json')), true));

$this->artisan('explorer:cache-market-data-statistics');

Expand Down Expand Up @@ -316,6 +335,7 @@
$currency = 'USD';
$crypto->setHistoricalFullResponse(Network::currency(), $currency, json_decode(file_get_contents(base_path('tests/fixtures/coingecko/historical_all.json')), true));
$crypto->setHistoricalHourlyFullResponse(Network::currency(), $currency, json_decode(file_get_contents(base_path('tests/fixtures/coingecko/historical_all.json')), true));
$crypto->setPriceData(Network::currency(), json_decode(file_get_contents(base_path('tests/fixtures/coingecko/coin.json')), true));

$this->artisan('explorer:cache-market-data-statistics');

Expand Down Expand Up @@ -348,6 +368,7 @@
$currency = 'USD';
$crypto->setHistoricalFullResponse(Network::currency(), $currency, json_decode(file_get_contents(base_path('tests/fixtures/coingecko/historical_all.json')), true));
$crypto->setHistoricalHourlyFullResponse(Network::currency(), $currency, json_decode(file_get_contents(base_path('tests/fixtures/coingecko/historical_all.json')), true));
$crypto->setPriceData(Network::currency(), json_decode(file_get_contents(base_path('tests/fixtures/coingecko/coin.json')), true));

$this->artisan('explorer:cache-market-data-statistics');

Expand Down

0 comments on commit 91a4b0e

Please sign in to comment.