mirror of
https://github.com/mempool/mempool.git
synced 2025-01-18 13:24:01 +01:00
Bisq markets: General trading volume graph.
This commit is contained in:
parent
8e29a4cefd
commit
da77dbece1
@ -1,7 +1,7 @@
|
||||
import { Injectable } from '@angular/core';
|
||||
import { HttpClient, HttpResponse, HttpParams } from '@angular/common/http';
|
||||
import { Observable } from 'rxjs';
|
||||
import { BisqTransaction, BisqBlock, BisqStats } from './bisq.interfaces';
|
||||
import { BisqTransaction, BisqBlock, BisqStats, MarketVolume } from './bisq.interfaces';
|
||||
|
||||
const API_BASE_URL = '/bisq/api';
|
||||
|
||||
@ -71,4 +71,8 @@ export class BisqApiService {
|
||||
getMarketVolumesByTime$(period: string): Observable<any[]> {
|
||||
return this.httpClient.get<any[]>(API_BASE_URL + '/markets/volumes/' + period);
|
||||
}
|
||||
|
||||
getAllVolumesDay$(): Observable<MarketVolume[]> {
|
||||
return this.httpClient.get<MarketVolume[]>(API_BASE_URL + '/markets/volumes?interval=week');
|
||||
}
|
||||
}
|
||||
|
@ -1,21 +1,28 @@
|
||||
<div class="container-xl">
|
||||
<div class="container-xl">
|
||||
|
||||
<h1>Trading volume</h1>
|
||||
|
||||
<div id="volumeHolder">
|
||||
<ng-container *ngIf="volumes$ | async as volumes">
|
||||
<app-lightweight-charts-area [data]="volumes.data" [lineData]="volumes.linesData"></app-lightweight-charts-area>
|
||||
</ng-container>
|
||||
</div>
|
||||
|
||||
<br>
|
||||
|
||||
<h1>Markets</h1>
|
||||
<ng-container *ngIf="{ value: (tickers$ | async) } as tickers">
|
||||
|
||||
<table class="table table-borderless table-striped">
|
||||
<thead>
|
||||
<th>Rank</th>
|
||||
<th>Currency</th>
|
||||
<th>Pair</th>
|
||||
<th>Price</th>
|
||||
<th>Volume (7d)</th>
|
||||
<th>Trades (7d)</th>
|
||||
</thead>
|
||||
<tbody *ngIf="tickers.value; else loadingTmpl">
|
||||
<tr *ngFor="let ticker of tickers.value; trackBy: trackByFn; let i = index">
|
||||
<td>{{ i + 1 }}</td>
|
||||
<td>{{ ticker.market.rtype === 'crypto' ? ticker.market.lname : ticker.market.rname }}</td>
|
||||
<td><a [routerLink]="['/market' | relativeUrl, ticker.pair_url]">{{ ticker.pair }}</a></td>
|
||||
<tr *ngFor="let ticker of tickers.value; trackBy: trackByFn;">
|
||||
<td><a [routerLink]="['/market' | relativeUrl, ticker.pair_url]">{{ ticker.market.rtype === 'crypto' ? ticker.market.lname : ticker.market.rname }} ({{ ticker.market.rtype === 'crypto' ? ticker.market.lsymbol : ticker.market.rsymbol }})</a></td>
|
||||
<td>
|
||||
<app-fiat *ngIf="ticker.market.rtype === 'crypto'; else fiat" [value]="ticker.last * 100000000"></app-fiat>
|
||||
<ng-template #fiat>
|
||||
|
@ -0,0 +1,4 @@
|
||||
#volumeHolder {
|
||||
height: 500px;
|
||||
background-color: #000;
|
||||
}
|
@ -14,6 +14,8 @@ import { BisqApiService } from '../bisq-api.service';
|
||||
})
|
||||
export class BisqDashboardComponent implements OnInit {
|
||||
tickers$: Observable<any>;
|
||||
volumes$: Observable<any>;
|
||||
|
||||
allowCryptoCoins = ['usdc', 'l-btc', 'bsq'];
|
||||
|
||||
constructor(
|
||||
@ -27,6 +29,30 @@ export class BisqDashboardComponent implements OnInit {
|
||||
this.seoService.setTitle(`Markets`);
|
||||
this.websocketService.want(['blocks']);
|
||||
|
||||
this.volumes$ = this.bisqApiService.getAllVolumesDay$()
|
||||
.pipe(
|
||||
map((volumes) => {
|
||||
const data = volumes.map((volume) => {
|
||||
return {
|
||||
time: volume.period_start,
|
||||
value: volume.volume,
|
||||
};
|
||||
});
|
||||
|
||||
const linesData = volumes.map((volume) => {
|
||||
return {
|
||||
time: volume.period_start,
|
||||
value: volume.num_trades,
|
||||
};
|
||||
});
|
||||
|
||||
return {
|
||||
data: data,
|
||||
linesData: linesData,
|
||||
};
|
||||
})
|
||||
);
|
||||
|
||||
this.tickers$ = combineLatest([
|
||||
this.bisqApiService.getMarketsTicker$(),
|
||||
this.bisqApiService.getMarkets$(),
|
||||
|
@ -3,7 +3,7 @@
|
||||
<ng-container *ngIf="hlocData$ | async as hlocData; else loadingSpinner">
|
||||
|
||||
<ng-container *ngIf="currency$ | async as currency; else loadingSpinner">
|
||||
<h1>{{ currency.market.lname }} - {{ currency.pair }}</h1>
|
||||
<h1>{{ currency.market.rtype === 'crypto' ? currency.market.lname : currency.market.rname }} - {{ currency.pair }}</h1>
|
||||
<div class="float-left">
|
||||
<span class="priceheader">
|
||||
<ng-container *ngIf="currency.market.rtype === 'fiat'; else headerPriceCrypto"><span class="green-color">{{ hlocData.hloc[hlocData.hloc.length - 1].close | currency: currency.market.rsymbol }}</span></ng-container>
|
||||
|
@ -80,3 +80,180 @@ interface SpentInfo {
|
||||
inputIndex: number;
|
||||
txId: string;
|
||||
}
|
||||
|
||||
|
||||
export interface BisqTrade {
|
||||
direction: string;
|
||||
price: string;
|
||||
amount: string;
|
||||
volume: string;
|
||||
payment_method: string;
|
||||
trade_id: string;
|
||||
trade_date: number;
|
||||
market?: string;
|
||||
}
|
||||
|
||||
export interface Currencies { [txid: string]: Currency; }
|
||||
|
||||
export interface Currency {
|
||||
code: string;
|
||||
name: string;
|
||||
precision: number;
|
||||
|
||||
_type: string;
|
||||
}
|
||||
|
||||
export interface Depth { [market: string]: Market; }
|
||||
|
||||
interface Market {
|
||||
'buys': string[];
|
||||
'sells': string[];
|
||||
}
|
||||
|
||||
export interface HighLowOpenClose {
|
||||
period_start: number | string;
|
||||
open: string;
|
||||
high: string;
|
||||
low: string;
|
||||
close: string;
|
||||
volume_left: string;
|
||||
volume_right: string;
|
||||
avg: string;
|
||||
}
|
||||
|
||||
export interface Markets { [txid: string]: Pair; }
|
||||
|
||||
interface Pair {
|
||||
pair: string;
|
||||
lname: string;
|
||||
rname: string;
|
||||
lsymbol: string;
|
||||
rsymbol: string;
|
||||
lprecision: number;
|
||||
rprecision: number;
|
||||
ltype: string;
|
||||
rtype: string;
|
||||
name: string;
|
||||
}
|
||||
|
||||
export interface Offers { [market: string]: OffersMarket; }
|
||||
|
||||
interface OffersMarket {
|
||||
buys: Offer[] | null;
|
||||
sells: Offer[] | null;
|
||||
}
|
||||
|
||||
export interface OffersData {
|
||||
direction: string;
|
||||
currencyCode: string;
|
||||
minAmount: number;
|
||||
amount: number;
|
||||
price: number;
|
||||
date: number;
|
||||
useMarketBasedPrice: boolean;
|
||||
marketPriceMargin: number;
|
||||
paymentMethod: string;
|
||||
id: string;
|
||||
currencyPair: string;
|
||||
primaryMarketDirection: string;
|
||||
priceDisplayString: string;
|
||||
primaryMarketAmountDisplayString: string;
|
||||
primaryMarketMinAmountDisplayString: string;
|
||||
primaryMarketVolumeDisplayString: string;
|
||||
primaryMarketMinVolumeDisplayString: string;
|
||||
primaryMarketPrice: number;
|
||||
primaryMarketAmount: number;
|
||||
primaryMarketMinAmount: number;
|
||||
primaryMarketVolume: number;
|
||||
primaryMarketMinVolume: number;
|
||||
}
|
||||
|
||||
export interface Offer {
|
||||
offer_id: string;
|
||||
offer_date: number;
|
||||
direction: string;
|
||||
min_amount: string;
|
||||
amount: string;
|
||||
price: string;
|
||||
volume: string;
|
||||
payment_method: string;
|
||||
offer_fee_txid: any;
|
||||
}
|
||||
|
||||
export interface Tickers { [market: string]: Ticker | null; }
|
||||
|
||||
export interface Ticker {
|
||||
last: string;
|
||||
high: string;
|
||||
low: string;
|
||||
volume_left: string;
|
||||
volume_right: string;
|
||||
buy: string | null;
|
||||
sell: string | null;
|
||||
}
|
||||
|
||||
export interface Trade {
|
||||
direction: string;
|
||||
price: string;
|
||||
amount: string;
|
||||
volume: string;
|
||||
payment_method: string;
|
||||
trade_id: string;
|
||||
trade_date: number;
|
||||
}
|
||||
|
||||
export interface TradesData {
|
||||
currency: string;
|
||||
direction: string;
|
||||
tradePrice: number;
|
||||
tradeAmount: number;
|
||||
tradeDate: number;
|
||||
paymentMethod: string;
|
||||
offerDate: number;
|
||||
useMarketBasedPrice: boolean;
|
||||
marketPriceMargin: number;
|
||||
offerAmount: number;
|
||||
offerMinAmount: number;
|
||||
offerId: string;
|
||||
depositTxId?: string;
|
||||
currencyPair: string;
|
||||
primaryMarketDirection: string;
|
||||
primaryMarketTradePrice: number;
|
||||
primaryMarketTradeAmount: number;
|
||||
primaryMarketTradeVolume: number;
|
||||
|
||||
_market: string;
|
||||
_tradePriceStr: string;
|
||||
_tradeAmountStr: string;
|
||||
_tradeVolumeStr: string;
|
||||
_offerAmountStr: string;
|
||||
_tradePrice: number;
|
||||
_tradeAmount: number;
|
||||
_tradeVolume: number;
|
||||
_offerAmount: number;
|
||||
}
|
||||
|
||||
export interface MarketVolume {
|
||||
period_start: number;
|
||||
num_trades: number;
|
||||
volume: string;
|
||||
}
|
||||
|
||||
export interface MarketsApiError {
|
||||
success: number;
|
||||
error: string;
|
||||
}
|
||||
|
||||
export type Interval = 'minute' | 'half_hour' | 'hour' | 'half_day' | 'day' | 'week' | 'month' | 'year' | 'auto';
|
||||
|
||||
export interface SummarizedIntervals { [market: string]: SummarizedInterval; }
|
||||
export interface SummarizedInterval {
|
||||
'period_start': number;
|
||||
'open': number;
|
||||
'close': number;
|
||||
'high': number;
|
||||
'low': number;
|
||||
'avg': number;
|
||||
'volume_right': number;
|
||||
'volume_left': number;
|
||||
}
|
||||
|
@ -4,6 +4,7 @@ import { SharedModule } from '../shared/shared.module';
|
||||
import { NgxBootstrapMultiselectModule } from 'ngx-bootrap-multiselect';
|
||||
|
||||
import { LightweightChartsComponent } from './lightweight-charts/lightweight-charts.component';
|
||||
import { LightweightChartsAreaComponent } from './lightweight-charts-area/lightweight-charts-area.component';
|
||||
import { BisqMarketComponent } from './bisq-market/bisq-market.component';
|
||||
import { BisqTransactionsComponent } from './bisq-transactions/bisq-transactions.component';
|
||||
import { NgbPaginationModule } from '@ng-bootstrap/ng-bootstrap';
|
||||
@ -36,6 +37,7 @@ import { BsqAmountComponent } from './bsq-amount/bsq-amount.component';
|
||||
BisqStatsComponent,
|
||||
BsqAmountComponent,
|
||||
LightweightChartsComponent,
|
||||
LightweightChartsAreaComponent,
|
||||
BisqDashboardComponent,
|
||||
BisqMarketComponent,
|
||||
],
|
||||
|
@ -0,0 +1,25 @@
|
||||
:host ::ng-deep .floating-tooltip-2 {
|
||||
width: 160px;
|
||||
height: 80px;
|
||||
position: absolute;
|
||||
display: none;
|
||||
padding: 8px;
|
||||
box-sizing: border-box;
|
||||
font-size: 12px;
|
||||
color:rgba(255, 255, 255, 1);
|
||||
background-color: #131722;
|
||||
text-align: left;
|
||||
z-index: 1000;
|
||||
top: 12px;
|
||||
left: 12px;
|
||||
pointer-events: none;
|
||||
border-radius: 2px;
|
||||
}
|
||||
|
||||
:host ::ng-deep .volumeText {
|
||||
color: rgba(37, 177, 53, 1);
|
||||
}
|
||||
|
||||
:host ::ng-deep .tradesText {
|
||||
color: rgba(33, 150, 243, 1);
|
||||
}
|
@ -0,0 +1,131 @@
|
||||
import { createChart, CrosshairMode, isBusinessDay } from 'lightweight-charts';
|
||||
import { ChangeDetectionStrategy, Component, ElementRef, Input, OnChanges, OnDestroy } from '@angular/core';
|
||||
|
||||
@Component({
|
||||
selector: 'app-lightweight-charts-area',
|
||||
template: '<ng-component></ng-component>',
|
||||
styleUrls: ['./lightweight-charts-area.component.scss'],
|
||||
changeDetection: ChangeDetectionStrategy.OnPush,
|
||||
})
|
||||
export class LightweightChartsAreaComponent implements OnChanges, OnDestroy {
|
||||
@Input() data: any;
|
||||
@Input() lineData: any;
|
||||
@Input() precision: number;
|
||||
|
||||
areaSeries: any;
|
||||
volumeSeries: any;
|
||||
chart: any;
|
||||
lineSeries: any;
|
||||
container: any;
|
||||
|
||||
width = 1110;
|
||||
height = 500;
|
||||
|
||||
constructor(
|
||||
private element: ElementRef,
|
||||
) {
|
||||
|
||||
this.container = document.createElement('div');
|
||||
const chartholder = this.element.nativeElement.appendChild(this.container);
|
||||
|
||||
this.chart = createChart(chartholder, {
|
||||
width: this.width,
|
||||
height: this.height,
|
||||
crosshair: {
|
||||
mode: CrosshairMode.Normal,
|
||||
},
|
||||
layout: {
|
||||
backgroundColor: '#000',
|
||||
textColor: 'rgba(255, 255, 255, 0.8)',
|
||||
},
|
||||
grid: {
|
||||
vertLines: {
|
||||
color: 'rgba(255, 255, 255, 0.1)',
|
||||
},
|
||||
horzLines: {
|
||||
color: 'rgba(255, 255, 255, 0.1)',
|
||||
},
|
||||
},
|
||||
rightPriceScale: {
|
||||
borderColor: 'rgba(255, 255, 255, 0.2)',
|
||||
},
|
||||
timeScale: {
|
||||
borderColor: 'rgba(255, 255, 255, 0.2)',
|
||||
},
|
||||
});
|
||||
|
||||
this.areaSeries = this.chart.addAreaSeries({
|
||||
topColor: 'rgba(33, 150, 243, 0.9)',
|
||||
bottomColor: 'rgba(33, 150, 243, 0.1)',
|
||||
lineColor: 'rgba(33, 150, 243, 1)',
|
||||
lineWidth: 2,
|
||||
});
|
||||
|
||||
this.lineSeries = this.chart.addLineSeries({
|
||||
color: 'rgba(37, 177, 53, 1)',
|
||||
lineColor: 'rgba(216, 27, 96, 1)',
|
||||
lineWidth: 2,
|
||||
});
|
||||
|
||||
var toolTip = document.createElement('div');
|
||||
toolTip.className = 'floating-tooltip-2';
|
||||
chartholder.appendChild(toolTip);
|
||||
|
||||
this.chart.subscribeCrosshairMove((param) => {
|
||||
if (!param.time || param.point.x < 0 || param.point.x > this.width || param.point.y < 0 || param.point.y > this.height) {
|
||||
toolTip.style.display = 'none';
|
||||
return;
|
||||
}
|
||||
|
||||
var dateStr = isBusinessDay(param.time)
|
||||
? this.businessDayToString(param.time)
|
||||
: new Date(param.time * 1000).toLocaleDateString();
|
||||
|
||||
toolTip.style.display = 'block';
|
||||
var price = param.seriesPrices.get(this.areaSeries);
|
||||
var line = param.seriesPrices.get(this.lineSeries);
|
||||
|
||||
toolTip.innerHTML =
|
||||
`<table><tr><td class="volumeText">Volume:<td class="text-right volumeText">${Math.round(price * 100) / 100} BTC</td></tr>
|
||||
<tr><td class="tradesText"># of trades:</td><td class="text-right tradesText">${Math.round(line * 100) / 100}</td></tr>
|
||||
</table>
|
||||
<div>${dateStr}</div>`;
|
||||
|
||||
var y = param.point.y;
|
||||
|
||||
var toolTipWidth = 100;
|
||||
var toolTipHeight = 80;
|
||||
var toolTipMargin = 15;
|
||||
|
||||
var left = param.point.x + toolTipMargin;
|
||||
if (left > this.width - toolTipWidth) {
|
||||
left = param.point.x - toolTipMargin - toolTipWidth;
|
||||
}
|
||||
|
||||
var top = y + toolTipMargin;
|
||||
if (top > this.height - toolTipHeight) {
|
||||
top = y - toolTipHeight - toolTipMargin;
|
||||
}
|
||||
|
||||
toolTip.style.left = left + 'px';
|
||||
toolTip.style.top = top + 'px';
|
||||
});
|
||||
}
|
||||
|
||||
businessDayToString(businessDay) {
|
||||
return businessDay.year + '-' + businessDay.month + '-' + businessDay.day;
|
||||
}
|
||||
|
||||
ngOnChanges() {
|
||||
if (!this.data) {
|
||||
return;
|
||||
}
|
||||
this.areaSeries.setData(this.data);
|
||||
this.lineSeries.setData(this.lineData);
|
||||
}
|
||||
|
||||
ngOnDestroy() {
|
||||
this.chart.remove();
|
||||
}
|
||||
|
||||
}
|
Loading…
Reference in New Issue
Block a user