forked from EvergreenCrypto/docker-finance
php: fetch: prices: support asset's blockchain(s)
- Related refactoring - Update documentation
This commit is contained in:
@@ -40,15 +40,15 @@ namespace docker_finance\prices\internal
|
|||||||
{
|
{
|
||||||
/**
|
/**
|
||||||
* @brief Get network response data
|
* @brief Get network response data
|
||||||
* @param string $id ID of given symbol ('bitcoin' in 'bitcoin/BTC')
|
* @param array<string> $asset Parsed environment entries of assets
|
||||||
* @param string $timestamp Given year(s) to fetch
|
* @param string $timestamp Given year(s) to fetch
|
||||||
* @return array<mixed> Array of [N]([timestamp][price]) for given year(s)
|
* @return array<int<0, max>, array<string>> Entries of [N][timestamp, price] since given timestamp
|
||||||
*/
|
*/
|
||||||
public function getter(string $id, string $timestamp): array;
|
public function getter(array $asset, string $timestamp): array;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief Prepare price data for given symbols
|
* @brief Prepare price data for given symbols
|
||||||
* @param string $symbols Symbols in 'asset/ticker,...' format ('bitcoin/BTC,ethereum/ETH')
|
* @param string $symbols Unparsed envrionment assets (e.g., 'id/ticker,id/ticker,blockchain:id/ticker,...')
|
||||||
* @return array<int<0, max>, array<string>> Prices for all given symbols
|
* @return array<int<0, max>, array<string>> Prices for all given symbols
|
||||||
*/
|
*/
|
||||||
public function reader(string $symbols): array;
|
public function reader(string $symbols): array;
|
||||||
@@ -89,8 +89,8 @@ namespace docker_finance\prices\internal
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief Parse given string of 'id/symbol'
|
* @brief Parse environment assets
|
||||||
* @param string $symbols Expected format: bitcoin/BTC,ethereum/ETH,litecoin/LTC,etc.
|
* @param string $symbols Expected format: 'id/ticker,id/ticker,blockchain:id/ticker,...' e.g., 'bitcoin/BTC,ethereum/ETH,avalanche:0xb97ef9ef8734c71904d8002f8b6bc66dd9c48a6e/USDC,...'
|
||||||
* @details
|
* @details
|
||||||
*
|
*
|
||||||
* Caveats:
|
* Caveats:
|
||||||
@@ -98,25 +98,24 @@ namespace docker_finance\prices\internal
|
|||||||
* 1. There are multiple reasons why asset must be passed with symbol
|
* 1. There are multiple reasons why asset must be passed with symbol
|
||||||
* instead of symbol alone:
|
* instead of symbol alone:
|
||||||
*
|
*
|
||||||
* a. CoinGecko uses same symbol for multiple ID's:
|
* a. CoinGecko uses same symbol for multiple asset ID's:
|
||||||
*
|
*
|
||||||
* ltc = binance-peg-litecoin
|
* ltc = binance-peg-litecoin
|
||||||
* ltc = litecoin
|
* ltc = litecoin
|
||||||
* eth = ethereum
|
* eth = ethereum
|
||||||
* eth = ethereum-wormhole
|
* eth = ethereum-wormhole
|
||||||
*
|
*
|
||||||
* So, pass ID instead of symbol!
|
* b. Mobula will often require blockchain along with asset ID
|
||||||
*
|
* NOTE: with Mobula, ID can also consist of a contract address
|
||||||
* b. Mobula support will require an asset name along with symbol
|
|
||||||
*
|
*
|
||||||
* 2. Ticker-symbol comes *AFTER* ID because hledger's prices are:
|
* 2. Ticker-symbol comes *AFTER* ID because hledger's prices are:
|
||||||
*
|
*
|
||||||
* a. *CASE SENSITIVE*
|
* a. *CASE SENSITIVE*
|
||||||
*
|
*
|
||||||
* b *WILL NOT UNDERSTAND THE DIFFERENCE BETWEEN (for example):
|
* b. Will not understand the difference between (for example):
|
||||||
* aGUSD and AGUSD* (CoinGecko will return lowercase symbol)
|
* aGUSD and AGUSD (CoinGecko will return lowercase symbol)
|
||||||
*
|
*
|
||||||
* @return array<string> Array of symbols
|
* @return array<array<string>> Parsed environment entries
|
||||||
*/
|
*/
|
||||||
protected function parse_symbols(string $symbols): array
|
protected function parse_symbols(string $symbols): array
|
||||||
{
|
{
|
||||||
@@ -126,13 +125,23 @@ namespace docker_finance\prices\internal
|
|||||||
utils\CLI::throw_fatal("malformed symbols format");
|
utils\CLI::throw_fatal("malformed symbols format");
|
||||||
}
|
}
|
||||||
|
|
||||||
$list = explode(',', $symbols);
|
|
||||||
$parsed = [];
|
$parsed = [];
|
||||||
|
$csv = explode(',', $symbols);
|
||||||
|
|
||||||
foreach ($list as $coin) {
|
for ($i = 0; $i < count($csv); $i++) {
|
||||||
list($key, $value) = explode('/', $coin);
|
list($asset, $ticker) = explode('/', $csv[$i]);
|
||||||
$parsed[$key] = $value;
|
|
||||||
|
$id = $asset;
|
||||||
|
$blockchain = "";
|
||||||
|
if (str_contains($id, ':')) {
|
||||||
|
list($blockchain, $id) = explode(':', $asset);
|
||||||
|
}
|
||||||
|
|
||||||
|
$parsed[$i]['id'] = $id;
|
||||||
|
$parsed[$i]['ticker'] = $ticker;
|
||||||
|
$parsed[$i]['blockchain'] = $blockchain;
|
||||||
}
|
}
|
||||||
|
|
||||||
return $parsed;
|
return $parsed;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -182,8 +191,13 @@ namespace docker_finance\prices\internal
|
|||||||
return $decoded;
|
return $decoded;
|
||||||
}
|
}
|
||||||
|
|
||||||
//! @brief Impl-specific REST API request
|
/**
|
||||||
abstract protected function request(string $id, string $timestamp): mixed;
|
* @brief Impl-specific REST API request generator
|
||||||
|
* @param array<mixed> $asset Parsed environment asset entries to request
|
||||||
|
* @param string $timestamp Timestamp of entries to request
|
||||||
|
* @return mixed REST API raw response price data
|
||||||
|
*/
|
||||||
|
abstract protected function request(array $asset, string $timestamp): mixed;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief Make impl-specific timestamp requirement
|
* @brief Make impl-specific timestamp requirement
|
||||||
@@ -250,15 +264,27 @@ namespace docker_finance\prices\internal
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief Parse fetched date and prices
|
* @brief Parse fetched date and prices
|
||||||
* @param array<mixed> $prices Fetched prices [N]([timestamp][price])
|
* @param array<mixed> $prices Fetched prices [N][timestamp, price]
|
||||||
* @return array<string> Date and prices without ID or symbol
|
* @details
|
||||||
|
* $prices Expectation:
|
||||||
|
*
|
||||||
|
* array[0] = oldest entry<br>
|
||||||
|
* array[0][0] = timestamp<br>
|
||||||
|
* array[0][1] = price<br>
|
||||||
|
*
|
||||||
|
* array[1] = next hour<br>
|
||||||
|
* array[1][0] = timestamp<br>
|
||||||
|
* array[1][1] = price<br>
|
||||||
|
*
|
||||||
|
* ...etc.
|
||||||
|
* @return array<string> Date and price single-line entries without ID or symbol
|
||||||
*/
|
*/
|
||||||
abstract protected function parse_prices(array $prices): array;
|
abstract protected function parse_prices(array $prices): array;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief Create data for master price journal file
|
* @brief Create data for master price journal file
|
||||||
* @param string $symbol Given symbol associated with ID
|
* @param string $symbol Given symbol associated with ID
|
||||||
* @param array<mixed> $prices Array of [N]([timestamp][price])for given year(s)
|
* @param array<mixed> $prices Array of [N][timestamp, price] for given year(s)
|
||||||
* @return array<string> Master price journal file data
|
* @return array<string> Master price journal file data
|
||||||
*/
|
*/
|
||||||
protected function make_master(string $symbol, array $prices): array
|
protected function make_master(string $symbol, array $prices): array
|
||||||
@@ -314,7 +340,7 @@ namespace docker_finance\prices\internal
|
|||||||
return $stack;
|
return $stack;
|
||||||
}
|
}
|
||||||
|
|
||||||
public function getter(string $id, string $timestamp): array
|
public function getter(array $asset, string $timestamp): array
|
||||||
{
|
{
|
||||||
$response = [];
|
$response = [];
|
||||||
$timer = 60; // seconds
|
$timer = 60; // seconds
|
||||||
@@ -322,14 +348,14 @@ namespace docker_finance\prices\internal
|
|||||||
|
|
||||||
while (!$success) {
|
while (!$success) {
|
||||||
try {
|
try {
|
||||||
$response = $this->request($id, $timestamp);
|
$response = $this->request($asset, $timestamp);
|
||||||
$success = true; // Should throw before this is assigned, alla C++
|
$success = true; // Should throw before this is assigned, alla C++
|
||||||
} catch (\Throwable $e) {
|
} catch (\Throwable $e) {
|
||||||
$code = $e->getCode();
|
$code = $e->getCode();
|
||||||
$message = $e->getMessage();
|
$message = $e->getMessage();
|
||||||
|
|
||||||
utils\CLI::print_warning(
|
utils\CLI::print_warning(
|
||||||
"server sent error '{$message}' with code '{$code}' for '{$id}'"
|
"server sent error '{$message}' with code '{$code}' for '{$asset['id']}'"
|
||||||
. " - retrying in '{$timer}' seconds"
|
. " - retrying in '{$timer}' seconds"
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -364,9 +390,9 @@ namespace docker_finance\prices\internal
|
|||||||
|
|
||||||
utils\CLI::print_normal(" ─ Symbols");
|
utils\CLI::print_normal(" ─ Symbols");
|
||||||
|
|
||||||
foreach ($this->parse_symbols($symbols) as $id => $symbol) {
|
foreach ($this->parse_symbols($symbols) as $asset) {
|
||||||
$parsed = $this->parse_prices($this->getter($id, $timestamp));
|
$parsed = $this->parse_prices($this->getter($asset, $timestamp));
|
||||||
$master = $this->make_master($symbol, $this->make_average($parsed));
|
$master = $this->make_master($asset['ticker'], $this->make_average($parsed));
|
||||||
|
|
||||||
utils\CLI::print_debug($master);
|
utils\CLI::print_debug($master);
|
||||||
array_push($stack, $master);
|
array_push($stack, $master);
|
||||||
|
|||||||
@@ -44,13 +44,7 @@ namespace docker_finance\prices\internal\prices\crypto
|
|||||||
parent::__construct($env);
|
parent::__construct($env);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
protected function request(array $asset, string $timestamp): mixed
|
||||||
* @brief REST API request generator
|
|
||||||
* @param string $id Symbol's ID to request
|
|
||||||
* @param string $timestamp Timestamp to request
|
|
||||||
* @return mixed REST API response data
|
|
||||||
*/
|
|
||||||
protected function request(string $id, string $timestamp): mixed
|
|
||||||
{
|
{
|
||||||
// If `key` exists, use Pro API (otherwise, use Public API)
|
// If `key` exists, use Pro API (otherwise, use Public API)
|
||||||
$key = $this->get_env()->get_env('API_PRICES_KEY');
|
$key = $this->get_env()->get_env('API_PRICES_KEY');
|
||||||
@@ -61,7 +55,7 @@ namespace docker_finance\prices\internal\prices\crypto
|
|||||||
$header = ["x-cg-pro-api-key: $key"];
|
$header = ["x-cg-pro-api-key: $key"];
|
||||||
}
|
}
|
||||||
$vs_currency = 'usd';
|
$vs_currency = 'usd';
|
||||||
$url = "https://{$domain}/api/v3/coins/{$id}/market_chart?vs_currency={$vs_currency}&days={$timestamp}";
|
$url = "https://{$domain}/api/v3/coins/{$asset['id']}/market_chart?vs_currency={$vs_currency}&days={$timestamp}";
|
||||||
|
|
||||||
$response = $this->request_impl($url, $header);
|
$response = $this->request_impl($url, $header);
|
||||||
if (array_key_exists('status', $response)) {
|
if (array_key_exists('status', $response)) {
|
||||||
@@ -76,19 +70,9 @@ namespace docker_finance\prices\internal\prices\crypto
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief Parse fetched date and prices
|
|
||||||
* @param array<mixed> $prices Fetched prices [N]([timestamp][price])
|
|
||||||
* @details
|
* @details
|
||||||
*
|
|
||||||
* Parses historical market data include price, market cap
|
* Parses historical market data include price, market cap
|
||||||
* and 24h volume (granularity auto)
|
* and 24h volume (granularity auto)
|
||||||
*
|
|
||||||
* - Data granularity is automatic (cannot be adjusted)
|
|
||||||
* - 1 day from current time = 5 minute interval data
|
|
||||||
* - 1 - 90 days from current time = hourly data
|
|
||||||
* above 90 days from current time = daily data (00:00 UTC)
|
|
||||||
*
|
|
||||||
* @return array<string> Date and prices without ID or symbol
|
|
||||||
*/
|
*/
|
||||||
protected function parse_prices(array $prices): array
|
protected function parse_prices(array $prices): array
|
||||||
{
|
{
|
||||||
@@ -99,29 +83,19 @@ namespace docker_finance\prices\internal\prices\crypto
|
|||||||
$stack = [];
|
$stack = [];
|
||||||
|
|
||||||
for($i = 0; $i < count($prices); $i++) {
|
for($i = 0; $i < count($prices); $i++) {
|
||||||
/**
|
|
||||||
* Expectation:
|
|
||||||
*
|
|
||||||
* array[0] = oldest entry
|
|
||||||
* array[0][0] = timestamp
|
|
||||||
* array[0][1] = price
|
|
||||||
*
|
|
||||||
* array[1] = next hour
|
|
||||||
* array[1][0] = timestamp
|
|
||||||
* array[1][1] = price
|
|
||||||
*
|
|
||||||
* ...etc.
|
|
||||||
*/
|
|
||||||
|
|
||||||
$timestamp = $prices[$i][0] / 1000;
|
$timestamp = $prices[$i][0] / 1000;
|
||||||
$date = date('Y/m/d', $timestamp);
|
$date = date('Y/m/d', $timestamp);
|
||||||
|
|
||||||
// Isolate given year.
|
// Isolate given year.
|
||||||
//
|
//
|
||||||
// If 'all', then all years are needed. Otherwise, for example,
|
// NOTE:
|
||||||
// if the given year is for last year, and the beginning of last
|
//
|
||||||
// year was 375 days ago, then upstream will send 375 entries
|
// If 'all', then all years are needed. Otherwise, for example,
|
||||||
// (but only the first 365 will be needed).
|
// if the given year is last year (and the beginning of last
|
||||||
|
// year was 375 days ago), then upstream will send 375 entries
|
||||||
|
// (but only the first 365 will be needed).
|
||||||
|
|
||||||
$given_year = $this->get_env()->get_env('API_FETCH_YEAR');
|
$given_year = $this->get_env()->get_env('API_FETCH_YEAR');
|
||||||
if ($given_year != 'all' && !preg_match('/^'.$given_year.'\//', $date)) {
|
if ($given_year != 'all' && !preg_match('/^'.$given_year.'\//', $date)) {
|
||||||
utils\CLI::print_debug("skipping $date");
|
utils\CLI::print_debug("skipping $date");
|
||||||
@@ -130,6 +104,14 @@ namespace docker_finance\prices\internal\prices\crypto
|
|||||||
$price = $prices[$i][1];
|
$price = $prices[$i][1];
|
||||||
|
|
||||||
// Push to stack
|
// Push to stack
|
||||||
|
//
|
||||||
|
// NOTE:
|
||||||
|
//
|
||||||
|
// - Data granularity is automatic (cannot be adjusted)
|
||||||
|
// - 1 day from current time = 5 minute interval data
|
||||||
|
// - 1 through 90 days from current time = hourly data
|
||||||
|
// - Above 90 days from current time = daily data (00:00 UTC)
|
||||||
|
|
||||||
$stack += [$date => $price];
|
$stack += [$date => $price];
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -182,13 +164,7 @@ namespace docker_finance\prices\internal\prices\crypto
|
|||||||
parent::__construct($env);
|
parent::__construct($env);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
protected function request(array $asset, string $timestamp): mixed
|
||||||
* @brief REST API request generator
|
|
||||||
* @param string $id Symbol's ID to request
|
|
||||||
* @param string $timestamp Timestamp to request
|
|
||||||
* @return mixed REST API response data
|
|
||||||
*/
|
|
||||||
protected function request(string $id, string $timestamp): mixed
|
|
||||||
{
|
{
|
||||||
// If `key` exists, append to header (used in all plans)
|
// If `key` exists, append to header (used in all plans)
|
||||||
$key = $this->get_env()->get_env('API_PRICES_KEY');
|
$key = $this->get_env()->get_env('API_PRICES_KEY');
|
||||||
@@ -197,7 +173,11 @@ namespace docker_finance\prices\internal\prices\crypto
|
|||||||
if ($key != 'None') {
|
if ($key != 'None') {
|
||||||
$header = ["Authorization: $key"];
|
$header = ["Authorization: $key"];
|
||||||
}
|
}
|
||||||
$url = "https://{$domain}/api/1/market/history?asset={$id}&from={$timestamp}";
|
|
||||||
|
$url = "https://{$domain}/api/1/market/history?asset={$asset['id']}&from={$timestamp}";
|
||||||
|
if (!empty($asset['blockchain'])) {
|
||||||
|
$url .= "&blockchain={$asset['blockchain']}";
|
||||||
|
}
|
||||||
|
|
||||||
$response = $this->request_impl($url, $header);
|
$response = $this->request_impl($url, $header);
|
||||||
if (array_key_exists('error', $response)) {
|
if (array_key_exists('error', $response)) {
|
||||||
@@ -220,19 +200,6 @@ namespace docker_finance\prices\internal\prices\crypto
|
|||||||
$stack = [];
|
$stack = [];
|
||||||
|
|
||||||
for($i = 0; $i < count($prices); $i++) {
|
for($i = 0; $i < count($prices); $i++) {
|
||||||
/**
|
|
||||||
* Expectation:
|
|
||||||
*
|
|
||||||
* array[0] = oldest entry
|
|
||||||
* array[0][0] = timestamp
|
|
||||||
* array[0][1] = price
|
|
||||||
*
|
|
||||||
* array[1] = next entry
|
|
||||||
* array[1][0] = timestamp
|
|
||||||
* array[1][1] = price
|
|
||||||
*
|
|
||||||
* ...etc.
|
|
||||||
*/
|
|
||||||
|
|
||||||
$timestamp = $prices[$i][0] / 1000;
|
$timestamp = $prices[$i][0] / 1000;
|
||||||
$date = date('Y/m/d', $timestamp);
|
$date = date('Y/m/d', $timestamp);
|
||||||
|
|||||||
Reference in New Issue
Block a user