У меня есть следующий график:
Он состоит из трех серий: свечного графика, точечного графика для ордеров на покупку и другого точечного графика для ордеров на продажу.
Если вы посмотрите на изображение выше или мой код ниже, я заполняю свечную диаграмму и диаграммы разброса одновременно время в том же массиве. Первые 5 аргументов this.chartData.push
относятся к свечному графику, а два других числа (0,0174 и 0,0173) относятся к точечным графикам. То, что я хочу сделать, - это сначала загрузить данные свечей, как я делаю сейчас, и всякий раз, когда он завершает тестирование на истории (занимает около 20 секунд), а затем получает ордера от бэкенда, он должен добавить данные диаграммы рассеяния после этого. Другими словами, я хочу разделить эти аргументы на 3 массива или, по крайней мере, добавить информацию о заказе отдельно, потому что заказы - это трудоемкая операция, а отображение свечей - это не так.
Что-то в этом роде:
for (let i = 0; i < candlesticks.length; i++) {
// get all orders with candlesticks[i].openTime
this.chartData.push([
null,
candlesticks[i].low,
candlesticks[i].open,
candlesticks[i].close,
candlesticks[i].high,
this.customTooltip(candlesticks[i])
]);
}
...
this.orders$ = this.backtestingService.performTest();
this.orders$
.pipe(takeUntil(this.componentDestroyed$))
.subscribe(orders => {
this.chartData.push(orders);
});
Мой код:
import { Component, OnInit, OnDestroy } from '@angular/core';
import { Observable, Subject } from 'rxjs';
import { takeUntil } from 'rxjs/operators';
import { KlineInterval } from 'src/app/core/types/bot';
import { DatePipe } from '@angular/common';
import { BinanceKline } from 'src/app/core/types/binance';
import { BinanceService } from 'src/app/core/services/binance.service';
import { BacktestingService } from 'src/app/core/services/backtesting.service';
import { Order } from 'src/app/core/types/order';
@Component({
selector: 'app-backtesting',
templateUrl: './backtesting.component.html',
styleUrls: ['./backtesting.component.css']
})
export class BacktestingComponent implements OnInit, OnDestroy {
binances$: Observable<BinanceKline[]>;
orders$: Observable<Order[]>;
private componentDestroyed$ = new Subject<boolean>();
// Angular Google Charts
chartDrawn = false;
chartData = [];
chartOptions = {
tooltip: { isHtml: true },
title: 'Backtesting',
height: 500,
chartArea: { width: '80%', height: '80%' },
legend: { position: 'right', textStyle: { color: 'black', fontSize: 16 } },
seriesType: 'candlesticks',
series: {
1: { type: 'scatter', color: 'green' },
2: { type: 'scatter', color: 'red' }
}
};
chartColumnNames = [
'Label', 'Low', 'Open', 'Close', 'High',
{ type: 'string', role: 'tooltip', p: { html: true } },
{ label: 'Buy', type: 'number' },
{ label: 'Sell', type: 'number' }
];
constructor(
private binanceService: BinanceService,
private backtestingService: BacktestingService) { }
ngOnInit() {
this.getAllKlines();
}
ngOnDestroy() {
this.componentDestroyed$.next(true);
this.componentDestroyed$.complete();
}
private customTooltip(candlestick: BinanceKline): string {
let pipe = new DatePipe('en-US');
let openTime = pipe.transform(candlestick.openTime, 'HH:mm');
let closeTime = pipe.transform(candlestick.closeTime, 'HH:mm');
return `<div style="font-size: 14px; white-space: nowrap; padding: 10px;">
<b>Open Time:</b> ${openTime}<br /><b>Close Time:</b> ${closeTime}<br />
<b>Open:</b> ${candlestick.open}<br /><b>Close:</b> ${candlestick.close}<br />
<b>High:</b> ${candlestick.high}<br /><b>Low:</b> ${candlestick.low}<br />
<b>VOL:</b> ${candlestick.volume}
</div>`;
}
private getAllKlines() {
this.orders$ = this.backtestingService.performTest();
this.orders$
.pipe(takeUntil(this.componentDestroyed$))
.subscribe(orders => {
console.log(orders);
});
this.binances$ = this.binanceService.getAllKlines("TRXUSDT", KlineInterval.OneHour);
this.chartDrawn = false;
this.chartData = [];
this.binances$
.pipe(takeUntil(this.componentDestroyed$))
.subscribe(candlesticks => {
for (let i = 0; i < candlesticks.length; i++) {
// get all orders with candlesticks[i].openTime
this.chartData.push([
null,
candlesticks[i].low,
candlesticks[i].open,
candlesticks[i].close,
candlesticks[i].high,
this.customTooltip(candlesticks[i]),
0.0174,
0.0173
]);
}
this.chartDrawn = true;
});
}
}
<section id="backtesting">
<div class="container">
<div class="row">
<ng-container *ngIf="chartDrawn">
<div class="col-lg-12">
<google-chart class="mb-1" type="ComboChart" [data]="chartData" [options]="chartOptions"
[columnNames]="chartColumnNames">
</google-chart>
</div>
</ng-container>
</div>
</div>
</section>
backtesting.service.ts
import { Injectable } from '@angular/core';
import { HttpClient } from '@angular/common/http';
import { environment } from 'src/environments/environment';
import { Order } from '../types/order';
@Injectable({
providedIn: 'root'
})
export class BacktestingService {
private actionUrl: string;
constructor(private httpClient: HttpClient) {
this.actionUrl = `${environment.baseUrls.server}api/backtesting`;
}
performTest() {
return this.httpClient.get<Order[]>(`${this.actionUrl}`);
}
}
Order.cs
using Binance.Net.Objects;
using System;
using System.ComponentModel.DataAnnotations.Schema;
namespace Binance.Entity
{
public class Order
{
public int Id { get; set; }
public OrderSide OrderSide { get; set; }
public OrderType OrderType { get; set; }
public DateTime PlacedAt { get; set; }
public DateTime OpenTime { get; set; }
[Column(TypeName = "decimal(10,1)")]
public decimal Quantity { get; set; }
[Column(TypeName = "decimal(10,10)")]
public decimal Price { get; set; }
public int BotId { get; set; }
public Bot Bot { get; set; }
}
}
BinanceKlineDto. CS
using CryptoExchange.Net.Converters;
using Newtonsoft.Json;
using System;
namespace Binance.Dtos
{
public class BinanceKlineDto
{
[JsonConverter(typeof(TimestampConverter))]
public DateTime OpenTime { get; set; }
public decimal Open { get; set; }
public decimal High { get; set; }
public decimal Low { get; set; }
public decimal Close { get; set; }
public decimal Volume { get; set; }
[JsonConverter(typeof(TimestampConverter))]
public DateTime CloseTime { get; set; }
}
}