diff --git a/frontend/src/app/bisq/bisq-api.service.ts b/frontend/src/app/bisq/bisq-api.service.ts index cb0ece96e..b6f6a08b7 100644 --- a/frontend/src/app/bisq/bisq-api.service.ts +++ b/frontend/src/app/bisq/bisq-api.service.ts @@ -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 { return this.httpClient.get(API_BASE_URL + '/markets/volumes/' + period); } + + getAllVolumesDay$(): Observable { + return this.httpClient.get(API_BASE_URL + '/markets/volumes?interval=week'); + } } diff --git a/frontend/src/app/bisq/bisq-dashboard/bisq-dashboard.component.html b/frontend/src/app/bisq/bisq-dashboard/bisq-dashboard.component.html index efac216f7..38ed7e2c6 100644 --- a/frontend/src/app/bisq/bisq-dashboard/bisq-dashboard.component.html +++ b/frontend/src/app/bisq/bisq-dashboard/bisq-dashboard.component.html @@ -1,21 +1,28 @@ -
+
+ +

Trading volume

+ +
+ + + +
+ +
+

Markets

- - - - - - + +
Rank CurrencyPair Price Volume (7d) Trades (7d)
{{ i + 1 }}{{ ticker.market.rtype === 'crypto' ? ticker.market.lname : ticker.market.rname }}{{ ticker.pair }}
{{ ticker.market.rtype === 'crypto' ? ticker.market.lname : ticker.market.rname }} ({{ ticker.market.rtype === 'crypto' ? ticker.market.lsymbol : ticker.market.rsymbol }}) diff --git a/frontend/src/app/bisq/bisq-dashboard/bisq-dashboard.component.scss b/frontend/src/app/bisq/bisq-dashboard/bisq-dashboard.component.scss index e69de29bb..2680f5212 100644 --- a/frontend/src/app/bisq/bisq-dashboard/bisq-dashboard.component.scss +++ b/frontend/src/app/bisq/bisq-dashboard/bisq-dashboard.component.scss @@ -0,0 +1,4 @@ +#volumeHolder { + height: 500px; + background-color: #000; +} \ No newline at end of file diff --git a/frontend/src/app/bisq/bisq-dashboard/bisq-dashboard.component.ts b/frontend/src/app/bisq/bisq-dashboard/bisq-dashboard.component.ts index efc64901d..79d767e85 100644 --- a/frontend/src/app/bisq/bisq-dashboard/bisq-dashboard.component.ts +++ b/frontend/src/app/bisq/bisq-dashboard/bisq-dashboard.component.ts @@ -14,6 +14,8 @@ import { BisqApiService } from '../bisq-api.service'; }) export class BisqDashboardComponent implements OnInit { tickers$: Observable; + volumes$: Observable; + 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$(), diff --git a/frontend/src/app/bisq/bisq-market/bisq-market.component.html b/frontend/src/app/bisq/bisq-market/bisq-market.component.html index b1501ee62..753ce21e1 100644 --- a/frontend/src/app/bisq/bisq-market/bisq-market.component.html +++ b/frontend/src/app/bisq/bisq-market/bisq-market.component.html @@ -3,7 +3,7 @@ -

{{ currency.market.lname }} - {{ currency.pair }}

+

{{ currency.market.rtype === 'crypto' ? currency.market.lname : currency.market.rname }} - {{ currency.pair }}

{{ hlocData.hloc[hlocData.hloc.length - 1].close | currency: currency.market.rsymbol }} diff --git a/frontend/src/app/bisq/bisq.interfaces.ts b/frontend/src/app/bisq/bisq.interfaces.ts index 710bada2a..edb2f1d76 100644 --- a/frontend/src/app/bisq/bisq.interfaces.ts +++ b/frontend/src/app/bisq/bisq.interfaces.ts @@ -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; +} diff --git a/frontend/src/app/bisq/bisq.module.ts b/frontend/src/app/bisq/bisq.module.ts index 6f245c639..355ccefb0 100644 --- a/frontend/src/app/bisq/bisq.module.ts +++ b/frontend/src/app/bisq/bisq.module.ts @@ -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, ], diff --git a/frontend/src/app/bisq/lightweight-charts-area/lightweight-charts-area.component.scss b/frontend/src/app/bisq/lightweight-charts-area/lightweight-charts-area.component.scss new file mode 100644 index 000000000..277c3861f --- /dev/null +++ b/frontend/src/app/bisq/lightweight-charts-area/lightweight-charts-area.component.scss @@ -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); +} diff --git a/frontend/src/app/bisq/lightweight-charts-area/lightweight-charts-area.component.ts b/frontend/src/app/bisq/lightweight-charts-area/lightweight-charts-area.component.ts new file mode 100644 index 000000000..815cbb448 --- /dev/null +++ b/frontend/src/app/bisq/lightweight-charts-area/lightweight-charts-area.component.ts @@ -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: '', + 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 = + ` + +
Volume:${Math.round(price * 100) / 100} BTC
# of trades:${Math.round(line * 100) / 100}
+
${dateStr}
`; + + 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(); + } + +}