Bisq markets: General trading volume graph.

This commit is contained in:
softsimon 2021-03-16 01:17:40 +07:00
parent 8e29a4cefd
commit da77dbece1
No known key found for this signature in database
GPG Key ID: 488D7DCFB5A430D7
9 changed files with 385 additions and 9 deletions

View File

@ -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');
}
}

View File

@ -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>

View File

@ -0,0 +1,4 @@
#volumeHolder {
height: 500px;
background-color: #000;
}

View File

@ -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$(),

View File

@ -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>

View File

@ -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;
}

View File

@ -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,
],

View File

@ -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);
}

View File

@ -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();
}
}