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:
Oda
2025-04-28 16:52:42 +02:00
committed by GitHub
parent 967e8410dc
commit 204bd87e6a
39 changed files with 600 additions and 546 deletions

View File

@@ -7,10 +7,10 @@ namespace Managing.Application.Abstractions
{
public interface IBotFactory
{
ITradingBot CreateScalpingBot(string accountName, MoneyManagement moneyManagement, string name, Ticker ticker, string scenario, Timeframe interval, bool isForWatchingOnly);
ITradingBot CreateBacktestScalpingBot(string accountName, MoneyManagement moneyManagement, Ticker ticker, string scenario, Timeframe interval, bool isForWatchingOnly);
ITradingBot CreateFlippingBot(string accountName, MoneyManagement moneyManagement, string name, Ticker ticker, string scenario, Timeframe interval, bool isForWatchingOnly);
ITradingBot CreateBacktestFlippingBot(string accountName, MoneyManagement moneyManagement, Ticker ticker, string scenario, Timeframe interval, bool isForWatchingOnly);
ITradingBot CreateScalpingBot(string accountName, MoneyManagement moneyManagement, string name, Ticker ticker, string scenario, Timeframe interval, bool isForWatchingOnly, decimal initialTradingBalance);
ITradingBot CreateBacktestScalpingBot(string accountName, MoneyManagement moneyManagement, Ticker ticker, string scenario, Timeframe interval, bool isForWatchingOnly, decimal initialTradingBalance);
ITradingBot CreateFlippingBot(string accountName, MoneyManagement moneyManagement, string name, Ticker ticker, string scenario, Timeframe interval, bool isForWatchingOnly, decimal initialTradingBalance);
ITradingBot CreateBacktestFlippingBot(string accountName, MoneyManagement moneyManagement, Ticker ticker, string scenario, Timeframe interval, bool isForWatchingOnly, decimal initialTradingBalance);
IBot CreateSimpleBot(string botName, Workflow workflow);
}
}

View File

@@ -1,5 +1,4 @@
using Managing.Common;
using Managing.Domain.Accounts;
using Managing.Domain.Bots;
using Managing.Domain.MoneyManagements;
using Managing.Domain.Workflows;
@@ -18,16 +17,16 @@ public interface IBotService
BotBackup GetBotBackup(string name);
ITradingBot CreateScalpingBot(string accountName, MoneyManagement moneyManagement, string name, Enums.Ticker ticker,
string scenario, Enums.Timeframe interval, bool isForWatchingOnly);
string scenario, Enums.Timeframe interval, bool isForWatchingOnly, decimal initialTradingAmount);
ITradingBot CreateBacktestScalpingBot(string accountName, MoneyManagement moneyManagement, Enums.Ticker ticker,
string scenario, Enums.Timeframe interval, bool isForWatchingOnly);
string scenario, Enums.Timeframe interval, bool isForWatchingOnly, decimal initialTradingAmount);
ITradingBot CreateFlippingBot(string accountName, MoneyManagement moneyManagement, string name, Enums.Ticker ticker,
string scenario, Enums.Timeframe interval, bool isForWatchingOnly);
string scenario, Enums.Timeframe interval, bool isForWatchingOnly, decimal initialTradingAmount);
ITradingBot CreateBacktestFlippingBot(string accountName, MoneyManagement moneyManagement, Enums.Ticker ticker,
string scenario, Enums.Timeframe interval, bool isForWatchingOnly);
string scenario, Enums.Timeframe interval, bool isForWatchingOnly, decimal initialTradingAmount);
IBot CreateSimpleBot(string botName, Workflow workflow);
Task<string> StopBot(string requestName);

View File

@@ -11,10 +11,10 @@ using Managing.Domain.Scenarios;
using Managing.Domain.Shared.Helpers;
using Managing.Domain.Strategies;
using Managing.Domain.Strategies.Base;
using Managing.Domain.Users;
using Managing.Domain.Workflows;
using Microsoft.Extensions.Logging;
using static Managing.Common.Enums;
using Managing.Domain.Users;
namespace Managing.Application.Backtesting
{
@@ -67,7 +67,7 @@ namespace Managing.Application.Backtesting
List<Candle> initialCandles = null)
{
var scalpingBot = _botFactory.CreateBacktestScalpingBot(account.Name, moneyManagement, ticker, "scenario",
timeframe, isForWatchingOnly);
timeframe, isForWatchingOnly, balance);
scalpingBot.LoadScenario(scenario.Name);
await scalpingBot.LoadAccount();
var candles = initialCandles ?? GetCandles(account, ticker, timeframe, startDate, endDate);
@@ -121,7 +121,7 @@ namespace Managing.Application.Backtesting
List<Candle> initialCandles = null)
{
var flippingBot = _botFactory.CreateBacktestFlippingBot(account.Name, moneyManagement, ticker, "scenario",
timeframe, false);
timeframe, false, balance);
flippingBot.LoadScenario(scenario.Name);
await flippingBot.LoadAccount();
@@ -152,7 +152,7 @@ namespace Managing.Application.Backtesting
{
var ticker = MiscExtensions.ParseEnum<Ticker>(candles.FirstOrDefault().Ticker);
var bot = _botFactory.CreateBacktestScalpingBot(account.Name, moneyManagement, ticker, "scenario",
timeframe, false);
timeframe, false, balance);
bot.LoadScenario(scenario.Name);
await bot.LoadAccount();
@@ -173,7 +173,7 @@ namespace Managing.Application.Backtesting
{
var ticker = MiscExtensions.ParseEnum<Ticker>(candles.FirstOrDefault().Ticker);
var bot = _botFactory.CreateBacktestFlippingBot(account.Name, moneyManagement, ticker, "scenario",
timeframe, false);
timeframe, false, balance);
bot.LoadScenario(scenario.Name);
await bot.LoadAccount();

View File

@@ -1,10 +1,10 @@
using Microsoft.Extensions.Logging;
using static Managing.Common.Enums;
using Managing.Application.Abstractions;
using Managing.Application.Abstractions;
using Managing.Application.Abstractions.Services;
using Managing.Domain.Bots;
using Managing.Domain.MoneyManagements;
using Managing.Domain.Workflows;
using Managing.Domain.Bots;
using Microsoft.Extensions.Logging;
using static Managing.Common.Enums;
namespace Managing.Application.Bots.Base
{
@@ -38,7 +38,7 @@ namespace Managing.Application.Bots.Base
return new SimpleBot(botName, _tradingBotLogger, workflow, _botService);
}
ITradingBot IBotFactory.CreateScalpingBot(string accountName, MoneyManagement moneyManagement, string name, Ticker ticker, string scenario, Timeframe interval, bool isForWatchingOnly)
ITradingBot IBotFactory.CreateScalpingBot(string accountName, MoneyManagement moneyManagement, string name, Ticker ticker, string scenario, Timeframe interval, bool isForWatchingOnly, decimal initialTradingBalance)
{
return new ScalpingBot(
accountName,
@@ -53,10 +53,11 @@ namespace Managing.Application.Bots.Base
_accountService,
_messengerService,
_botService,
initialTradingBalance,
isForWatchingOnly: isForWatchingOnly);
}
ITradingBot IBotFactory.CreateBacktestScalpingBot(string accountName, MoneyManagement moneyManagement, Ticker ticker, string scenario, Timeframe interval, bool isForWatchingOnly)
ITradingBot IBotFactory.CreateBacktestScalpingBot(string accountName, MoneyManagement moneyManagement, Ticker ticker, string scenario, Timeframe interval, bool isForWatchingOnly, decimal initialTradingBalance)
{
return new ScalpingBot(
accountName,
@@ -71,11 +72,12 @@ namespace Managing.Application.Bots.Base
_accountService,
_messengerService,
_botService,
true,
isForWatchingOnly);
initialTradingBalance,
isForBacktest: true,
isForWatchingOnly: isForWatchingOnly);
}
public ITradingBot CreateFlippingBot(string accountName, MoneyManagement moneyManagement, string name, Ticker ticker, string scenario, Timeframe interval, bool isForWatchingOnly)
public ITradingBot CreateFlippingBot(string accountName, MoneyManagement moneyManagement, string name, Ticker ticker, string scenario, Timeframe interval, bool isForWatchingOnly, decimal initialTradingBalance)
{
return new FlippingBot(
accountName,
@@ -90,10 +92,11 @@ namespace Managing.Application.Bots.Base
_accountService,
_messengerService,
_botService,
initialTradingBalance,
isForWatchingOnly: isForWatchingOnly);
}
public ITradingBot CreateBacktestFlippingBot(string accountName, MoneyManagement moneyManagement, Ticker ticker, string scenario, Timeframe interval, bool isForWatchingOnly)
public ITradingBot CreateBacktestFlippingBot(string accountName, MoneyManagement moneyManagement, Ticker ticker, string scenario, Timeframe interval, bool isForWatchingOnly, decimal initialTradingBalance)
{
return new FlippingBot(
accountName,
@@ -108,8 +111,9 @@ namespace Managing.Application.Bots.Base
_accountService,
_messengerService,
_botService,
true,
isForWatchingOnly);
initialTradingBalance,
isForBacktest: true,
isForWatchingOnly: isForWatchingOnly);
}
}
}

View File

@@ -20,6 +20,7 @@ namespace Managing.Application.Bots
IAccountService accountService,
IMessengerService messengerService,
IBotService botService,
decimal initialTradingBalance,
bool isForBacktest = false,
bool isForWatchingOnly = false)
: base(accountName,
@@ -34,6 +35,7 @@ namespace Managing.Application.Bots
accountService,
messengerService,
botService,
initialTradingBalance,
isForBacktest,
isForWatchingOnly,
flipPosition: true)

View File

@@ -20,6 +20,7 @@ namespace Managing.Application.Bots
IAccountService accountService,
IMessengerService messengerService,
IBotService botService,
decimal initialTradingBalance,
bool isForBacktest = false,
bool isForWatchingOnly = false)
: base(accountName,
@@ -34,6 +35,7 @@ namespace Managing.Application.Bots
accountService,
messengerService,
botService,
initialTradingBalance,
isForBacktest,
isForWatchingOnly)
{

View File

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

View File

@@ -55,7 +55,7 @@ namespace Managing.Application.ManageBot
if (backup != null)
{
backup.Data = data;
_botRepository.UpdateBackupBot(backup);
_botRepository.UpdateBackupBot(backup);
}
else
{
@@ -136,7 +136,8 @@ namespace Managing.Application.ManageBot
scalpingBotData.Ticker,
scalpingBotData.ScenarioName,
scalpingBotData.Timeframe,
scalpingBotData.IsForWatchingOnly);
scalpingBotData.IsForWatchingOnly,
scalpingBotData.BotTradingBalance);
botTask = Task.Run(() => InitBot((ITradingBot)bot, backupBot));
break;
case Enums.BotType.FlippingBot:
@@ -150,7 +151,8 @@ namespace Managing.Application.ManageBot
flippingBotData.Ticker,
flippingBotData.ScenarioName,
flippingBotData.Timeframe,
flippingBotData.IsForWatchingOnly);
flippingBotData.IsForWatchingOnly,
flippingBotData.BotTradingBalance);
botTask = Task.Run(InitBot((ITradingBot)bot, backupBot));
break;
}
@@ -243,7 +245,8 @@ namespace Managing.Application.ManageBot
}
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,
decimal initialTradingBalance)
{
return new ScalpingBot(
accountName,
@@ -258,11 +261,13 @@ namespace Managing.Application.ManageBot
_accountService,
_messengerService,
this,
initialTradingBalance,
isForWatchingOnly: isForWatchingOnly);
}
public ITradingBot CreateBacktestScalpingBot(string accountName, MoneyManagement moneyManagement,
Enums.Ticker ticker, string scenario, Enums.Timeframe interval, bool isForWatchingOnly)
Enums.Ticker ticker, string scenario, Enums.Timeframe interval, bool isForWatchingOnly,
decimal initialTradingBalance)
{
return new ScalpingBot(
accountName,
@@ -277,12 +282,14 @@ namespace Managing.Application.ManageBot
_accountService,
_messengerService,
this,
true,
isForWatchingOnly);
initialTradingBalance,
isForBacktest: true,
isForWatchingOnly: isForWatchingOnly);
}
public ITradingBot CreateFlippingBot(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,
decimal initialTradingBalance)
{
return new FlippingBot(
accountName,
@@ -297,11 +304,13 @@ namespace Managing.Application.ManageBot
_accountService,
_messengerService,
this,
initialTradingBalance,
isForWatchingOnly: isForWatchingOnly);
}
public ITradingBot CreateBacktestFlippingBot(string accountName, MoneyManagement moneyManagement,
Enums.Ticker ticker, string scenario, Enums.Timeframe interval, bool isForWatchingOnly)
Enums.Ticker ticker, string scenario, Enums.Timeframe interval, bool isForWatchingOnly,
decimal initialTradingBalance)
{
return new FlippingBot(
accountName,
@@ -316,8 +325,9 @@ namespace Managing.Application.ManageBot
_accountService,
_messengerService,
this,
true,
isForWatchingOnly);
initialTradingBalance,
isForBacktest: true,
isForWatchingOnly: isForWatchingOnly);
}
}
}

View File

@@ -1,5 +1,5 @@
using MediatR;
using Managing.Domain.Users;
using Managing.Domain.Users;
using MediatR;
using static Managing.Common.Enums;
namespace Managing.Application.ManageBot.Commands
@@ -15,6 +15,7 @@ namespace Managing.Application.ManageBot.Commands
public string AccountName { get; internal set; }
public string MoneyManagementName { get; internal set; }
public User User { get; internal set; }
public decimal InitialTradingBalance { get; internal set; }
public StartBotCommand(BotType botType,
string name,
@@ -24,7 +25,8 @@ namespace Managing.Application.ManageBot.Commands
string accountName,
string moneyManagementName,
User user,
bool isForWatchingOnly = false)
bool isForWatchingOnly = false,
decimal initialTradingBalance = 0)
{
BotType = botType;
Name = name;
@@ -35,6 +37,8 @@ namespace Managing.Application.ManageBot.Commands
AccountName = accountName;
MoneyManagementName = moneyManagementName;
User = user;
InitialTradingBalance = initialTradingBalance > 0 ? initialTradingBalance :
throw new ArgumentException("Initial trading balance must be greater than zero", nameof(initialTradingBalance));
}
}
}

View File

@@ -1,7 +1,8 @@
using MediatR;
using static Managing.Common.Enums;
using Managing.Application.Abstractions;
using Managing.Application.Abstractions;
using Managing.Application.Abstractions.Services;
using Managing.Application.ManageBot.Commands;
using MediatR;
using static Managing.Common.Enums;
namespace Managing.Application.ManageBot
{
@@ -10,38 +11,61 @@ namespace Managing.Application.ManageBot
private readonly IBotFactory _botFactory;
private readonly IBotService _botService;
private readonly IMoneyManagementService _moneyManagementService;
private readonly IExchangeService _exchangeService;
private readonly IAccountService _accountService;
public StartBotCommandHandler(IBotFactory botFactory, IBotService botService,
IMoneyManagementService moneyManagementService)
IMoneyManagementService moneyManagementService, IExchangeService exchangeService,
IAccountService accountService)
{
_botFactory = botFactory;
_botService = botService;
_moneyManagementService = moneyManagementService;
_exchangeService = exchangeService;
_accountService = accountService;
}
public Task<string> Handle(StartBotCommand request, CancellationToken cancellationToken)
public async Task<string> Handle(StartBotCommand request, CancellationToken cancellationToken)
{
BotStatus botStatus = BotStatus.Down;
var moneyManagement = _moneyManagementService.GetMoneyMangement(request.User, request.MoneyManagementName).Result;
var account = await _accountService.GetAccount(request.AccountName, true, true);
if (account == null)
{
throw new Exception($"Account {request.AccountName} not found");
}
var usdcBalance = account.Balances.FirstOrDefault(b => b.TokenName == Ticker.USDC.ToString());
if (usdcBalance == null || usdcBalance.Value < request.InitialTradingBalance)
{
throw new Exception($"Account {request.AccountName} has no USDC balance or not enough balance");
}
var moneyManagement =
await _moneyManagementService.GetMoneyMangement(request.User, request.MoneyManagementName);
switch (request.BotType)
{
case BotType.SimpleBot:
var bot = _botFactory.CreateSimpleBot(request.Name, null);
_botService.AddSimpleBotToCache(bot);
return Task.FromResult(bot.GetStatus());
return bot.GetStatus();
case BotType.ScalpingBot:
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,
request.InitialTradingBalance);
_botService.AddTradingBotToCache(sBot);
return Task.FromResult(sBot.GetStatus());
return sBot.GetStatus();
case BotType.FlippingBot:
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,
request.InitialTradingBalance);
_botService.AddTradingBotToCache(fBot);
return Task.FromResult(fBot.GetStatus());
return fBot.GetStatus();
}
return Task.FromResult(botStatus.ToString());
return botStatus.ToString();
}
}
}

View File

@@ -1,8 +1,8 @@
using Managing.Domain.MoneyManagements;
using Managing.Application.Abstractions;
using Microsoft.Extensions.Logging;
using Managing.Application.Abstractions;
using Managing.Application.Abstractions.Repositories;
using Managing.Domain.MoneyManagements;
using Managing.Domain.Users;
using Microsoft.Extensions.Logging;
namespace Managing.Application.MoneyManagements;
@@ -23,12 +23,12 @@ public class MoneyManagementService : IMoneyManagementService
{
// Try to get user-specific strategy first
var moneyManagement = await _settingsRepository.GetMoneyManagementByUser(user, request.Name);
// Fall back to regular lookup if user-specific endpoint is not implemented
if (moneyManagement == null)
{
moneyManagement = await _settingsRepository.GetMoneyManagement(request.Name);
// If found by name but doesn't belong to this user, treat as new
if (moneyManagement != null && moneyManagement.User?.Name != user.Name)
{
@@ -47,16 +47,16 @@ public class MoneyManagementService : IMoneyManagementService
// Additional check to ensure user's ownership
if (moneyManagement.User?.Name != user.Name)
{
throw new UnauthorizedAccessException("You do not have permission to update this money management strategy.");
throw new UnauthorizedAccessException(
"You do not have permission to update this money management strategy.");
}
moneyManagement.StopLoss = request.StopLoss;
moneyManagement.TakeProfit = request.TakeProfit;
moneyManagement.BalanceAtRisk = request.BalanceAtRisk;
moneyManagement.Leverage = request.Leverage;
moneyManagement.Timeframe = request.Timeframe;
moneyManagement.User = user;
_settingsRepository.UpdateMoneyManagement(moneyManagement);
return moneyManagement;
}
@@ -69,7 +69,7 @@ public class MoneyManagementService : IMoneyManagementService
public IEnumerable<MoneyManagement> GetMoneyMangements(User user)
{
try
try
{
// Try to use user-specific repository method first
return _settingsRepository.GetMoneyManagementsByUser(user);
@@ -85,7 +85,7 @@ public class MoneyManagementService : IMoneyManagementService
public async Task<MoneyManagement> GetMoneyMangement(User user, string name)
{
MoneyManagement moneyManagement;
try
{
// Try to use user-specific repository method first
@@ -95,14 +95,14 @@ public class MoneyManagementService : IMoneyManagementService
{
// Fall back to regular lookup if user-specific endpoint is not implemented
moneyManagement = await _settingsRepository.GetMoneyManagement(name);
// Filter by user
if (moneyManagement != null && moneyManagement.User?.Name != user.Name)
{
moneyManagement = null;
}
}
return moneyManagement;
}
@@ -119,15 +119,16 @@ public class MoneyManagementService : IMoneyManagementService
{
// Fall back to verifying user ownership before deletion
var moneyManagement = _settingsRepository.GetMoneyManagement(name).Result;
if (moneyManagement != null && moneyManagement.User?.Name != user.Name)
{
throw new UnauthorizedAccessException("You do not have permission to delete this money management strategy.");
throw new UnauthorizedAccessException(
"You do not have permission to delete this money management strategy.");
}
_settingsRepository.DeleteMoneyManagement(name);
}
return true;
}
catch (Exception ex)
@@ -150,9 +151,10 @@ public class MoneyManagementService : IMoneyManagementService
{
// This fallback is not ideal as it would delete all money managements regardless of user
// In a real implementation, we would need a filtered repository method
_logger.LogWarning("DeleteMoneyManagementsByUser not implemented, cannot delete user-specific money managements");
_logger.LogWarning(
"DeleteMoneyManagementsByUser not implemented, cannot delete user-specific money managements");
}
return true;
}
catch (Exception ex)
@@ -161,4 +163,4 @@ public class MoneyManagementService : IMoneyManagementService
return false;
}
}
}
}

View File

@@ -200,7 +200,6 @@ public class SettingsService : ISettingsService
{
Name = "Personal-Hourly",
Timeframe = Timeframe.OneHour,
BalanceAtRisk = 25, // 25%
StopLoss = 2, // 2%
TakeProfit = 4, // 4%
Leverage = 1,

View File

@@ -16,25 +16,28 @@ namespace Managing.Application.Trading.Commands
PositionInitiator initiator,
DateTime date,
User user,
decimal amountToTrade,
bool isForPaperTrading = false,
decimal? price = null,
decimal? balance = 1000,
decimal? fee = null,
bool? ignoreSLTP = false,
string signalIdentifier = null)
{
AccountName = accountName;
MoneyManagement = moneyManagement;
Direction = direction;
Ticker = ticker;
Initiator = initiator;
Date = date;
User = user;
if (amountToTrade <= 10)
{
throw new ArgumentException("Bot trading balance must be greater than zero", nameof(amountToTrade));
}
AmountToTrade = amountToTrade;
IsForPaperTrading = isForPaperTrading;
Price = price;
Date = date;
Balance = balance;
Initiator = initiator;
Fee = fee;
IgnoreSLTP = ignoreSLTP;
User = user;
SignalIdentifier = signalIdentifier;
}
@@ -45,11 +48,9 @@ namespace Managing.Application.Trading.Commands
public Ticker Ticker { get; }
public bool IsForPaperTrading { get; }
public decimal? Price { get; }
public decimal? Fee { get; }
public bool? IgnoreSLTP { get; }
public decimal? Balance { get; }
public DateTime Date { get; set; }
public PositionInitiator Initiator { get; internal set; }
public User User { get; internal set; }
public decimal AmountToTrade { get; }
public DateTime Date { get; }
public PositionInitiator Initiator { get; }
public User User { get; }
}
}

View File

@@ -1,6 +1,7 @@
using Managing.Application.Abstractions;
using Managing.Application.Abstractions.Services;
using Managing.Application.Trading.Commands;
using Managing.Common;
using Managing.Domain.Shared.Helpers;
using Managing.Domain.Trades;
using static Managing.Common.Enums;
@@ -36,22 +37,20 @@ namespace Managing.Application.Trading
position.SignalIdentifier = request.SignalIdentifier;
}
var balance = request.IsForPaperTrading
? request.Balance.GetValueOrDefault()
: exchangeService.GetBalance(account, request.IsForPaperTrading).Result;
var balanceAtRisk = RiskHelpers.GetBalanceAtRisk(balance, request.MoneyManagement);
// Always use BotTradingBalance directly as the balance to risk
decimal balanceToRisk = request.AmountToTrade;
if (balanceAtRisk < 7)
// Minimum check
if (balanceToRisk < Constants.GMX.Config.MinimumPositionAmount)
{
throw new Exception($"Try to risk {balanceAtRisk} $ but inferior to minimum to trade");
throw new Exception(
$"Bot trading balance of {balanceToRisk} USD is less than the minimum {Constants.GMX.Config.MinimumPositionAmount} USD required to trade");
}
var price = request.IsForPaperTrading && request.Price.HasValue
? request.Price.Value
: exchangeService.GetPrice(account, request.Ticker, DateTime.Now);
var quantity = balanceAtRisk / price;
// var expectedStatus = GetExpectedStatus(request);
// position.Open = TradingPolicies.OpenPosition(expectedStatus).Execute(async () => { });
var quantity = balanceToRisk / price;
var openPrice = request.IsForPaperTrading || request.Price.HasValue
? request.Price.Value
@@ -71,20 +70,19 @@ namespace Managing.Application.Trading
TradeType.Limit,
isForPaperTrading: request.IsForPaperTrading,
currentDate: request.Date,
stopLossPrice: stopLossPrice, // Pass determined SL price
takeProfitPrice: takeProfitPrice); // Pass determined TP price
stopLossPrice: stopLossPrice,
takeProfitPrice: takeProfitPrice);
//trade.Fee = TradingHelpers.GetFeeAmount(fee, openPrice * quantity, account.Exchange);
position.Open = trade;
var closeDirection = request.Direction == TradeDirection.Long
? TradeDirection.Short
: TradeDirection.Long;
// Stop loss - Use the determined price
// Stop loss
position.StopLoss = exchangeService.BuildEmptyTrade(
request.Ticker,
stopLossPrice, // Use determined SL price
stopLossPrice,
position.Open.Quantity,
closeDirection,
request.MoneyManagement.Leverage,
@@ -92,13 +90,10 @@ namespace Managing.Application.Trading
request.Date,
TradeStatus.Requested);
// position.StopLoss.Fee = TradingHelpers.GetFeeAmount(fee,
// position.StopLoss.Price * position.StopLoss.Quantity, account.Exchange);
// Take profit - Use the determined price
// Take profit
position.TakeProfit1 = exchangeService.BuildEmptyTrade(
request.Ticker,
takeProfitPrice, // Use determined TP price
takeProfitPrice,
quantity,
closeDirection,
request.MoneyManagement.Leverage,

View File

@@ -147,10 +147,9 @@ public class OpenPosition : FlowBase
PositionInitiator.Bot,
signal.Date,
account.User,
100m, // Default bot trading balance
OpenPositionParameters.IsForBacktest,
lastPrice,
balance: WalletBalances.LastOrDefault().Value,
fee: Fee);
lastPrice);
var position = await new OpenPositionCommandHandler(_exchangeService, _accountService, _tradingService)
.Handle(command);