Fix backup (#5)

This commit is contained in:
Oda
2024-07-20 23:48:12 +07:00
committed by GitHub
parent 743d04e6c5
commit 3360e48f47
8 changed files with 317 additions and 298 deletions

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -38,6 +38,11 @@ namespace Managing.Domain.Strategies
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())

View File

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