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:
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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();
|
||||
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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)
|
||||
{
|
||||
|
||||
@@ -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; }
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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,
|
||||
|
||||
@@ -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; }
|
||||
}
|
||||
}
|
||||
@@ -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,
|
||||
|
||||
@@ -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);
|
||||
|
||||
Reference in New Issue
Block a user