Fix backup (#5)
This commit is contained in:
@@ -7,8 +7,8 @@ namespace Managing.Application.Abstractions;
|
|||||||
|
|
||||||
public interface IBotService
|
public interface IBotService
|
||||||
{
|
{
|
||||||
void SaveBotBackup(BotBackup botBackup);
|
void SaveOrUpdateBotBackup(BotBackup botBackup);
|
||||||
void SaveBotBackup(string name, Enums.BotType botType, string data);
|
void SaveOrUpdateBotBackup(string name, Enums.BotType botType, string data);
|
||||||
void AddSimpleBotToCache(IBot bot);
|
void AddSimpleBotToCache(IBot bot);
|
||||||
void AddTradingBotToCache(ITradingBot bot);
|
void AddTradingBotToCache(ITradingBot bot);
|
||||||
List<ITradingBot> GetActiveBots();
|
List<ITradingBot> GetActiveBots();
|
||||||
@@ -31,4 +31,5 @@ public interface IBotService
|
|||||||
Task<string> StopBot(string requestName);
|
Task<string> StopBot(string requestName);
|
||||||
Task<bool> DeleteBot(string requestName);
|
Task<bool> DeleteBot(string requestName);
|
||||||
Task<string> RestartBot(string requestName);
|
Task<string> RestartBot(string requestName);
|
||||||
|
void DeleteBotBackup(string backupBotName);
|
||||||
}
|
}
|
||||||
@@ -45,7 +45,7 @@ namespace Managing.Application.Bots
|
|||||||
public override void SaveBackup()
|
public override void SaveBackup()
|
||||||
{
|
{
|
||||||
var data = JsonConvert.SerializeObject(_workflow);
|
var data = JsonConvert.SerializeObject(_workflow);
|
||||||
_botService.SaveBotBackup(Name, BotType.SimpleBot, data);
|
_botService.SaveOrUpdateBotBackup(Name, BotType.SimpleBot, data);
|
||||||
}
|
}
|
||||||
|
|
||||||
public override void LoadBackup(BotBackup backup)
|
public override void LoadBackup(BotBackup backup)
|
||||||
|
|||||||
@@ -714,7 +714,7 @@ public class TradingBot : Bot, ITradingBot
|
|||||||
WalletBalances = WalletBalances,
|
WalletBalances = WalletBalances,
|
||||||
MoneyManagement = MoneyManagement
|
MoneyManagement = MoneyManagement
|
||||||
};
|
};
|
||||||
BotService.SaveBotBackup(Name, BotType, JsonConvert.SerializeObject(data));
|
BotService.SaveOrUpdateBotBackup(Name, BotType, JsonConvert.SerializeObject(data));
|
||||||
}
|
}
|
||||||
|
|
||||||
public override void LoadBackup(BotBackup backup)
|
public override void LoadBackup(BotBackup backup)
|
||||||
|
|||||||
@@ -35,7 +35,7 @@ namespace Managing.Application.ManageBot
|
|||||||
_tradingService = tradingService;
|
_tradingService = tradingService;
|
||||||
}
|
}
|
||||||
|
|
||||||
public async void SaveBotBackup(BotBackup botBackup)
|
public async void SaveOrUpdateBotBackup(BotBackup botBackup)
|
||||||
{
|
{
|
||||||
await _botRepository.InsertBotAsync(botBackup);
|
await _botRepository.InsertBotAsync(botBackup);
|
||||||
}
|
}
|
||||||
@@ -45,7 +45,7 @@ namespace Managing.Application.ManageBot
|
|||||||
return _botRepository.GetBots().FirstOrDefault(b => b.Name == name);
|
return _botRepository.GetBots().FirstOrDefault(b => b.Name == name);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void SaveBotBackup(string name, Enums.BotType botType, string data)
|
public void SaveOrUpdateBotBackup(string name, Enums.BotType botType, string data)
|
||||||
{
|
{
|
||||||
var backup = GetBotBackup(name);
|
var backup = GetBotBackup(name);
|
||||||
|
|
||||||
@@ -53,17 +53,18 @@ namespace Managing.Application.ManageBot
|
|||||||
{
|
{
|
||||||
backup.Data = data;
|
backup.Data = data;
|
||||||
_botRepository.UpdateBackupBot(backup);
|
_botRepository.UpdateBackupBot(backup);
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
|
else
|
||||||
var botBackup = new BotBackup
|
|
||||||
{
|
{
|
||||||
Name = name,
|
var botBackup = new BotBackup
|
||||||
BotType = botType,
|
{
|
||||||
Data = data
|
Name = name,
|
||||||
};
|
BotType = botType,
|
||||||
|
Data = data
|
||||||
|
};
|
||||||
|
|
||||||
_botRepository.InsertBotAsync(botBackup);
|
_botRepository.InsertBotAsync(botBackup);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public class BotTaskWrapper
|
public class BotTaskWrapper
|
||||||
@@ -197,6 +198,11 @@ namespace Managing.Application.ManageBot
|
|||||||
return Task.FromResult(Enums.BotStatus.Down.ToString());
|
return Task.FromResult(Enums.BotStatus.Down.ToString());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void DeleteBotBackup(string backupBotName)
|
||||||
|
{
|
||||||
|
_botRepository.DeleteBotBackup(backupBotName);
|
||||||
|
}
|
||||||
|
|
||||||
public ITradingBot CreateScalpingBot(string accountName, MoneyManagement moneyManagement, string name,
|
public ITradingBot CreateScalpingBot(string accountName, MoneyManagement moneyManagement, string name,
|
||||||
Enums.Ticker ticker, string scenario, Enums.Timeframe interval, bool isForWatchingOnly)
|
Enums.Ticker ticker, string scenario, Enums.Timeframe interval, bool isForWatchingOnly)
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -74,6 +74,7 @@ public class LoadBackupBotCommandHandler : IRequestHandler<LoadBackupBotCommand,
|
|||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
_logger.LogError($"Error loading bot {backupBot.Name}", ex.Message);
|
_logger.LogError($"Error loading bot {backupBot.Name}", ex.Message);
|
||||||
|
_botService.DeleteBotBackup(backupBot.Name);
|
||||||
result.Add(backupBot.Name, BotStatus.Down);
|
result.Add(backupBot.Name, BotStatus.Down);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -27,19 +27,16 @@ namespace Managing.Application.ManageBot
|
|||||||
{
|
{
|
||||||
case BotType.SimpleBot:
|
case BotType.SimpleBot:
|
||||||
var bot = _botFactory.CreateSimpleBot(request.Name, null);
|
var bot = _botFactory.CreateSimpleBot(request.Name, null);
|
||||||
bot.Start();
|
|
||||||
_botService.AddSimpleBotToCache(bot);
|
_botService.AddSimpleBotToCache(bot);
|
||||||
return Task.FromResult(bot.GetStatus());
|
return Task.FromResult(bot.GetStatus());
|
||||||
case BotType.ScalpingBot:
|
case BotType.ScalpingBot:
|
||||||
var sBot = _botFactory.CreateScalpingBot(request.AccountName, moneyManagement, request.Name,
|
var sBot = _botFactory.CreateScalpingBot(request.AccountName, moneyManagement, request.Name,
|
||||||
request.Ticker, request.Scenario, request.Timeframe, request.IsForWatchingOnly);
|
request.Ticker, request.Scenario, request.Timeframe, request.IsForWatchingOnly);
|
||||||
sBot.Start();
|
|
||||||
_botService.AddTradingBotToCache(sBot);
|
_botService.AddTradingBotToCache(sBot);
|
||||||
return Task.FromResult(sBot.GetStatus());
|
return Task.FromResult(sBot.GetStatus());
|
||||||
case BotType.FlippingBot:
|
case BotType.FlippingBot:
|
||||||
var fBot = _botFactory.CreateFlippingBot(request.AccountName, moneyManagement, request.Name,
|
var fBot = _botFactory.CreateFlippingBot(request.AccountName, moneyManagement, request.Name,
|
||||||
request.Ticker, request.Scenario, request.Timeframe, request.IsForWatchingOnly);
|
request.Ticker, request.Scenario, request.Timeframe, request.IsForWatchingOnly);
|
||||||
fBot.Start();
|
|
||||||
_botService.AddTradingBotToCache(fBot);
|
_botService.AddTradingBotToCache(fBot);
|
||||||
return Task.FromResult(fBot.GetStatus());
|
return Task.FromResult(fBot.GetStatus());
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -36,8 +36,13 @@ namespace Managing.Domain.Strategies
|
|||||||
return new List<Signal>();
|
return new List<Signal>();
|
||||||
}
|
}
|
||||||
|
|
||||||
public void UpdateCandles(HashSet<Candle> newCandles)
|
public void UpdateCandles(HashSet<Candle> newCandles)
|
||||||
{
|
{
|
||||||
|
if (newCandles == null || newCandles.Count == 0)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
lock (Candles)
|
lock (Candles)
|
||||||
{
|
{
|
||||||
foreach (var item in newCandles.ToList())
|
foreach (var item in newCandles.ToList())
|
||||||
@@ -55,4 +60,4 @@ namespace Managing.Domain.Strategies
|
|||||||
return Name;
|
return Name;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1,302 +1,311 @@
|
|||||||
import type {
|
import type {
|
||||||
CandlestickData,
|
CandlestickData,
|
||||||
IChartApi,
|
IChartApi,
|
||||||
ISeriesApi,
|
ISeriesApi,
|
||||||
PriceLineOptions,
|
PriceLineOptions,
|
||||||
SeriesMarker,
|
SeriesMarker,
|
||||||
SeriesMarkerShape,
|
SeriesMarkerShape,
|
||||||
Time,
|
Time,
|
||||||
UTCTimestamp,
|
UTCTimestamp,
|
||||||
} from 'lightweight-charts'
|
} from 'lightweight-charts'
|
||||||
import { LineStyle, createChart, CrosshairMode } from 'lightweight-charts'
|
import {LineStyle, createChart, CrosshairMode} from 'lightweight-charts'
|
||||||
import moment from 'moment'
|
import moment from 'moment'
|
||||||
import * as React from 'react'
|
import * as React from 'react'
|
||||||
import { useEffect, useRef, useState } from 'react'
|
import {useEffect, useRef, useState} from 'react'
|
||||||
|
|
||||||
import type {
|
import type {
|
||||||
Candle,
|
Candle,
|
||||||
KeyValuePairOfDateTimeAndDecimal,
|
KeyValuePairOfDateTimeAndDecimal,
|
||||||
Position,
|
Position,
|
||||||
Signal,
|
Signal,
|
||||||
} from '../../../../generated/ManagingApi'
|
} from '../../../../generated/ManagingApi'
|
||||||
import {
|
import {
|
||||||
PositionStatus,
|
PositionStatus,
|
||||||
TradeDirection,
|
TradeDirection,
|
||||||
} from '../../../../generated/ManagingApi'
|
} from '../../../../generated/ManagingApi'
|
||||||
import useTheme from '../../../../hooks/useTheme'
|
import useTheme from '../../../../hooks/useTheme'
|
||||||
|
|
||||||
type ITradeChartProps = {
|
type ITradeChartProps = {
|
||||||
candles: Candle[]
|
candles: Candle[]
|
||||||
positions: Position[]
|
positions: Position[]
|
||||||
signals: Signal[]
|
signals: Signal[]
|
||||||
walletBalances?: KeyValuePairOfDateTimeAndDecimal[] | null
|
walletBalances?: KeyValuePairOfDateTimeAndDecimal[] | null
|
||||||
stream?: Candle | null
|
stream?: Candle | null
|
||||||
width: number
|
width: number
|
||||||
height: number
|
height: number
|
||||||
}
|
}
|
||||||
|
|
||||||
const TradeChart = ({
|
const TradeChart = ({
|
||||||
candles,
|
candles,
|
||||||
positions,
|
positions,
|
||||||
signals,
|
signals,
|
||||||
walletBalances,
|
walletBalances,
|
||||||
stream,
|
stream,
|
||||||
width,
|
width,
|
||||||
height,
|
height,
|
||||||
}: ITradeChartProps) => {
|
}: ITradeChartProps) => {
|
||||||
const chartRef = React.useRef<HTMLDivElement>(null)
|
const chartRef = React.useRef<HTMLDivElement>(null)
|
||||||
const chart = useRef<IChartApi>()
|
const chart = useRef<IChartApi>()
|
||||||
const { themeProperty } = useTheme()
|
const {themeProperty} = useTheme()
|
||||||
const theme = themeProperty()
|
const theme = themeProperty()
|
||||||
const series1 = useRef<ISeriesApi<'Candlestick'>>()
|
const series1 = useRef<ISeriesApi<'Candlestick'>>()
|
||||||
const [timeDiff, setTimeDiff] = useState<number>(0)
|
const [timeDiff, setTimeDiff] = useState<number>(0)
|
||||||
const [candleCount, setCandleCount] = useState<number>(candles.length)
|
const [candleCount, setCandleCount] = useState<number>(candles.length)
|
||||||
|
|
||||||
function buildLine(
|
function buildLine(
|
||||||
color: string,
|
color: string,
|
||||||
price: number,
|
price: number,
|
||||||
title: string
|
title: string
|
||||||
): PriceLineOptions {
|
): PriceLineOptions {
|
||||||
return {
|
|
||||||
axisLabelVisible: true,
|
|
||||||
color: color,
|
|
||||||
lineStyle: LineStyle.Dotted,
|
|
||||||
lineVisible: true,
|
|
||||||
lineWidth: 1,
|
|
||||||
price: price,
|
|
||||||
title: title,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function buildMarker(
|
|
||||||
shape: SeriesMarkerShape,
|
|
||||||
color: string,
|
|
||||||
direction: TradeDirection,
|
|
||||||
date: Date,
|
|
||||||
text?: string
|
|
||||||
): SeriesMarker<Time> {
|
|
||||||
return {
|
|
||||||
color: color,
|
|
||||||
position: direction == TradeDirection.Long ? 'belowBar' : 'aboveBar',
|
|
||||||
shape: shape,
|
|
||||||
size: 1,
|
|
||||||
text: text,
|
|
||||||
time: moment(date).unix() as UTCTimestamp,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function mapCandle(c: Candle): CandlestickData {
|
|
||||||
return {
|
|
||||||
close: c.close,
|
|
||||||
high: c.high,
|
|
||||||
low: c.low,
|
|
||||||
open: c.open,
|
|
||||||
time: moment(c.date).unix() as UTCTimestamp,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function mergeTickToBar(candle: CandlestickData) {
|
|
||||||
const previousCandle = series1.current?.dataByIndex(
|
|
||||||
candleCount - 1
|
|
||||||
) as CandlestickData
|
|
||||||
|
|
||||||
if ((candle.time as number) - (previousCandle?.time as number) > timeDiff) {
|
|
||||||
series1.current?.update(candle)
|
|
||||||
setCandleCount((prev) => prev + 1)
|
|
||||||
} else {
|
|
||||||
previousCandle.close = candle.close
|
|
||||||
previousCandle.high = Math.max(previousCandle.high, candle.high)
|
|
||||||
previousCandle.low = Math.min(previousCandle.low, candle.low)
|
|
||||||
series1.current?.update(previousCandle)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function getPositionColor(position: Position) {
|
|
||||||
let color = 'mintcream'
|
|
||||||
if (position == undefined) return color
|
|
||||||
|
|
||||||
const negativeColor = 'palevioletred'
|
|
||||||
const positiveColor = 'lightgreen'
|
|
||||||
const status = position.status
|
|
||||||
const realized = position.profitAndLoss?.realized ?? 0
|
|
||||||
|
|
||||||
if (status != undefined) {
|
|
||||||
if (
|
|
||||||
status == PositionStatus.Finished ||
|
|
||||||
status == PositionStatus.Flipped
|
|
||||||
) {
|
|
||||||
if (realized > 0) {
|
|
||||||
color = positiveColor
|
|
||||||
} else {
|
|
||||||
color = negativeColor
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (position.profitAndLoss?.realized == null) {
|
|
||||||
color = 'yellow'
|
|
||||||
}
|
|
||||||
|
|
||||||
return color
|
|
||||||
}
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
if (chartRef.current) {
|
|
||||||
const lineColor = theme.secondary
|
|
||||||
chart.current = createChart(chartRef.current, {
|
|
||||||
crosshair: {
|
|
||||||
mode: CrosshairMode.Normal,
|
|
||||||
},
|
|
||||||
grid: {
|
|
||||||
horzLines: {
|
|
||||||
color: lineColor,
|
|
||||||
visible: false,
|
|
||||||
},
|
|
||||||
vertLines: {
|
|
||||||
color: lineColor,
|
|
||||||
visible: false,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
height: height,
|
|
||||||
layout: {
|
|
||||||
background: { color: '#121212' },
|
|
||||||
textColor: theme.secondary,
|
|
||||||
},
|
|
||||||
localization: {
|
|
||||||
dateFormat: 'yyyy-MM-dd',
|
|
||||||
},
|
|
||||||
rightPriceScale: {
|
|
||||||
autoScale: true,
|
|
||||||
borderColor: lineColor,
|
|
||||||
borderVisible: true,
|
|
||||||
},
|
|
||||||
timeScale: {
|
|
||||||
borderColor: lineColor,
|
|
||||||
lockVisibleTimeRangeOnResize: true,
|
|
||||||
secondsVisible: true,
|
|
||||||
timeVisible: true,
|
|
||||||
},
|
|
||||||
width: width,
|
|
||||||
})
|
|
||||||
|
|
||||||
prepareChart()
|
|
||||||
}
|
|
||||||
}, [])
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
if (series1.current === null) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
if (stream && candles.length > 1 && timeDiff) {
|
|
||||||
const c = mapCandle(stream)
|
|
||||||
mergeTickToBar(c)
|
|
||||||
}
|
|
||||||
}, [stream])
|
|
||||||
|
|
||||||
function prepareChart() {
|
|
||||||
if (!chart.current) return
|
|
||||||
|
|
||||||
series1.current = chart.current.addCandlestickSeries({
|
|
||||||
borderDownColor: theme.secondary,
|
|
||||||
borderUpColor: theme.primary,
|
|
||||||
downColor: theme.secondary,
|
|
||||||
upColor: theme.primary,
|
|
||||||
wickDownColor: theme.secondary,
|
|
||||||
wickUpColor: theme.primary,
|
|
||||||
})
|
|
||||||
|
|
||||||
const data: CandlestickData[] = candles.map((c) => mapCandle(c))
|
|
||||||
series1.current.setData(data)
|
|
||||||
series1.current.applyOptions({
|
|
||||||
priceFormat: {
|
|
||||||
minMove: 0.0001,
|
|
||||||
precision: 4,
|
|
||||||
type: 'price',
|
|
||||||
},
|
|
||||||
})
|
|
||||||
|
|
||||||
const diff =
|
|
||||||
(data[data.length - 2].time as number) -
|
|
||||||
(data[data.length - 3].time as number)
|
|
||||||
setTimeDiff(diff)
|
|
||||||
setCandleCount(data.length)
|
|
||||||
|
|
||||||
const markers: SeriesMarker<Time>[] = []
|
|
||||||
|
|
||||||
if (signals) {
|
|
||||||
const signalMarkers = signals.map((s) =>
|
|
||||||
buildMarker(
|
|
||||||
'circle',
|
|
||||||
s.direction == TradeDirection.Long ? theme.success : theme.error,
|
|
||||||
s.direction,
|
|
||||||
s.date,
|
|
||||||
undefined
|
|
||||||
)
|
|
||||||
)
|
|
||||||
markers.push(...signalMarkers)
|
|
||||||
}
|
|
||||||
|
|
||||||
if (positions) {
|
|
||||||
const positionMarkers = positions.map((p) =>
|
|
||||||
buildMarker(
|
|
||||||
p.originDirection == TradeDirection.Long ? 'arrowUp' : 'arrowDown',
|
|
||||||
getPositionColor(p),
|
|
||||||
p.originDirection,
|
|
||||||
p.date,
|
|
||||||
p.open.price.toString()
|
|
||||||
)
|
|
||||||
)
|
|
||||||
|
|
||||||
markers.push(...positionMarkers)
|
|
||||||
|
|
||||||
const lastPositionOpen = positions[positions.length - 1]
|
|
||||||
|
|
||||||
if (lastPositionOpen) {
|
|
||||||
series1.current.createPriceLine(
|
|
||||||
buildLine(theme.error, lastPositionOpen.stopLoss.price, 'SL')
|
|
||||||
)
|
|
||||||
series1.current.createPriceLine(
|
|
||||||
buildLine(theme.success, lastPositionOpen.takeProfit1.price, 'TP')
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (markers.length > 0) {
|
|
||||||
series1.current.setMarkers(markers)
|
|
||||||
}
|
|
||||||
|
|
||||||
if (walletBalances != null) {
|
|
||||||
const walletSeries = chart.current.addBaselineSeries({
|
|
||||||
baseValue: { price: walletBalances[0].value, type: 'price' },
|
|
||||||
bottomFillColor1: 'rgba( 239, 83, 80, 0.05)',
|
|
||||||
bottomFillColor2: 'rgba( 239, 83, 80, 0.28)',
|
|
||||||
bottomLineColor: 'rgba( 239, 83, 80, 1)',
|
|
||||||
pane: 1,
|
|
||||||
topFillColor1: 'rgba( 38, 166, 154, 0.28)',
|
|
||||||
topFillColor2: 'rgba( 38, 166, 154, 0.05)',
|
|
||||||
topLineColor: 'rgba( 38, 166, 154, 1)',
|
|
||||||
})
|
|
||||||
|
|
||||||
const walletData = walletBalances.map((w) => {
|
|
||||||
return {
|
return {
|
||||||
time: moment(w.key).unix(),
|
axisLabelVisible: true,
|
||||||
value: w.value,
|
color: color,
|
||||||
|
lineStyle: LineStyle.Dotted,
|
||||||
|
lineVisible: true,
|
||||||
|
lineWidth: 1,
|
||||||
|
price: price,
|
||||||
|
title: title,
|
||||||
}
|
}
|
||||||
})
|
|
||||||
// @ts-ignore
|
|
||||||
walletSeries.setData(walletData)
|
|
||||||
walletSeries.applyOptions({
|
|
||||||
priceFormat: {
|
|
||||||
minMove: 0.0001,
|
|
||||||
precision: 4,
|
|
||||||
type: 'price',
|
|
||||||
},
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
return <div ref={chartRef} />
|
function buildMarker(
|
||||||
|
shape: SeriesMarkerShape,
|
||||||
|
color: string,
|
||||||
|
direction: TradeDirection,
|
||||||
|
date: Date,
|
||||||
|
text?: string
|
||||||
|
): SeriesMarker<Time> {
|
||||||
|
return {
|
||||||
|
color: color,
|
||||||
|
position: direction == TradeDirection.Long ? 'belowBar' : 'aboveBar',
|
||||||
|
shape: shape,
|
||||||
|
size: 1,
|
||||||
|
text: text,
|
||||||
|
time: moment(date).unix() as UTCTimestamp,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function mapCandle(c: Candle): CandlestickData {
|
||||||
|
return {
|
||||||
|
close: c.close,
|
||||||
|
high: c.high,
|
||||||
|
low: c.low,
|
||||||
|
open: c.open,
|
||||||
|
time: moment(c.date).unix() as UTCTimestamp,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function mergeTickToBar(candle: CandlestickData) {
|
||||||
|
const previousCandle = series1.current?.dataByIndex(
|
||||||
|
candleCount - 1
|
||||||
|
) as CandlestickData
|
||||||
|
|
||||||
|
if ((candle.time as number) - (previousCandle?.time as number) > timeDiff) {
|
||||||
|
series1.current?.update(candle)
|
||||||
|
setCandleCount((prev) => prev + 1)
|
||||||
|
} else {
|
||||||
|
previousCandle.close = candle.close
|
||||||
|
previousCandle.high = Math.max(previousCandle.high, candle.high)
|
||||||
|
previousCandle.low = Math.min(previousCandle.low, candle.low)
|
||||||
|
series1.current?.update(previousCandle)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function getPositionColor(position: Position) {
|
||||||
|
let color = 'mintcream'
|
||||||
|
if (position == undefined) return color
|
||||||
|
|
||||||
|
const negativeColor = 'palevioletred'
|
||||||
|
const positiveColor = 'lightgreen'
|
||||||
|
const status = position.status
|
||||||
|
const realized = position.profitAndLoss?.realized ?? 0
|
||||||
|
|
||||||
|
if (status != undefined) {
|
||||||
|
if (
|
||||||
|
status == PositionStatus.Finished ||
|
||||||
|
status == PositionStatus.Flipped
|
||||||
|
) {
|
||||||
|
if (realized > 0) {
|
||||||
|
color = positiveColor
|
||||||
|
} else {
|
||||||
|
color = negativeColor
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (position.profitAndLoss?.realized == null) {
|
||||||
|
color = 'yellow'
|
||||||
|
}
|
||||||
|
|
||||||
|
return color
|
||||||
|
}
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (chartRef.current) {
|
||||||
|
const lineColor = theme.secondary
|
||||||
|
chart.current = createChart(chartRef.current, {
|
||||||
|
crosshair: {
|
||||||
|
mode: CrosshairMode.Normal,
|
||||||
|
},
|
||||||
|
grid: {
|
||||||
|
horzLines: {
|
||||||
|
color: lineColor,
|
||||||
|
visible: false,
|
||||||
|
},
|
||||||
|
vertLines: {
|
||||||
|
color: lineColor,
|
||||||
|
visible: false,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
height: height,
|
||||||
|
layout: {
|
||||||
|
background: {color: '#121212'},
|
||||||
|
textColor: theme.secondary,
|
||||||
|
},
|
||||||
|
localization: {
|
||||||
|
dateFormat: 'yyyy-MM-dd',
|
||||||
|
},
|
||||||
|
rightPriceScale: {
|
||||||
|
autoScale: true,
|
||||||
|
borderColor: lineColor,
|
||||||
|
borderVisible: true,
|
||||||
|
},
|
||||||
|
timeScale: {
|
||||||
|
borderColor: lineColor,
|
||||||
|
lockVisibleTimeRangeOnResize: true,
|
||||||
|
secondsVisible: true,
|
||||||
|
timeVisible: true,
|
||||||
|
},
|
||||||
|
width: width,
|
||||||
|
})
|
||||||
|
|
||||||
|
prepareChart()
|
||||||
|
}
|
||||||
|
}, [])
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (series1.current === null) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if (stream && candles.length > 1 && timeDiff) {
|
||||||
|
const c = mapCandle(stream)
|
||||||
|
mergeTickToBar(c)
|
||||||
|
}
|
||||||
|
}, [stream])
|
||||||
|
|
||||||
|
function prepareChart() {
|
||||||
|
if (!chart.current) return
|
||||||
|
|
||||||
|
series1.current = chart.current.addCandlestickSeries({
|
||||||
|
borderDownColor: theme.secondary,
|
||||||
|
borderUpColor: theme.primary,
|
||||||
|
downColor: theme.secondary,
|
||||||
|
upColor: theme.primary,
|
||||||
|
wickDownColor: theme.secondary,
|
||||||
|
wickUpColor: theme.primary,
|
||||||
|
})
|
||||||
|
|
||||||
|
const data: CandlestickData[] = candles.map((c) => mapCandle(c))
|
||||||
|
let diff = 0; // Default to 0 if there's not enough data to calculate the difference
|
||||||
|
|
||||||
|
console.log(data)
|
||||||
|
console.log(data.length)
|
||||||
|
|
||||||
|
if (data.length > 3) {
|
||||||
|
diff =
|
||||||
|
(data[data.length - 1].time as number) -
|
||||||
|
(data[data.length - 2].time as number);
|
||||||
|
}
|
||||||
|
|
||||||
|
setTimeDiff(diff)
|
||||||
|
setCandleCount(data.length)
|
||||||
|
|
||||||
|
series1.current.setData(data)
|
||||||
|
series1.current.applyOptions({
|
||||||
|
priceFormat: {
|
||||||
|
minMove: 0.0001,
|
||||||
|
precision: 4,
|
||||||
|
type: 'price',
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
|
||||||
|
const markers: SeriesMarker<Time>[] = []
|
||||||
|
|
||||||
|
if (signals) {
|
||||||
|
const signalMarkers = signals.map((s) =>
|
||||||
|
buildMarker(
|
||||||
|
'circle',
|
||||||
|
s.direction == TradeDirection.Long ? theme.success : theme.error,
|
||||||
|
s.direction,
|
||||||
|
s.date,
|
||||||
|
undefined
|
||||||
|
)
|
||||||
|
)
|
||||||
|
markers.push(...signalMarkers)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (positions) {
|
||||||
|
const positionMarkers = positions.map((p) =>
|
||||||
|
buildMarker(
|
||||||
|
p.originDirection == TradeDirection.Long ? 'arrowUp' : 'arrowDown',
|
||||||
|
getPositionColor(p),
|
||||||
|
p.originDirection,
|
||||||
|
p.date,
|
||||||
|
p.open.price.toString()
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
markers.push(...positionMarkers)
|
||||||
|
|
||||||
|
const lastPositionOpen = positions[positions.length - 1]
|
||||||
|
|
||||||
|
if (lastPositionOpen) {
|
||||||
|
series1.current.createPriceLine(
|
||||||
|
buildLine(theme.error, lastPositionOpen.stopLoss.price, 'SL')
|
||||||
|
)
|
||||||
|
series1.current.createPriceLine(
|
||||||
|
buildLine(theme.success, lastPositionOpen.takeProfit1.price, 'TP')
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (markers.length > 0) {
|
||||||
|
series1.current.setMarkers(markers)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (walletBalances != null) {
|
||||||
|
const walletSeries = chart.current.addBaselineSeries({
|
||||||
|
baseValue: {price: walletBalances[0].value, type: 'price'},
|
||||||
|
bottomFillColor1: 'rgba( 239, 83, 80, 0.05)',
|
||||||
|
bottomFillColor2: 'rgba( 239, 83, 80, 0.28)',
|
||||||
|
bottomLineColor: 'rgba( 239, 83, 80, 1)',
|
||||||
|
pane: 1,
|
||||||
|
topFillColor1: 'rgba( 38, 166, 154, 0.28)',
|
||||||
|
topFillColor2: 'rgba( 38, 166, 154, 0.05)',
|
||||||
|
topLineColor: 'rgba( 38, 166, 154, 1)',
|
||||||
|
})
|
||||||
|
|
||||||
|
const walletData = walletBalances.map((w) => {
|
||||||
|
return {
|
||||||
|
time: moment(w.key).unix(),
|
||||||
|
value: w.value,
|
||||||
|
}
|
||||||
|
})
|
||||||
|
// @ts-ignore
|
||||||
|
walletSeries.setData(walletData)
|
||||||
|
walletSeries.applyOptions({
|
||||||
|
priceFormat: {
|
||||||
|
minMove: 0.0001,
|
||||||
|
precision: 4,
|
||||||
|
type: 'price',
|
||||||
|
},
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return <div ref={chartRef}/>
|
||||||
}
|
}
|
||||||
|
|
||||||
export default TradeChart
|
export default TradeChart
|
||||||
|
|||||||
Reference in New Issue
Block a user