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
{
void SaveBotBackup(BotBackup botBackup);
void SaveBotBackup(string name, Enums.BotType botType, string data);
void SaveOrUpdateBotBackup(BotBackup botBackup);
void SaveOrUpdateBotBackup(string name, Enums.BotType botType, string data);
void AddSimpleBotToCache(IBot bot);
void AddTradingBotToCache(ITradingBot bot);
List<ITradingBot> GetActiveBots();
@@ -31,4 +31,5 @@ public interface IBotService
Task<string> StopBot(string requestName);
Task<bool> DeleteBot(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()
{
var data = JsonConvert.SerializeObject(_workflow);
_botService.SaveBotBackup(Name, BotType.SimpleBot, data);
_botService.SaveOrUpdateBotBackup(Name, BotType.SimpleBot, data);
}
public override void LoadBackup(BotBackup backup)

View File

@@ -714,7 +714,7 @@ public class TradingBot : Bot, ITradingBot
WalletBalances = WalletBalances,
MoneyManagement = MoneyManagement
};
BotService.SaveBotBackup(Name, BotType, JsonConvert.SerializeObject(data));
BotService.SaveOrUpdateBotBackup(Name, BotType, JsonConvert.SerializeObject(data));
}
public override void LoadBackup(BotBackup backup)

View File

@@ -35,7 +35,7 @@ namespace Managing.Application.ManageBot
_tradingService = tradingService;
}
public async void SaveBotBackup(BotBackup botBackup)
public async void SaveOrUpdateBotBackup(BotBackup botBackup)
{
await _botRepository.InsertBotAsync(botBackup);
}
@@ -45,7 +45,7 @@ namespace Managing.Application.ManageBot
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);
@@ -53,17 +53,18 @@ namespace Managing.Application.ManageBot
{
backup.Data = data;
_botRepository.UpdateBackupBot(backup);
return;
}
var botBackup = new BotBackup
else
{
Name = name,
BotType = botType,
Data = data
};
var botBackup = new BotBackup
{
Name = name,
BotType = botType,
Data = data
};
_botRepository.InsertBotAsync(botBackup);
_botRepository.InsertBotAsync(botBackup);
}
}
public class BotTaskWrapper
@@ -197,6 +198,11 @@ namespace Managing.Application.ManageBot
return Task.FromResult(Enums.BotStatus.Down.ToString());
}
public void DeleteBotBackup(string backupBotName)
{
_botRepository.DeleteBotBackup(backupBotName);
}
public ITradingBot CreateScalpingBot(string accountName, MoneyManagement moneyManagement, string name,
Enums.Ticker ticker, string scenario, Enums.Timeframe interval, bool isForWatchingOnly)
{

View File

@@ -74,6 +74,7 @@ public class LoadBackupBotCommandHandler : IRequestHandler<LoadBackupBotCommand,
catch (Exception ex)
{
_logger.LogError($"Error loading bot {backupBot.Name}", ex.Message);
_botService.DeleteBotBackup(backupBot.Name);
result.Add(backupBot.Name, BotStatus.Down);
}
}

View File

@@ -27,19 +27,16 @@ namespace Managing.Application.ManageBot
{
case BotType.SimpleBot:
var bot = _botFactory.CreateSimpleBot(request.Name, null);
bot.Start();
_botService.AddSimpleBotToCache(bot);
return Task.FromResult(bot.GetStatus());
case BotType.ScalpingBot:
var sBot = _botFactory.CreateScalpingBot(request.AccountName, moneyManagement, request.Name,
request.Ticker, request.Scenario, request.Timeframe, request.IsForWatchingOnly);
sBot.Start();
_botService.AddTradingBotToCache(sBot);
return Task.FromResult(sBot.GetStatus());
case BotType.FlippingBot:
var fBot = _botFactory.CreateFlippingBot(request.AccountName, moneyManagement, request.Name,
request.Ticker, request.Scenario, request.Timeframe, request.IsForWatchingOnly);
fBot.Start();
_botService.AddTradingBotToCache(fBot);
return Task.FromResult(fBot.GetStatus());
}

View File

@@ -36,8 +36,13 @@ namespace Managing.Domain.Strategies
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)
{
foreach (var item in newCandles.ToList())
@@ -55,4 +60,4 @@ namespace Managing.Domain.Strategies
return Name;
}
}
}
}

View File

@@ -1,302 +1,311 @@
import type {
CandlestickData,
IChartApi,
ISeriesApi,
PriceLineOptions,
SeriesMarker,
SeriesMarkerShape,
Time,
UTCTimestamp,
CandlestickData,
IChartApi,
ISeriesApi,
PriceLineOptions,
SeriesMarker,
SeriesMarkerShape,
Time,
UTCTimestamp,
} from 'lightweight-charts'
import { LineStyle, createChart, CrosshairMode } from 'lightweight-charts'
import {LineStyle, createChart, CrosshairMode} from 'lightweight-charts'
import moment from 'moment'
import * as React from 'react'
import { useEffect, useRef, useState } from 'react'
import {useEffect, useRef, useState} from 'react'
import type {
Candle,
KeyValuePairOfDateTimeAndDecimal,
Position,
Signal,
Candle,
KeyValuePairOfDateTimeAndDecimal,
Position,
Signal,
} from '../../../../generated/ManagingApi'
import {
PositionStatus,
TradeDirection,
PositionStatus,
TradeDirection,
} from '../../../../generated/ManagingApi'
import useTheme from '../../../../hooks/useTheme'
type ITradeChartProps = {
candles: Candle[]
positions: Position[]
signals: Signal[]
walletBalances?: KeyValuePairOfDateTimeAndDecimal[] | null
stream?: Candle | null
width: number
height: number
candles: Candle[]
positions: Position[]
signals: Signal[]
walletBalances?: KeyValuePairOfDateTimeAndDecimal[] | null
stream?: Candle | null
width: number
height: number
}
const TradeChart = ({
candles,
positions,
signals,
walletBalances,
stream,
width,
height,
}: ITradeChartProps) => {
const chartRef = React.useRef<HTMLDivElement>(null)
const chart = useRef<IChartApi>()
const { themeProperty } = useTheme()
const theme = themeProperty()
const series1 = useRef<ISeriesApi<'Candlestick'>>()
const [timeDiff, setTimeDiff] = useState<number>(0)
const [candleCount, setCandleCount] = useState<number>(candles.length)
candles,
positions,
signals,
walletBalances,
stream,
width,
height,
}: ITradeChartProps) => {
const chartRef = React.useRef<HTMLDivElement>(null)
const chart = useRef<IChartApi>()
const {themeProperty} = useTheme()
const theme = themeProperty()
const series1 = useRef<ISeriesApi<'Candlestick'>>()
const [timeDiff, setTimeDiff] = useState<number>(0)
const [candleCount, setCandleCount] = useState<number>(candles.length)
function buildLine(
color: string,
price: number,
title: string
): 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) => {
function buildLine(
color: string,
price: number,
title: string
): PriceLineOptions {
return {
time: moment(w.key).unix(),
value: w.value,
axisLabelVisible: true,
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