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 { Injectable } from '@angular/core';
|
||||||
import { HttpClient, HttpResponse, HttpParams } from '@angular/common/http';
|
import { HttpClient, HttpResponse, HttpParams } from '@angular/common/http';
|
||||||
import { Observable } from 'rxjs';
|
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';
|
const API_BASE_URL = '/bisq/api';
|
||||||
|
|
||||||
@ -71,4 +71,8 @@ export class BisqApiService {
|
|||||||
getMarketVolumesByTime$(period: string): Observable<any[]> {
|
getMarketVolumesByTime$(period: string): Observable<any[]> {
|
||||||
return this.httpClient.get<any[]>(API_BASE_URL + '/markets/volumes/' + period);
|
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>
|
<h1>Markets</h1>
|
||||||
<ng-container *ngIf="{ value: (tickers$ | async) } as tickers">
|
<ng-container *ngIf="{ value: (tickers$ | async) } as tickers">
|
||||||
|
|
||||||
<table class="table table-borderless table-striped">
|
<table class="table table-borderless table-striped">
|
||||||
<thead>
|
<thead>
|
||||||
<th>Rank</th>
|
|
||||||
<th>Currency</th>
|
<th>Currency</th>
|
||||||
<th>Pair</th>
|
|
||||||
<th>Price</th>
|
<th>Price</th>
|
||||||
<th>Volume (7d)</th>
|
<th>Volume (7d)</th>
|
||||||
<th>Trades (7d)</th>
|
<th>Trades (7d)</th>
|
||||||
</thead>
|
</thead>
|
||||||
<tbody *ngIf="tickers.value; else loadingTmpl">
|
<tbody *ngIf="tickers.value; else loadingTmpl">
|
||||||
<tr *ngFor="let ticker of tickers.value; trackBy: trackByFn; let i = index">
|
<tr *ngFor="let ticker of tickers.value; trackBy: trackByFn;">
|
||||||
<td>{{ i + 1 }}</td>
|
<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>{{ ticker.market.rtype === 'crypto' ? ticker.market.lname : ticker.market.rname }}</td>
|
|
||||||
<td><a [routerLink]="['/market' | relativeUrl, ticker.pair_url]">{{ ticker.pair }}</a></td>
|
|
||||||
<td>
|
<td>
|
||||||
<app-fiat *ngIf="ticker.market.rtype === 'crypto'; else fiat" [value]="ticker.last * 100000000"></app-fiat>
|
<app-fiat *ngIf="ticker.market.rtype === 'crypto'; else fiat" [value]="ticker.last * 100000000"></app-fiat>
|
||||||
<ng-template #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 {
|
export class BisqDashboardComponent implements OnInit {
|
||||||
tickers$: Observable<any>;
|
tickers$: Observable<any>;
|
||||||
|
volumes$: Observable<any>;
|
||||||
|
|
||||||
allowCryptoCoins = ['usdc', 'l-btc', 'bsq'];
|
allowCryptoCoins = ['usdc', 'l-btc', 'bsq'];
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
@ -27,6 +29,30 @@ export class BisqDashboardComponent implements OnInit {
|
|||||||
this.seoService.setTitle(`Markets`);
|
this.seoService.setTitle(`Markets`);
|
||||||
this.websocketService.want(['blocks']);
|
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.tickers$ = combineLatest([
|
||||||
this.bisqApiService.getMarketsTicker$(),
|
this.bisqApiService.getMarketsTicker$(),
|
||||||
this.bisqApiService.getMarkets$(),
|
this.bisqApiService.getMarkets$(),
|
||||||
|
@ -3,7 +3,7 @@
|
|||||||
<ng-container *ngIf="hlocData$ | async as hlocData; else loadingSpinner">
|
<ng-container *ngIf="hlocData$ | async as hlocData; else loadingSpinner">
|
||||||
|
|
||||||
<ng-container *ngIf="currency$ | async as currency; 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">
|
<div class="float-left">
|
||||||
<span class="priceheader">
|
<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>
|
<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;
|
inputIndex: number;
|
||||||
txId: string;
|
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 { NgxBootstrapMultiselectModule } from 'ngx-bootrap-multiselect';
|
||||||
|
|
||||||
import { LightweightChartsComponent } from './lightweight-charts/lightweight-charts.component';
|
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 { BisqMarketComponent } from './bisq-market/bisq-market.component';
|
||||||
import { BisqTransactionsComponent } from './bisq-transactions/bisq-transactions.component';
|
import { BisqTransactionsComponent } from './bisq-transactions/bisq-transactions.component';
|
||||||
import { NgbPaginationModule } from '@ng-bootstrap/ng-bootstrap';
|
import { NgbPaginationModule } from '@ng-bootstrap/ng-bootstrap';
|
||||||
@ -36,6 +37,7 @@ import { BsqAmountComponent } from './bsq-amount/bsq-amount.component';
|
|||||||
BisqStatsComponent,
|
BisqStatsComponent,
|
||||||
BsqAmountComponent,
|
BsqAmountComponent,
|
||||||
LightweightChartsComponent,
|
LightweightChartsComponent,
|
||||||
|
LightweightChartsAreaComponent,
|
||||||
BisqDashboardComponent,
|
BisqDashboardComponent,
|
||||||
BisqMarketComponent,
|
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…
x
Reference in New Issue
Block a user