docker files fixes from liaqat

This commit is contained in:
alirehmani
2024-05-03 16:39:25 +05:00
commit 464a8730e8
587 changed files with 44288 additions and 0 deletions

View File

@@ -0,0 +1,111 @@
using Microsoft.Extensions.Logging;
using static Managing.Common.Enums;
using Managing.Application.Abstractions;
using Managing.Application.Abstractions.Services;
using Managing.Domain.MoneyManagements;
using Managing.Domain.Workflows;
using Managing.Domain.Bots;
namespace Managing.Application.Bots.Base
{
public class BotFactory : IBotFactory
{
private readonly IMoneyManagementService _moneyManagementService;
private readonly IExchangeService _exchangeService;
private readonly IMessengerService _messengerService;
private readonly IAccountService _accountService;
private readonly ILogger<TradingBot> _tradingBotLogger;
private readonly ITradingService _tradingService;
public BotFactory(
IExchangeService exchangeService,
ILogger<TradingBot> tradingBotLogger,
IMoneyManagementService moneyManagementService,
IMessengerService messengerService,
IAccountService accountService,
ITradingService tradingService)
{
_tradingBotLogger = tradingBotLogger;
_exchangeService = exchangeService;
_moneyManagementService = moneyManagementService;
_messengerService = messengerService;
_accountService = accountService;
_tradingService = tradingService;
}
IBot IBotFactory.CreateSimpleBot(string botName, Workflow workflow)
{
return new SimpleBot(botName, _tradingBotLogger, workflow);
}
ITradingBot IBotFactory.CreateScalpingBot(string accountName, MoneyManagement moneyManagement, string name, Ticker ticker, string scenario, Timeframe interval, bool isForWatchingOnly)
{
return new ScalpingBot(
accountName,
moneyManagement,
name,
scenario,
_exchangeService,
ticker,
_tradingService,
_tradingBotLogger,
interval,
_accountService,
_messengerService,
isForWatchingOnly: isForWatchingOnly);
}
ITradingBot IBotFactory.CreateBacktestScalpingBot(string accountName, MoneyManagement moneyManagement, Ticker ticker, string scenario, Timeframe interval, bool isForWatchingOnly)
{
return new ScalpingBot(
accountName,
moneyManagement,
"BacktestBot",
scenario,
_exchangeService,
ticker,
_tradingService,
_tradingBotLogger,
interval,
_accountService,
_messengerService,
true,
isForWatchingOnly);
}
public ITradingBot CreateFlippingBot(string accountName, MoneyManagement moneyManagement, string name, Ticker ticker, string scenario, Timeframe interval, bool isForWatchingOnly)
{
return new FlippingBot(
accountName,
moneyManagement,
name,
scenario,
_exchangeService,
ticker,
_tradingService,
_tradingBotLogger,
interval,
_accountService,
_messengerService,
isForWatchingOnly: isForWatchingOnly);
}
public ITradingBot CreateBacktestFlippingBot(string accountName, MoneyManagement moneyManagement, Ticker ticker, string scenario, Timeframe interval, bool isForWatchingOnly)
{
return new FlippingBot(
accountName,
moneyManagement,
"BacktestBot",
scenario,
_exchangeService,
ticker,
_tradingService,
_tradingBotLogger,
interval,
_accountService,
_messengerService,
true,
isForWatchingOnly);
}
}
}

View File

@@ -0,0 +1,49 @@
using Microsoft.Extensions.Logging;
using static Managing.Common.Enums;
using Managing.Application.Abstractions.Services;
using Managing.Domain.MoneyManagements;
namespace Managing.Application.Bots
{
public class FlippingBot : TradingBot
{
public FlippingBot(string accountName,
MoneyManagement moneyManagement,
string name,
string scenario,
IExchangeService exchangeService,
Ticker ticker,
ITradingService tradingService,
ILogger<TradingBot> logger,
Timeframe timeframe,
IAccountService accountService,
IMessengerService messengerService,
bool isForBacktest = false,
bool isForWatchingOnly = false)
: base(accountName,
moneyManagement,
name,
ticker,
scenario,
exchangeService,
logger,
tradingService,
timeframe,
accountService,
messengerService,
isForBacktest,
isForWatchingOnly,
flipPosition: true)
{
BotType = BotType.FlippingBot;
Start();
}
public sealed override void Start()
{
Logger.LogInformation($"{Name} - Bot Started");
base.Start();
Logger.LogInformation($"Starting {Name} bot - Status : {Status}");
}
}
}

View File

@@ -0,0 +1,48 @@
using Microsoft.Extensions.Logging;
using static Managing.Common.Enums;
using Managing.Application.Abstractions.Services;
using Managing.Domain.MoneyManagements;
namespace Managing.Application.Bots
{
public class ScalpingBot : TradingBot
{
public ScalpingBot(string accountName,
MoneyManagement moneyManagement,
string name,
string scenario,
IExchangeService exchangeService,
Ticker ticker,
ITradingService tradingService,
ILogger<TradingBot> logger,
Timeframe timeframe,
IAccountService accountService,
IMessengerService messengerService,
bool isForBacktest = false,
bool isForWatchingOnly = false)
: base(accountName,
moneyManagement,
name,
ticker,
scenario,
exchangeService,
logger,
tradingService,
timeframe,
accountService,
messengerService,
isForBacktest,
isForWatchingOnly)
{
BotType = BotType.ScalpingBot;
Start();
}
public sealed override void Start()
{
Logger.LogInformation($"{Name} - Bot Started");
base.Start();
Logger.LogInformation($"Starting {Name} bot - Status : {Status}");
}
}
}

View File

@@ -0,0 +1,39 @@
using Managing.Domain.Bots;
using Managing.Domain.Workflows;
using Microsoft.Extensions.Logging;
namespace Managing.Application.Bots
{
public class SimpleBot : Bot
{
public readonly ILogger<TradingBot> Logger;
private readonly Workflow _workflow;
public SimpleBot(string name, ILogger<TradingBot> logger, Workflow workflow) : base(name)
{
Logger = logger;
_workflow = workflow;
Interval = 100;
Start();
}
public override void Start()
{
Task.Run(() => InitWorker(Run));
base.Start();
}
public async Task Run()
{
await Task.Run(
async () =>
{
Logger.LogInformation(Identifier);
Logger.LogInformation(DateTime.Now.ToString());
await _workflow.Execute();
Logger.LogInformation("__________________________________________________");
});
}
}
}

View File

@@ -0,0 +1,651 @@
using Managing.Application.Abstractions;
using Managing.Application.Abstractions.Services;
using Managing.Application.Trading;
using Managing.Application.Trading.Commands;
using Managing.Domain.Accounts;
using Managing.Domain.Bots;
using Managing.Domain.Candles;
using Managing.Domain.MoneyManagements;
using Managing.Domain.Scenarios;
using Managing.Domain.Shared.Helpers;
using Managing.Domain.Strategies;
using Managing.Domain.Trades;
using Microsoft.Extensions.Logging;
using static Managing.Common.Enums;
namespace Managing.Application.Bots;
public class TradingBot : Bot, ITradingBot
{
public readonly ILogger<TradingBot> Logger;
public readonly IExchangeService ExchangeService;
public readonly IMessengerService MessengerService;
public readonly IAccountService AccountService;
private readonly ITradingService TradingService;
public Account Account { get; set; }
public HashSet<IStrategy> Strategies { get; set; }
public HashSet<Candle> Candles { get; set; }
public HashSet<Signal> Signals { get; set; }
public List<Position> Positions { get; set; }
public Ticker Ticker { get; set; }
public string Scenario { get; set; }
public string AccountName { get; set; }
public MoneyManagement MoneyManagement { get; set; }
public Timeframe Timeframe { get; set; }
public bool IsForBacktest { get; set; }
public DateTime PreloadSince { get; set; }
public bool IsForWatchingOnly { get; set; }
public bool FlipPosition { get; set; }
public int PreloadedCandlesCount { get; set; }
public BotType BotType { get; set; }
public decimal Fee { get; set; }
public Dictionary<DateTime, decimal> WalletBalances { get; set; }
public TradingBot(
string accountName,
MoneyManagement moneyManagement,
string name,
Ticker ticker,
string scenario,
IExchangeService exchangeService,
ILogger<TradingBot> logger,
ITradingService tradingService,
Timeframe timeframe,
IAccountService accountService,
IMessengerService messengerService,
bool isForBacktest = false,
bool isForWatchingOnly = false,
bool flipPosition = false)
: base(name)
{
ExchangeService = exchangeService;
AccountService = accountService;
MessengerService = messengerService;
TradingService = tradingService;
IsForWatchingOnly = isForWatchingOnly;
FlipPosition = flipPosition;
AccountName = accountName;
MoneyManagement = moneyManagement;
Ticker = ticker;
Scenario = scenario;
Timeframe = timeframe;
IsForBacktest = isForBacktest;
Logger = logger;
Strategies = new HashSet<IStrategy>();
Signals = new HashSet<Signal>();
Candles = new HashSet<Candle>();
Positions = new List<Position>();
WalletBalances = new Dictionary<DateTime, decimal>();
if (!isForBacktest)
{
Interval = CandleExtensions.GetIntervalFromTimeframe(timeframe);
PreloadSince = CandleExtensions.GetBotPreloadSinceFromTimeframe(timeframe);
}
}
public override async void Start()
{
base.Start();
await LoadAccount();
if (!IsForBacktest)
{
LoadScenario();
await PreloadCandles();
await CancelAllOrders();
await MessengerService.SendMessage($"Hi everyone, I'm going to run {Name}. \nI will send a message here everytime a signal is triggered by the {string.Join(",", Strategies.Select(s => s.Name))} strategies.");
await InitWorker(Run);
}
Fee = TradingService.GetFee(Account, IsForBacktest);
}
private async Task LoadAccount()
{
var account = await AccountService.GetAccount(AccountName, false, true);
if (account == null)
{
Logger.LogWarning($"No account found for this {AccountName}");
Stop();
}
else
{
Account = account;
}
}
public void LoadScenario()
{
var scenario = TradingService.GetScenarioByName(Scenario);
if (scenario == null)
{
Logger.LogWarning("No scenario found for this scenario name");
Stop();
}
else
{
LoadStrategies(ScenarioHelpers.GetStrategiesFromScenario(scenario));
}
}
public void LoadStrategies(IEnumerable<IStrategy> strategies)
{
foreach (var strategy in strategies)
{
Strategies.Add(strategy);
}
}
public async Task Run()
{
Logger.LogInformation($"____________________{Name}____________________");
Logger.LogInformation($"Time : {DateTime.Now} - Server time {DateTime.Now.ToUniversalTime()} - Bot : {Name} - Type {BotType} - Ticker : {Ticker}");
var previousCandleCount = Candles.Count;
if (!IsForBacktest)
await UpdateCandles();
if (Candles.Count > previousCandleCount || IsForBacktest)
await UpdateSignals(Candles);
else
Logger.LogInformation($"No need to update signals for {Ticker}");
if (!IsForWatchingOnly)
await ManagePositions();
await UpdateWalletBalances();
Logger.LogInformation($"Candles : {Candles.Count}");
Logger.LogInformation($"Signals : {Signals.Count}");
Logger.LogInformation($"ExecutionCount : {ExecutionCount}");
Logger.LogInformation($"Positions : {Positions.Count}");
Logger.LogInformation("__________________________________________________");
}
private async Task PreloadCandles()
{
var candles = await ExchangeService.GetCandlesInflux(Account.Exchange, Ticker, PreloadSince, Timeframe);
foreach (var candle in candles.Where(c => c.Date < DateTime.Now.ToUniversalTime()))
{
if (!Candles.Any(c => c.Date == candle.Date))
{
Candles.Add(candle);
await UpdateSignals(Candles);
}
}
PreloadedCandlesCount = Candles.Count();
}
private async Task UpdateSignals(HashSet<Candle> candles)
{
var signal = TradingBox.GetSignal(candles, Strategies, Signals);
if (signal == null) return;
await AddSignal(signal);
}
private async Task AddSignal(Signal signal)
{
Signals.Add(signal);
if (!IsForBacktest)
TradingService.InsertSignal(signal);
if (IsForWatchingOnly || (ExecutionCount < 1 && !IsForBacktest))
signal.Status = SignalStatus.Expired;
var signalText = $"{Scenario} trigger a signal. Signal told you " +
$"to {signal.Direction} {Ticker} on {Timeframe}. The confidence in this signal is {signal.Confidence}. Identifier : {signal.Identifier}";
Logger.LogInformation(signalText);
if (IsForWatchingOnly && !IsForBacktest && ExecutionCount > 0)
{
await MessengerService.SendSignal(signalText, Account.Exchange, Ticker, signal.Direction, Timeframe);
}
}
protected async Task UpdateCandles()
{
if (Candles.Count == 0 || ExecutionCount == 0)
return;
var lastCandle = Candles.Last();
var newCandle = await ExchangeService.GetCandlesInflux(Account.Exchange, Ticker, lastCandle.Date, Timeframe);
foreach (var candle in newCandle.Where(c => c.Date < DateTime.Now.ToUniversalTime()))
{
Candles.Add(candle);
}
}
private async Task ManagePositions()
{
if (!IsForBacktest && ExecutionCount < 1)
return;
// Update position
foreach (var signal in Signals.Where(s => s.Status == SignalStatus.PositionOpen))
{
var positionForSignal = Positions.FirstOrDefault(p => p.SignalIdentifier == signal.Identifier);
await UpdatePosition(signal, positionForSignal);
}
// Open position for signal waiting for a position open
foreach (var signal in Signals.Where(s => s.Status == SignalStatus.WaitingForPosition))
{
Task.Run(() => OpenPosition(signal)).GetAwaiter().GetResult();
}
}
private async Task UpdateWalletBalances()
{
if (WalletBalances.Count == 0)
{
WalletBalances.Add(Candles.LastOrDefault().Date, await ExchangeService.GetBalance(Account, IsForBacktest));
}
else if (!WalletBalances.Any(w => w.Key == Candles.LastOrDefault().Date))
{
var walletBalance = WalletBalances.FirstOrDefault().Value + GetProfitAndLoss();
WalletBalances.Add(Candles.LastOrDefault().Date, walletBalance);
}
}
private async Task UpdatePosition(Signal signal, Position positionForSignal)
{
try
{
Logger.LogInformation($"Updating position {positionForSignal.SignalIdentifier}");
var position = IsForBacktest ? positionForSignal : TradingService.GetPositionByIdentifier(positionForSignal.Identifier);
if (position.Status == (PositionStatus.Finished | PositionStatus.Flipped))
{
await HandleClosedPosition(positionForSignal);
return;
}
else if (position.Status == PositionStatus.Filled)
{
// For backtesting or force close if not executed on exchange :
// check if position is still open
// Check status, if still open update the status of the position
var lastCandle = IsForBacktest ? Candles.Last() : ExchangeService.GetCandle(Account, Ticker, DateTime.UtcNow);
if (positionForSignal.OriginDirection == TradeDirection.Long)
{
if (positionForSignal.StopLoss.Price >= lastCandle.Low)
{
await LogInformation($"Closing position - SL {positionForSignal.StopLoss.Price} >= Price {lastCandle.Low}");
await CloseTrade(signal, positionForSignal, positionForSignal.StopLoss, positionForSignal.StopLoss.Price, true);
positionForSignal.StopLoss.SetStatus(TradeStatus.Filled);
}
else if (positionForSignal.TakeProfit1.Price <= lastCandle.High
&& positionForSignal.TakeProfit1.Status != TradeStatus.Filled)
{
await LogInformation($"Closing position - TP1 {positionForSignal.TakeProfit1.Price} <= Price {lastCandle.High}");
await CloseTrade(signal, positionForSignal, positionForSignal.TakeProfit1, positionForSignal.TakeProfit1.Price, positionForSignal.TakeProfit2 == null);
positionForSignal.TakeProfit1.SetStatus(TradeStatus.Filled);
}
else if (positionForSignal.TakeProfit2?.Price <= lastCandle.High)
{
await LogInformation($"Closing position - TP2 {positionForSignal.TakeProfit2.Price} <= Price {lastCandle.High}");
await CloseTrade(signal, positionForSignal, positionForSignal.TakeProfit2, positionForSignal.TakeProfit2.Price, true);
positionForSignal.TakeProfit2.SetStatus(TradeStatus.Filled);
}
else
{
Logger.LogInformation($"Position {signal.Identifier} don't need to be update. Position still opened");
}
}
if (positionForSignal.OriginDirection == TradeDirection.Short)
{
if (positionForSignal.StopLoss.Price <= lastCandle.High)
{
await LogInformation($"Closing position - SL {positionForSignal.StopLoss.Price} <= Price {lastCandle.High}");
await CloseTrade(signal, positionForSignal, positionForSignal.StopLoss, positionForSignal.StopLoss.Price, true);
positionForSignal.StopLoss.SetStatus(TradeStatus.Filled);
}
else if (positionForSignal.TakeProfit1.Price >= lastCandle.Low
&& positionForSignal.TakeProfit1.Status != TradeStatus.Filled)
{
await LogInformation($"Closing position - TP1 {positionForSignal.TakeProfit1.Price} >= Price {lastCandle.Low}");
await CloseTrade(signal, positionForSignal, positionForSignal.TakeProfit1, positionForSignal.TakeProfit1.Price, positionForSignal.TakeProfit2 == null);
positionForSignal.TakeProfit1.SetStatus(TradeStatus.Filled);
}
else if (positionForSignal.TakeProfit2?.Price >= lastCandle.Low)
{
await LogInformation($"Closing position - TP2 {positionForSignal.TakeProfit2.Price} >= Price {lastCandle.Low}");
await CloseTrade(signal, positionForSignal, positionForSignal.TakeProfit2, positionForSignal.TakeProfit2.Price, true);
positionForSignal.TakeProfit2.SetStatus(TradeStatus.Filled);
}
else
{
Logger.LogInformation($"Position {signal.Identifier} don't need to be update. Position still opened");
}
}
}
else if (position.Status == (PositionStatus.Rejected | PositionStatus.Canceled))
{
await LogWarning($"Open position trade is rejected for signal {signal.Identifier}");
// if position is not open
// Re-open the trade for the signal only if signal still up
//if (signal.Status == SignalStatus.PositionOpen)
//{
// Logger.LogInformation($"Try to re-open position");
// OpenPosition(signal);
//}
}
}
catch (Exception ex)
{
Logger.LogError(ex, ex.Message);
return;
}
}
private async Task OpenPosition(Signal signal)
{
// Check if a position is already open
Logger.LogInformation($"Opening position for {signal.Identifier}");
var openedPosition = Positions.FirstOrDefault(p => p.Status == PositionStatus.Filled
&& p.SignalIdentifier != signal.Identifier);
var lastPrice = IsForBacktest ? Candles.Last().Close : ExchangeService.GetPrice(Account, Ticker, DateTime.UtcNow);
// If position open
if (openedPosition != null)
{
var previousSignal = Signals.First(s => s.Identifier == openedPosition.SignalIdentifier);
// Check if signal is the opposite side => flip the position
if (openedPosition.OriginDirection == signal.Direction)
{
// An operation is already open for the same direction
await LogInformation($"Signal {signal.Identifier} try to open a position but {previousSignal.Identifier} is already open for the same direction");
SetSignalStatus(signal.Identifier, SignalStatus.Expired);
}
else
{
// An operation is already open for the opposite direction
// ==> Flip the position
if (FlipPosition)
{
await LogInformation("Try to flip the position because of an opposite direction signal");
await CloseTrade(previousSignal, openedPosition, openedPosition.Open, lastPrice, true);
await SetPositionStatus(previousSignal.Identifier, PositionStatus.Flipped);
await OpenPosition(signal);
await LogInformation($"Position {previousSignal.Identifier} flipped by {signal.Identifier} at {lastPrice}$");
}
else
{
await LogWarning($"A position is already open for signal {previousSignal.Identifier}. Position flipping is currently not enable, the position will not be flipped.");
SetSignalStatus(signal.Identifier, SignalStatus.Expired);
}
}
}
else
{
if (!CanOpenPosition(signal))
{
await LogInformation("Tried to open position but last position was a loss. Wait for an opposition direction side or wait x candles to open a new position");
SetSignalStatus(signal.Identifier, SignalStatus.Expired);
return;
}
await LogInformation($"Open position - Date: {signal.Date:T} - SignalIdentifier : {signal.Identifier} - Strategie : {signal.StrategyType}");
try
{
var command = new OpenPositionRequest(
AccountName,
MoneyManagement,
signal.Direction,
Ticker,
PositionInitiator.Bot,
signal.Date,
IsForBacktest,
lastPrice,
balance: WalletBalances.LastOrDefault().Value,
fee: Fee);
var position = await new OpenPositionCommandHandler(ExchangeService, AccountService, TradingService)
.Handle(command);
if (position != null)
{
if (position.Open.Status != TradeStatus.Cancelled)
{
position.SignalIdentifier = signal.Identifier;
Positions.Add(position);
SetSignalStatus(signal.Identifier, SignalStatus.PositionOpen);
if (!IsForBacktest)
{
await MessengerService.SendPosition(position);
}
Logger.LogInformation($"Position requested");
}
else
{
await SetPositionStatus(signal.Identifier, PositionStatus.Rejected);
SetSignalStatus(signal.Identifier, SignalStatus.Expired);
}
}
}
catch (Exception ex)
{
SetSignalStatus(signal.Identifier, SignalStatus.Expired);
await LogWarning($"Cannot open trade : {ex.Message}");
}
}
}
private bool CanOpenPosition(Signal signal)
{
if (Positions.Count == 0)
return true;
var lastPosition = Positions.LastOrDefault(p => p.IsFinished()
&& p.SignalIdentifier != signal.Identifier
&& p.ProfitAndLoss.Realized < 0
&& p.OriginDirection == signal.Direction);
if (lastPosition == null)
return true;
var tenCandleAgo = Candles.TakeLast(10).First();
var positionSignal = Signals.FirstOrDefault(s => s.Identifier == lastPosition.SignalIdentifier);
return positionSignal.Date < tenCandleAgo.Date;
}
private async Task CloseTrade(Signal signal, Position position, Trade tradeToClose, decimal lastPrice, bool tradeClosingPosition = false)
{
if (position.TakeProfit2 != null && position.TakeProfit1.Status == TradeStatus.Filled && tradeToClose.TradeType == TradeType.StopMarket)
{
// If trade is the 2nd Take profit
tradeToClose.Quantity = position.TakeProfit2.Quantity;
}
await LogInformation($"Trying to close trade {Ticker} at {lastPrice} - Type : {tradeToClose.TradeType} - Quantity : {tradeToClose.Quantity} " +
$"- Closing Position : {tradeClosingPosition}");
// Get status of position before closing it. The position might be already close by the exchange
if (!IsForBacktest && await ExchangeService.GetQuantityInPosition(Account, Ticker) == 0)
{
Logger.LogInformation($"Trade already close on exchange");
await HandleClosedPosition(position);
}
else
{
var command = new ClosePositionCommand(position, lastPrice);
try
{
var closedPosition = await (new ClosePositionCommandHandler(ExchangeService, AccountService, TradingService))
.Handle(command);
if (closedPosition.Status == (PositionStatus.Finished | PositionStatus.Flipped))
{
if (tradeClosingPosition)
{
await SetPositionStatus(signal.Identifier, PositionStatus.Finished);
}
await HandleClosedPosition(closedPosition);
}
else
{
throw new Exception($"Wrong position status : {closedPosition.Status}");
}
}
catch (Exception ex)
{
await LogWarning($"Position {signal.Identifier} not closed : {ex.Message}");
if (position.Status == (PositionStatus.Canceled | PositionStatus.Rejected))
{
// Trade close on exchange => Should close trade manually
await SetPositionStatus(signal.Identifier, PositionStatus.Finished);
}
}
}
}
private async Task HandleClosedPosition(Position position)
{
if (Positions.Any(p => p.Identifier == position.Identifier))
{
var previousPosition = Positions.First(p => p.Identifier == position.Identifier);
var positionIndex = Positions.IndexOf(previousPosition);
position.SignalIdentifier = previousPosition.SignalIdentifier;
Positions[positionIndex] = position;
SetSignalStatus(position.SignalIdentifier, SignalStatus.Expired);
Logger.LogInformation($"Position {position.SignalIdentifier} type correctly close. Pnl on position : {position.ProfitAndLoss.Realized}");
}
else
{
await LogWarning("Weird things happen - Trying to update position status, but no position found");
}
if (!IsForBacktest)
{
await MessengerService.SendClosingPosition(position);
}
await CancelAllOrders();
}
private async Task CancelAllOrders()
{
if (!IsForBacktest)
{
try
{
var closePendingOrderStatus = await ExchangeService.CancelOrder(Account, Ticker);
Logger.LogInformation($"Closing all {Ticker} orders status : {closePendingOrderStatus}");
}
catch (Exception ex)
{
// Todo handle exception from evm
Logger.LogError(ex, "Error during cancelOrders");
}
}
}
private async Task SetPositionStatus(string signalIdentifier, PositionStatus positionStatus)
{
await LogInformation($"Position {signalIdentifier} is now {positionStatus}");
Positions.First(p => p.SignalIdentifier == signalIdentifier).Status = positionStatus;
SetSignalStatus(signalIdentifier, SignalStatus.Expired);
}
private void SetSignalStatus(string signalIdentifier, SignalStatus signalStatus)
{
if (Signals.Any(s => s.Identifier == signalIdentifier))
{
Signals.First(s => s.Identifier == signalIdentifier).Status = signalStatus;
Logger.LogInformation($"Signal {signalIdentifier} is now {signalStatus}");
}
}
public int GetWinRate()
{
var succeededPositions = Positions.Where(p => p.IsFinished()).Count(p => p.ProfitAndLoss?.Realized > 0);
var total = Positions.Where(p => p.IsFinished()).Count();
if (total == 0)
return 0;
return (succeededPositions * 100) / total;
}
public decimal GetProfitAndLoss()
{
var pnl = Positions.Where(p => p.ProfitAndLoss != null).Sum(p => p.ProfitAndLoss.Realized);
return pnl - GetTotalFees();
}
/// <summary>
/// Calculates the total fees paid by the trading bot for each position.
/// </summary>
/// <returns>Returns the total fees paid as a decimal value.</returns>
public decimal GetTotalFees()
{
decimal fees = 0;
foreach (var position in Positions.Where(p => p.Open.Fee > 0))
{
fees += position.Open.Fee;
fees += position.StopLoss.Status == TradeStatus.Filled ? position.StopLoss.Fee : 0;
fees += position.TakeProfit1.Status == TradeStatus.Filled ? position.TakeProfit1.Fee : 0;
if (position.IsFinished() &&
position.StopLoss.Status != TradeStatus.Filled && position.TakeProfit1.Status != TradeStatus.Filled)
fees += position.Open.Fee;
if (position.TakeProfit2 != null)
fees += position.TakeProfit2.Fee;
}
return fees;
}
public async Task ToggleIsForWatchOnly()
{
IsForWatchingOnly = (!IsForWatchingOnly);
await LogInformation($"Watch only toggle for bot : {Name} - Watch only : {IsForWatchingOnly}");
}
private async Task LogInformation(string message)
{
Logger.LogInformation(message);
await SendTradeMessage(message);
}
private async Task LogWarning(string message)
{
Logger.LogWarning(message);
await SendTradeMessage(message, true);
}
private async Task SendTradeMessage(string message, bool isBadBehavior = false)
{
if (!IsForBacktest)
{
await MessengerService.SendTradeMessage(message, isBadBehavior);
}
}
}