Balance for bot (#20)
* Add bot balance * Update amount to trade * fix initial trading balance * Update MM modal * fix backtest * stop bot if no more balance * Add constant for minimum trading * Add constant
This commit is contained in:
@@ -2,6 +2,7 @@
|
||||
using Managing.Application.Abstractions.Services;
|
||||
using Managing.Application.Trading;
|
||||
using Managing.Application.Trading.Commands;
|
||||
using Managing.Common;
|
||||
using Managing.Core.FixedSizedQueue;
|
||||
using Managing.Domain.Accounts;
|
||||
using Managing.Domain.Bots;
|
||||
@@ -50,6 +51,11 @@ public class TradingBot : Bot, ITradingBot
|
||||
public Dictionary<StrategyType, StrategiesResultBase> StrategiesValues { get; set; }
|
||||
public DateTime StartupTime { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The dedicated trading balance for this bot in USD
|
||||
/// </summary>
|
||||
public decimal BotTradingBalance { get; set; }
|
||||
|
||||
public TradingBot(
|
||||
string accountName,
|
||||
MoneyManagement moneyManagement,
|
||||
@@ -63,6 +69,7 @@ public class TradingBot : Bot, ITradingBot
|
||||
IAccountService accountService,
|
||||
IMessengerService messengerService,
|
||||
IBotService botService,
|
||||
decimal initialTradingBalance,
|
||||
bool isForBacktest = false,
|
||||
bool isForWatchingOnly = false,
|
||||
bool flipPosition = false)
|
||||
@@ -74,6 +81,13 @@ public class TradingBot : Bot, ITradingBot
|
||||
TradingService = tradingService;
|
||||
BotService = botService;
|
||||
|
||||
if (initialTradingBalance <= Constants.GMX.Config.MinimumPositionAmount)
|
||||
{
|
||||
throw new ArgumentException(
|
||||
$"Initial trading balance must be greater than {Constants.GMX.Config.MinimumPositionAmount}",
|
||||
nameof(initialTradingBalance));
|
||||
}
|
||||
|
||||
IsForWatchingOnly = isForWatchingOnly;
|
||||
FlipPosition = flipPosition;
|
||||
AccountName = accountName;
|
||||
@@ -83,6 +97,7 @@ public class TradingBot : Bot, ITradingBot
|
||||
Timeframe = timeframe;
|
||||
IsForBacktest = isForBacktest;
|
||||
Logger = logger;
|
||||
BotTradingBalance = initialTradingBalance;
|
||||
|
||||
Strategies = new HashSet<IStrategy>();
|
||||
Signals = new HashSet<Signal>();
|
||||
@@ -172,6 +187,17 @@ public class TradingBot : Bot, ITradingBot
|
||||
{
|
||||
if (!IsForBacktest)
|
||||
{
|
||||
// Check broker balance before running
|
||||
var balance = await ExchangeService.GetBalance(Account, false);
|
||||
if (balance < Constants.GMX.Config.MinimumPositionAmount)
|
||||
{
|
||||
await LogWarning(
|
||||
$"Balance on broker is below {Constants.GMX.Config.MinimumPositionAmount} USD (actual: {balance}). Stopping bot and saving backup.");
|
||||
SaveBackup();
|
||||
Stop();
|
||||
return;
|
||||
}
|
||||
|
||||
Logger.LogInformation($"____________________{Name}____________________");
|
||||
Logger.LogInformation(
|
||||
$"Time : {DateTime.Now} - Server time {DateTime.Now.ToUniversalTime()} - Last candle : {OptimizedCandles.Last().Date} - Bot : {Name} - Type {BotType} - Ticker : {Ticker}");
|
||||
@@ -315,7 +341,8 @@ public class TradingBot : Bot, ITradingBot
|
||||
|
||||
if (WalletBalances.Count == 0)
|
||||
{
|
||||
WalletBalances[date] = await ExchangeService.GetBalance(Account, IsForBacktest);
|
||||
// WalletBalances[date] = await ExchangeService.GetBalance(Account, IsForBacktest);
|
||||
WalletBalances[date] = BotTradingBalance;
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -555,10 +582,9 @@ public class TradingBot : Bot, ITradingBot
|
||||
PositionInitiator.Bot,
|
||||
signal.Date,
|
||||
User,
|
||||
BotTradingBalance,
|
||||
IsForBacktest,
|
||||
lastPrice,
|
||||
balance: WalletBalances.LastOrDefault().Value,
|
||||
fee: Fee,
|
||||
signalIdentifier: signal.Identifier);
|
||||
|
||||
var position = await new OpenPositionCommandHandler(ExchangeService, AccountService, TradingService)
|
||||
@@ -591,7 +617,6 @@ public class TradingBot : Bot, ITradingBot
|
||||
// Keep signal open for debug purpose
|
||||
//SetSignalStatus(signal.Identifier, SignalStatus.Expired);
|
||||
SetSignalStatus(signal.Identifier, SignalStatus.Expired);
|
||||
|
||||
await LogWarning($"Cannot open trade : {ex.Message}, stackTrace : {ex.StackTrace}");
|
||||
}
|
||||
}
|
||||
@@ -599,8 +624,9 @@ public class TradingBot : Bot, ITradingBot
|
||||
|
||||
private bool CanOpenPosition(Signal signal)
|
||||
{
|
||||
if (ExecutionCount < 1)
|
||||
if (!IsForBacktest && ExecutionCount < 1)
|
||||
return false;
|
||||
|
||||
if (Positions.Count == 0)
|
||||
return true;
|
||||
|
||||
@@ -639,7 +665,6 @@ public class TradingBot : Bot, ITradingBot
|
||||
}
|
||||
else
|
||||
{
|
||||
position.Initiator = PositionInitiator.Bot;
|
||||
var command = new ClosePositionCommand(position, lastPrice);
|
||||
try
|
||||
{
|
||||
@@ -681,6 +706,18 @@ public class TradingBot : Bot, ITradingBot
|
||||
await SetPositionStatus(position.SignalIdentifier, PositionStatus.Finished);
|
||||
Logger.LogInformation(
|
||||
$"Position {position.SignalIdentifier} type correctly close. Pnl on position : {position.ProfitAndLoss?.Realized}");
|
||||
|
||||
// Update the bot's trading balance after position is closed
|
||||
if (position.ProfitAndLoss != null)
|
||||
{
|
||||
// Add PnL (could be positive or negative)
|
||||
BotTradingBalance += position.ProfitAndLoss.Realized;
|
||||
|
||||
// Subtract fees
|
||||
BotTradingBalance -= GetPositionFees(position);
|
||||
|
||||
Logger.LogInformation($"Updated bot trading balance to: {BotTradingBalance}");
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -735,8 +772,12 @@ public class TradingBot : Bot, ITradingBot
|
||||
|
||||
private async Task SetPositionStatus(string signalIdentifier, PositionStatus positionStatus)
|
||||
{
|
||||
await LogInformation($"Position {signalIdentifier} is now {positionStatus}");
|
||||
Positions.First(p => p.SignalIdentifier == signalIdentifier).Status = positionStatus;
|
||||
if (!Positions.First(p => p.SignalIdentifier == signalIdentifier).Status.Equals(positionStatus))
|
||||
{
|
||||
await LogInformation($"Position {signalIdentifier} is now {positionStatus}");
|
||||
Positions.First(p => p.SignalIdentifier == signalIdentifier).Status = positionStatus;
|
||||
}
|
||||
|
||||
SetSignalStatus(signalIdentifier,
|
||||
positionStatus == PositionStatus.Filled ? SignalStatus.PositionOpen : SignalStatus.Expired);
|
||||
}
|
||||
@@ -799,6 +840,29 @@ public class TradingBot : Bot, ITradingBot
|
||||
return fees;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Calculates the total fees for a specific position
|
||||
/// </summary>
|
||||
/// <param name="position">The position to calculate fees for</param>
|
||||
/// <returns>The total fees for the position</returns>
|
||||
private decimal GetPositionFees(Position position)
|
||||
{
|
||||
decimal fees = 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.Status == TradeStatus.Filled ? position.TakeProfit2.Fee : 0;
|
||||
|
||||
return fees;
|
||||
}
|
||||
|
||||
public async Task ToggleIsForWatchOnly()
|
||||
{
|
||||
IsForWatchingOnly = (!IsForWatchingOnly);
|
||||
@@ -813,6 +877,7 @@ public class TradingBot : Bot, ITradingBot
|
||||
|
||||
private async Task LogWarning(string message)
|
||||
{
|
||||
message = $"[{Name}][{Identifier}] {message}";
|
||||
Logger.LogWarning(message);
|
||||
SentrySdk.CaptureException(new Exception(message));
|
||||
await SendTradeMessage(message, true);
|
||||
@@ -841,6 +906,7 @@ public class TradingBot : Bot, ITradingBot
|
||||
IsForWatchingOnly = IsForWatchingOnly,
|
||||
WalletBalances = WalletBalances,
|
||||
MoneyManagement = MoneyManagement,
|
||||
BotTradingBalance = BotTradingBalance,
|
||||
StartupTime = StartupTime,
|
||||
};
|
||||
BotService.SaveOrUpdateBotBackup(Name, BotType, JsonConvert.SerializeObject(data));
|
||||
@@ -858,6 +924,7 @@ public class TradingBot : Bot, ITradingBot
|
||||
ScenarioName = data.ScenarioName;
|
||||
AccountName = data.AccountName;
|
||||
IsForWatchingOnly = data.IsForWatchingOnly;
|
||||
BotTradingBalance = data.BotTradingBalance;
|
||||
|
||||
// Restore the startup time if it was previously saved
|
||||
if (data.StartupTime != DateTime.MinValue)
|
||||
@@ -902,10 +969,6 @@ public class TradingBot : Bot, ITradingBot
|
||||
throw new Exception("Failed to open position");
|
||||
}
|
||||
|
||||
// Removed manual setting of SL/TP, as MoneyManagement should handle it
|
||||
// position.StopLoss.Price = stopLossPrice;
|
||||
// position.TakeProfit1.Price = takeProfitPrice;
|
||||
|
||||
Logger.LogInformation($"Manually opened position {position.Identifier} for signal {signal.Identifier}");
|
||||
return position;
|
||||
}
|
||||
@@ -925,4 +988,5 @@ public class TradingBotBackup
|
||||
public Dictionary<DateTime, decimal> WalletBalances { get; set; }
|
||||
public MoneyManagement MoneyManagement { get; set; }
|
||||
public DateTime StartupTime { get; set; }
|
||||
public decimal BotTradingBalance { get; set; }
|
||||
}
|
||||
Reference in New Issue
Block a user