mirror of
https://github.com/mempool/mempool.git
synced 2025-09-28 04:56:48 +02:00
Merge pull request #5916 from mempool/natsoni/liquid-usd
Add USD series to Liquid reserves graph
This commit is contained in:
@@ -4,6 +4,7 @@ import config from '../../config';
|
|||||||
import elementsParser from './elements-parser';
|
import elementsParser from './elements-parser';
|
||||||
import icons from './icons';
|
import icons from './icons';
|
||||||
import { handleError } from '../../utils/api';
|
import { handleError } from '../../utils/api';
|
||||||
|
import PricesRepository from '../../repositories/PricesRepository';
|
||||||
|
|
||||||
class LiquidRoutes {
|
class LiquidRoutes {
|
||||||
public initRoutes(app: Application) {
|
public initRoutes(app: Application) {
|
||||||
@@ -31,6 +32,7 @@ class LiquidRoutes {
|
|||||||
.get(config.MEMPOOL.API_URL_PREFIX + 'liquid/reserves/utxos/emergency-spent', this.$getEmergencySpentUtxos)
|
.get(config.MEMPOOL.API_URL_PREFIX + 'liquid/reserves/utxos/emergency-spent', this.$getEmergencySpentUtxos)
|
||||||
.get(config.MEMPOOL.API_URL_PREFIX + 'liquid/reserves/utxos/emergency-spent/stats', this.$getEmergencySpentUtxosStats)
|
.get(config.MEMPOOL.API_URL_PREFIX + 'liquid/reserves/utxos/emergency-spent/stats', this.$getEmergencySpentUtxosStats)
|
||||||
.get(config.MEMPOOL.API_URL_PREFIX + 'liquid/reserves/status', this.$getFederationAuditStatus)
|
.get(config.MEMPOOL.API_URL_PREFIX + 'liquid/reserves/status', this.$getFederationAuditStatus)
|
||||||
|
.get(config.MEMPOOL.API_URL_PREFIX + 'historical-price', this.$getHistoricalPrice)
|
||||||
;
|
;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -255,6 +257,34 @@ class LiquidRoutes {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private async $getHistoricalPrice(req: Request, res: Response): Promise<void> {
|
||||||
|
try {
|
||||||
|
res.header('Pragma', 'public');
|
||||||
|
res.header('Cache-control', 'public');
|
||||||
|
res.setHeader('Expires', new Date(Date.now() + 1000 * 300).toUTCString());
|
||||||
|
if (['testnet', 'signet', 'liquidtestnet'].includes(config.MEMPOOL.NETWORK)) {
|
||||||
|
handleError(req, res, 400, 'Prices are not available on testnets.');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const timestamp = parseInt(req.query.timestamp as string, 10) || 0;
|
||||||
|
const currency = req.query.currency as string;
|
||||||
|
|
||||||
|
let response;
|
||||||
|
if (timestamp && currency) {
|
||||||
|
response = await PricesRepository.$getNearestHistoricalPrice(timestamp, currency);
|
||||||
|
} else if (timestamp) {
|
||||||
|
response = await PricesRepository.$getNearestHistoricalPrice(timestamp);
|
||||||
|
} else if (currency) {
|
||||||
|
response = await PricesRepository.$getHistoricalPrices(currency);
|
||||||
|
} else {
|
||||||
|
response = await PricesRepository.$getHistoricalPrices();
|
||||||
|
}
|
||||||
|
res.status(200).send(response);
|
||||||
|
} catch (e) {
|
||||||
|
handleError(req, res, 500, 'Failed to get historical prices');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export default new LiquidRoutes();
|
export default new LiquidRoutes();
|
||||||
|
@@ -1,4 +1,4 @@
|
|||||||
<div class="echarts" *browserOnly echarts [initOpts]="pegsChartInitOption" [options]="pegsChartOptions" (chartRendered)="rendered()"></div>
|
<div class="echarts" *browserOnly echarts [initOpts]="pegsChartInitOption" [options]="pegsChartOptions" (chartInit)="onChartInit($event)" (chartRendered)="rendered()"></div>
|
||||||
<div class="text-center loadingGraphs" *ngIf="!stateService.isBrowser || isLoading">
|
<div class="text-center loadingGraphs" *ngIf="!stateService.isBrowser || isLoading">
|
||||||
<div class="spinner-border text-light"></div>
|
<div class="spinner-border text-light"></div>
|
||||||
</div>
|
</div>
|
@@ -1,7 +1,10 @@
|
|||||||
import { Component, Inject, LOCALE_ID, ChangeDetectionStrategy, Input, OnChanges, OnInit } from '@angular/core';
|
import { Component, Inject, LOCALE_ID, ChangeDetectionStrategy, Input, OnChanges, OnInit, ChangeDetectorRef } from '@angular/core';
|
||||||
import { formatDate, formatNumber } from '@angular/common';
|
import { formatDate, formatNumber } from '@angular/common';
|
||||||
import { EChartsOption } from '@app/graphs/echarts';
|
import { EChartsOption } from '@app/graphs/echarts';
|
||||||
import { StateService } from '@app/services/state.service';
|
import { StateService } from '@app/services/state.service';
|
||||||
|
import { map, Subscription, switchMap } from 'rxjs';
|
||||||
|
import { PriceService } from '../../services/price.service';
|
||||||
|
import { AmountShortenerPipe } from '@app/shared/pipes/amount-shortener.pipe';
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'app-lbtc-pegs-graph',
|
selector: 'app-lbtc-pegs-graph',
|
||||||
@@ -21,19 +24,31 @@ export class LbtcPegsGraphComponent implements OnInit, OnChanges {
|
|||||||
@Input() data: any;
|
@Input() data: any;
|
||||||
@Input() height: number | string = '360';
|
@Input() height: number | string = '360';
|
||||||
pegsChartOptions: EChartsOption;
|
pegsChartOptions: EChartsOption;
|
||||||
|
subscription: Subscription;
|
||||||
|
|
||||||
right: number | string = '10';
|
right: number | string = '5';
|
||||||
top: number | string = '20';
|
top: number | string = '20';
|
||||||
left: number | string = '50';
|
left: number | string = '60';
|
||||||
template: ('widget' | 'advanced') = 'widget';
|
template: ('widget' | 'advanced') = 'widget';
|
||||||
isLoading = true;
|
isLoading = true;
|
||||||
|
chartInstance: any = undefined;
|
||||||
pegsChartInitOption = {
|
pegsChartInitOption = {
|
||||||
renderer: 'svg'
|
renderer: 'svg'
|
||||||
};
|
};
|
||||||
|
|
||||||
|
adjustedLeft: number;
|
||||||
|
adjustedRight: number;
|
||||||
|
selected = {
|
||||||
|
'L-BTC': true,
|
||||||
|
'BTC': true,
|
||||||
|
'USD': false,
|
||||||
|
};
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
public stateService: StateService,
|
public stateService: StateService,
|
||||||
|
public priceService: PriceService,
|
||||||
|
public amountShortenerPipe: AmountShortenerPipe,
|
||||||
|
public cd: ChangeDetectorRef,
|
||||||
@Inject(LOCALE_ID) private locale: string,
|
@Inject(LOCALE_ID) private locale: string,
|
||||||
) { }
|
) { }
|
||||||
|
|
||||||
@@ -42,14 +57,25 @@ export class LbtcPegsGraphComponent implements OnInit, OnChanges {
|
|||||||
}
|
}
|
||||||
|
|
||||||
ngOnChanges() {
|
ngOnChanges() {
|
||||||
if (!this.data?.liquidPegs) {
|
if (!this.data?.liquidReserves) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (!this.data.liquidReserves) {
|
|
||||||
this.pegsChartOptions = this.createChartOptions(this.data.liquidPegs.series, this.data.liquidPegs.labels);
|
this.subscription = this.stateService.conversions$.pipe(
|
||||||
} else {
|
switchMap(conversions =>
|
||||||
this.pegsChartOptions = this.createChartOptions(this.data.liquidPegs.series, this.data.liquidPegs.labels, this.data.liquidReserves.series);
|
this.priceService.getPriceByBulk$(this.data.liquidPegs.labels.map((date: string) => Math.floor(new Date(date).getTime() / 1000)).slice(0, -1), 'USD')
|
||||||
}
|
.pipe(
|
||||||
|
map(prices => this.data.liquidReserves.series.map((value, i) => value * (prices[i]?.price.USD || conversions['USD'])))
|
||||||
|
)
|
||||||
|
)
|
||||||
|
).subscribe((usdBanlance: any) => {
|
||||||
|
if (!this.data.liquidReserves) {
|
||||||
|
this.pegsChartOptions = this.createChartOptions(this.data.liquidPegs.series, this.data.liquidPegs.labels);
|
||||||
|
} else {
|
||||||
|
this.pegsChartOptions = this.createChartOptions(this.data.liquidPegs.series, this.data.liquidPegs.labels, this.data.liquidReserves.series, usdBanlance);
|
||||||
|
}
|
||||||
|
this.cd.markForCheck();
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
rendered() {
|
rendered() {
|
||||||
@@ -59,7 +85,7 @@ export class LbtcPegsGraphComponent implements OnInit, OnChanges {
|
|||||||
this.isLoading = false;
|
this.isLoading = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
createChartOptions(pegSeries: number[], labels: string[], reservesSeries?: number[],): EChartsOption {
|
createChartOptions(pegSeries: number[], labels: string[], reservesSeries?: number[], usdBalance?: number[]): EChartsOption {
|
||||||
return {
|
return {
|
||||||
grid: {
|
grid: {
|
||||||
height: this.height,
|
height: this.height,
|
||||||
@@ -89,6 +115,35 @@ export class LbtcPegsGraphComponent implements OnInit, OnChanges {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}],
|
}],
|
||||||
|
legend: {
|
||||||
|
data: [
|
||||||
|
{
|
||||||
|
name: 'L-BTC',
|
||||||
|
inactiveColor: 'var(--grey)',
|
||||||
|
textStyle: {
|
||||||
|
color: 'white',
|
||||||
|
},
|
||||||
|
icon: 'roundRect',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'BTC',
|
||||||
|
inactiveColor: 'var(--grey)',
|
||||||
|
textStyle: {
|
||||||
|
color: 'white',
|
||||||
|
},
|
||||||
|
icon: 'roundRect',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'USD',
|
||||||
|
inactiveColor: 'var(--grey)',
|
||||||
|
textStyle: {
|
||||||
|
color: 'white',
|
||||||
|
},
|
||||||
|
icon: 'roundRect',
|
||||||
|
}
|
||||||
|
],
|
||||||
|
selected: this.selected,
|
||||||
|
},
|
||||||
tooltip: {
|
tooltip: {
|
||||||
trigger: 'axis',
|
trigger: 'axis',
|
||||||
position: (pos, params, el, elRect, size) => {
|
position: (pos, params, el, elRect, size) => {
|
||||||
@@ -109,10 +164,12 @@ export class LbtcPegsGraphComponent implements OnInit, OnChanges {
|
|||||||
for (let index = params.length - 1; index >= 0; index--) {
|
for (let index = params.length - 1; index >= 0; index--) {
|
||||||
const item = params[index];
|
const item = params[index];
|
||||||
if (index < 26) {
|
if (index < 26) {
|
||||||
|
let formattedValue;
|
||||||
|
item.seriesName === 'USD' ? formattedValue = this.amountShortenerPipe.transform(item.value, 3, undefined, true, true) : formattedValue = formatNumber(item.value, this.locale, '1.2-2');
|
||||||
itemFormatted += `<div class="item">
|
itemFormatted += `<div class="item">
|
||||||
<div class="indicator-container">${colorSpan(item.color)}</div>
|
<div class="indicator-container">${colorSpan(item.color)}</div>
|
||||||
<div style="margin-right: 5px"></div>
|
<div style="margin-right: 5px"></div>
|
||||||
<div class="value">${formatNumber(item.value, this.locale, '1.2-2')} <span class="symbol">${item.seriesName}</span></div>
|
<div class="value">${formattedValue} <span class="symbol">${item.seriesName}</span></div>
|
||||||
</div>`;
|
</div>`;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -129,10 +186,13 @@ export class LbtcPegsGraphComponent implements OnInit, OnChanges {
|
|||||||
boundaryGap: false,
|
boundaryGap: false,
|
||||||
data: labels.map((value: any) => `${formatDate(value, 'MMM\ny', this.locale)}`),
|
data: labels.map((value: any) => `${formatDate(value, 'MMM\ny', this.locale)}`),
|
||||||
},
|
},
|
||||||
yAxis: {
|
yAxis: [{
|
||||||
type: 'value',
|
type: 'value',
|
||||||
axisLabel: {
|
axisLabel: {
|
||||||
fontSize: 11,
|
fontSize: 11,
|
||||||
|
formatter: (val): string => {
|
||||||
|
return `${this.amountShortenerPipe.transform(Math.round(val), 0, undefined, true)} BTC`;
|
||||||
|
}
|
||||||
},
|
},
|
||||||
splitLine: {
|
splitLine: {
|
||||||
lineStyle: {
|
lineStyle: {
|
||||||
@@ -142,10 +202,23 @@ export class LbtcPegsGraphComponent implements OnInit, OnChanges {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
type: 'value',
|
||||||
|
axisLabel: {
|
||||||
|
color: 'rgb(110, 112, 121)',
|
||||||
|
formatter: function(val) {
|
||||||
|
return `$${this.amountShortenerPipe.transform(val, 3, undefined, true, true)}`;
|
||||||
|
}.bind(this)
|
||||||
|
},
|
||||||
|
splitLine: {
|
||||||
|
show: false,
|
||||||
|
},
|
||||||
|
}],
|
||||||
series: [
|
series: [
|
||||||
{
|
{
|
||||||
data: pegSeries,
|
data: pegSeries,
|
||||||
name: 'L-BTC',
|
name: 'L-BTC',
|
||||||
|
yAxisIndex: 0,
|
||||||
color: '#116761',
|
color: '#116761',
|
||||||
type: 'line',
|
type: 'line',
|
||||||
stack: 'total',
|
stack: 'total',
|
||||||
@@ -163,6 +236,7 @@ export class LbtcPegsGraphComponent implements OnInit, OnChanges {
|
|||||||
{
|
{
|
||||||
data: reservesSeries,
|
data: reservesSeries,
|
||||||
name: 'BTC',
|
name: 'BTC',
|
||||||
|
yAxisIndex: 0,
|
||||||
color: '#EA983B',
|
color: '#EA983B',
|
||||||
type: 'line',
|
type: 'line',
|
||||||
smooth: true,
|
smooth: true,
|
||||||
@@ -172,8 +246,49 @@ export class LbtcPegsGraphComponent implements OnInit, OnChanges {
|
|||||||
color: '#EA983B',
|
color: '#EA983B',
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
data: usdBalance,
|
||||||
|
name: 'USD',
|
||||||
|
yAxisIndex: 1,
|
||||||
|
color: '#4CAF50',
|
||||||
|
type: 'line',
|
||||||
|
smooth: true,
|
||||||
|
showSymbol: false,
|
||||||
|
lineStyle: {
|
||||||
|
width: 2,
|
||||||
|
color: '#3BCC49',
|
||||||
|
},
|
||||||
|
},
|
||||||
],
|
],
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
onLegendSelectChanged(e) {
|
||||||
|
this.selected = e.selected;
|
||||||
|
this.adjustedRight = this.selected['USD'] ? +this.right + 40 : +this.right;
|
||||||
|
this.adjustedLeft = this.selected['L-BTC'] || this.selected['BTC'] ? +this.left : +this.left - 40;
|
||||||
|
|
||||||
|
this.pegsChartOptions = {
|
||||||
|
...this.pegsChartOptions,
|
||||||
|
grid: {
|
||||||
|
...this.pegsChartOptions.grid,
|
||||||
|
right: this.adjustedRight,
|
||||||
|
left: this.adjustedLeft,
|
||||||
|
},
|
||||||
|
legend: {
|
||||||
|
...this.pegsChartOptions.legend,
|
||||||
|
selected: this.selected,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
onChartInit(ec) {
|
||||||
|
this.chartInstance = ec;
|
||||||
|
this.chartInstance.on('legendselectchanged', this.onLegendSelectChanged.bind(this));
|
||||||
|
}
|
||||||
|
|
||||||
|
ngOnDestroy(): void {
|
||||||
|
this.subscription?.unsubscribe();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -251,7 +251,7 @@ export class PriceService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
getPriceByBulk$(timestamps: number[], currency: string): Observable<Price[]> {
|
getPriceByBulk$(timestamps: number[], currency: string): Observable<Price[]> {
|
||||||
if (this.stateService.env.BASE_MODULE !== 'mempool' || !this.stateService.env.HISTORICAL_PRICE) {
|
if (!this.stateService.env.HISTORICAL_PRICE) {
|
||||||
return of([]);
|
return of([]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Reference in New Issue
Block a user