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

@@ -93,17 +93,6 @@ public class BacktestController : BaseController
return Ok(_backtester.DeleteBacktestByUser(user, id));
}
/// <summary>
/// Deletes all backtests.
/// </summary>
/// <returns>An ActionResult indicating the outcome of the operation.</returns>
[HttpDelete]
[Route("deleteAll")]
public ActionResult DeleteBacktests()
{
return Ok(_backtester.DeleteBacktests());
}
/// <summary>
/// Runs a backtest with the specified parameters.
/// </summary>

View File

@@ -3,6 +3,7 @@ using Managing.Application.Abstractions;
using Managing.Application.Abstractions.Services;
using Managing.Application.Hubs;
using Managing.Application.ManageBot.Commands;
using Managing.Common;
using Managing.Domain.Trades;
using MediatR;
using Microsoft.AspNetCore.Authorization;
@@ -123,9 +124,16 @@ public class BotController : BaseController
return BadRequest("Money management not found");
}
// Validate initialTradingBalance
if (request.InitialTradingBalance <= Constants.GMX.Config.MinimumPositionAmount)
{
return BadRequest(
$"Initial trading balance must be greater than {Constants.GMX.Config.MinimumPositionAmount}");
}
var result = await _mediator.Send(new StartBotCommand(request.BotType, request.BotName, request.Ticker,
request.Scenario, request.Timeframe, request.AccountName, request.MoneyManagementName, user,
request.IsForWatchOnly));
request.IsForWatchOnly, request.InitialTradingBalance));
await NotifyBotSubscriberAsync();
return Ok(result);
@@ -430,7 +438,7 @@ public class BotController : BaseController
var activeBots = _botService.GetActiveBots();
var bot = activeBots.FirstOrDefault(b => b.Name == request.BotName) as ApplicationTradingBot;
if (bot == null)
{
return NotFound($"Bot {request.BotName} not found or is not a trading bot");
@@ -474,7 +482,7 @@ public class BotController : BaseController
var activeBots = _botService.GetActiveBots();
var bot = activeBots.FirstOrDefault(b => b.Name == request.BotName) as ApplicationTradingBot;
if (bot == null)
{
return NotFound($"Bot {request.BotName} not found or is not a trading bot");
@@ -508,7 +516,7 @@ public class BotController : BaseController
// Close the position at market price
await bot.CloseTrade(signal, position, position.Open, lastCandle.Close, true);
await NotifyBotSubscriberAsync();
return Ok(position);
}
@@ -529,7 +537,7 @@ public class ClosePositionRequest
/// The name of the bot
/// </summary>
public string BotName { get; set; }
/// <summary>
/// The ID of the position to close
/// </summary>

View File

@@ -150,6 +150,7 @@ public class TradingController : BaseController
PositionInitiator.User,
DateTime.UtcNow,
user,
100m, // Default trading balance for user-initiated trades
isForPaperTrading: isForPaperTrading,
price: openPrice);
var result = await _openTradeCommandHandler.Handle(command);

View File

@@ -5,21 +5,20 @@ namespace Managing.Api.Models.Requests
{
public class StartBotRequest
{
[Required] public BotType BotType { get; set; }
[Required] public string BotName { get; set; }
[Required] public Ticker Ticker { get; set; }
[Required] public Timeframe Timeframe { get; set; }
[Required] public bool IsForWatchOnly { get; set; }
[Required] public string Scenario { get; set; }
[Required] public string AccountName { get; set; }
[Required] public string MoneyManagementName { get; set; }
/// <summary>
/// Initial trading balance in USD for the bot
/// </summary>
[Required]
public BotType BotType { get; set; }
[Required]
public string BotName { get; set; }
[Required]
public Ticker Ticker { get; set; }
[Required]
public Timeframe Timeframe { get; set; }
[Required]
public bool IsForWatchOnly { get; set; }
[Required]
public string Scenario { get; set; }
[Required]
public string AccountName { get; set; }
[Required]
public string MoneyManagementName { get; set; }
[Range(10.00, double.MaxValue, ErrorMessage = "Initial trading balance must be greater than ten")]
public decimal InitialTradingBalance { get; set; }
}
}
}

View File

@@ -2,6 +2,7 @@
using Managing.Application.Abstractions.Services;
using Managing.Domain.Accounts;
using Managing.Domain.MoneyManagements;
using Managing.Domain.Users;
using Managing.Infrastructure.Tests;
using Moq;
using static Managing.Common.Enums;
@@ -24,7 +25,6 @@ public class BaseTests
_moneyManagementService = new Mock<IMoneyManagementService>();
MoneyManagement = new MoneyManagement()
{
BalanceAtRisk = 1m, // 30%
Leverage = 2, // x2
Timeframe = Timeframe.FifteenMinutes,
StopLoss = 0.008m, // 0.8%
@@ -32,7 +32,7 @@ public class BaseTests
Name = "Default MM"
};
_ = _moneyManagementService.Setup(m => m.GetMoneyMangement(It.IsAny<Domain.Users.User>(), It.IsAny<string>()))
_ = _moneyManagementService.Setup(m => m.GetMoneyMangement(It.IsAny<User>(), It.IsAny<string>()))
.Returns(Task.FromResult(MoneyManagement));
_accountService.Setup(a => a.GetAccount(It.IsAny<string>(), It.IsAny<bool>(), It.IsAny<bool>()))

View File

@@ -9,7 +9,6 @@ using Managing.Core;
using Managing.Domain.Candles;
using Managing.Domain.MoneyManagements;
using Managing.Domain.Scenarios;
using MongoDB.Driver.Linq;
using Moq;
using Newtonsoft.Json;
using Xunit;
@@ -115,7 +114,6 @@ namespace Managing.Application.Tests
var moneyManagement = new MoneyManagement()
{
BalanceAtRisk = 0.05m,
Leverage = 1,
Timeframe = timeframe,
StopLoss = 0.01m,
@@ -179,7 +177,6 @@ namespace Managing.Application.Tests
{
var moneyManagement = new MoneyManagement()
{
BalanceAtRisk = 0.05m,
Leverage = 1,
Timeframe = timeframe,
StopLoss = s,
@@ -293,7 +290,6 @@ namespace Managing.Application.Tests
{
var moneyManagement = new MoneyManagement()
{
BalanceAtRisk = 0.05m,
Leverage = 1,
Timeframe = timeframe,
StopLoss = s,

File diff suppressed because one or more lines are too long

View File

@@ -12,7 +12,6 @@ namespace Managing.Application.Tests
{
_moneyManagement = new MoneyManagement()
{
BalanceAtRisk = 0.05m,
Leverage = 1,
Timeframe = Timeframe.OneDay,
StopLoss = 0.008m,

View File

@@ -2,7 +2,6 @@
using Managing.Application.Abstractions.Services;
using Managing.Application.Workers.Abstractions;
using Managing.Domain.Accounts;
using Managing.Domain.Candles;
using Managing.Domain.MoneyManagements;
using Managing.Domain.Scenarios;
using Managing.Domain.Shared.Helpers;
@@ -243,7 +242,6 @@ public class StatisticService : IStatisticService
{
var moneyManagement = new MoneyManagement()
{
BalanceAtRisk = 0.05m,
Leverage = 1,
Timeframe = timeframe,
StopLoss = 0.008m,

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);

View File

@@ -84,6 +84,8 @@
{
public const int USD = 30;
}
public const decimal MinimumPositionAmount = 10m;
}
public class TokenAddress

View File

@@ -11,8 +11,6 @@ namespace Managing.Domain.MoneyManagements
[Required]
public Timeframe Timeframe { get; set; }
[Required]
public decimal BalanceAtRisk { get; set; }
[Required]
public decimal StopLoss { get; set; }
[Required]
public decimal TakeProfit { get; set; }
@@ -25,7 +23,6 @@ namespace Managing.Domain.MoneyManagements
{
StopLoss /= 100;
TakeProfit /= 100;
BalanceAtRisk /= 100;
}
}
}

View File

@@ -12,18 +12,6 @@ namespace Managing.Domain.Shared.Helpers
price += price * moneyManagement.StopLoss;
}
public static decimal GetBalanceAtRisk(decimal balance, MoneyManagement moneyManagement)
{
decimal amountToRisk = balance * moneyManagement.BalanceAtRisk;
if (amountToRisk <= 1)
{
throw new Exception("Cannot open trade, not enough balance");
}
return amountToRisk;
}
public static decimal GetTakeProfitPrice(TradeDirection direction, decimal price, MoneyManagement moneyManagement, int count = 1)
{
decimal percentage = moneyManagement.TakeProfit * count;

View File

@@ -134,7 +134,6 @@ public static class TradingBox
moneyManagement.StopLoss = stoplossPercentage.Average();
moneyManagement.TakeProfit = takeProfitsPercentage.Average();
moneyManagement.BalanceAtRisk = originMoneyManagement.BalanceAtRisk * 100;
moneyManagement.Timeframe = originMoneyManagement.Timeframe;
moneyManagement.Leverage = originMoneyManagement.Leverage;
moneyManagement.Name = "Optimized";
@@ -196,32 +195,32 @@ public static class TradingBox
public static decimal GetTotalVolumeTraded(List<Position> positions)
{
decimal totalVolume = 0;
foreach (var position in positions)
{
// Add entry volume
totalVolume += position.Open.Quantity * position.Open.Price;
// Add exit volumes from stop loss or take profits if they were executed
if (position.StopLoss.Status == TradeStatus.Filled)
{
totalVolume += position.StopLoss.Quantity * position.StopLoss.Price;
}
if (position.TakeProfit1.Status == TradeStatus.Filled)
{
totalVolume += position.TakeProfit1.Quantity * position.TakeProfit1.Price;
}
if (position.TakeProfit2 != null && position.TakeProfit2.Status == TradeStatus.Filled)
{
totalVolume += position.TakeProfit2.Quantity * position.TakeProfit2.Price;
}
}
return totalVolume;
}
/// <summary>
/// Calculates the volume traded in the last 24 hours
/// </summary>
@@ -231,38 +230,38 @@ public static class TradingBox
{
decimal last24hVolume = 0;
DateTime cutoff = DateTime.UtcNow.AddHours(-24);
foreach (var position in positions)
{
// Check if any part of this position was traded in the last 24 hours
// Add entry volume if it was within the last 24 hours
if (position.Open.Date >= cutoff)
{
last24hVolume += position.Open.Quantity * position.Open.Price;
}
// Add exit volumes if they were executed within the last 24 hours
if (position.StopLoss.Status == TradeStatus.Filled && position.StopLoss.Date >= cutoff)
{
last24hVolume += position.StopLoss.Quantity * position.StopLoss.Price;
}
if (position.TakeProfit1.Status == TradeStatus.Filled && position.TakeProfit1.Date >= cutoff)
{
last24hVolume += position.TakeProfit1.Quantity * position.TakeProfit1.Price;
}
if (position.TakeProfit2 != null && position.TakeProfit2.Status == TradeStatus.Filled &&
if (position.TakeProfit2 != null && position.TakeProfit2.Status == TradeStatus.Filled &&
position.TakeProfit2.Date >= cutoff)
{
last24hVolume += position.TakeProfit2.Quantity * position.TakeProfit2.Price;
}
}
return last24hVolume;
}
/// <summary>
/// Gets the win/loss counts from positions
/// </summary>
@@ -272,7 +271,7 @@ public static class TradingBox
{
int wins = 0;
int losses = 0;
foreach (var position in positions)
{
// Only count finished positions
@@ -288,10 +287,10 @@ public static class TradingBox
}
}
}
return (wins, losses);
}
/// <summary>
/// Calculates the ROI for the last 24 hours
/// </summary>
@@ -302,25 +301,26 @@ public static class TradingBox
decimal profitLast24h = 0;
decimal investmentLast24h = 0;
DateTime cutoff = DateTime.UtcNow.AddHours(-24);
foreach (var position in positions)
{
// Only count positions that were opened or closed within the last 24 hours
if (position.IsFinished() &&
(position.Open.Date >= cutoff ||
(position.StopLoss.Status == TradeStatus.Filled && position.StopLoss.Date >= cutoff) ||
if (position.IsFinished() &&
(position.Open.Date >= cutoff ||
(position.StopLoss.Status == TradeStatus.Filled && position.StopLoss.Date >= cutoff) ||
(position.TakeProfit1.Status == TradeStatus.Filled && position.TakeProfit1.Date >= cutoff) ||
(position.TakeProfit2 != null && position.TakeProfit2.Status == TradeStatus.Filled && position.TakeProfit2.Date >= cutoff)))
(position.TakeProfit2 != null && position.TakeProfit2.Status == TradeStatus.Filled &&
position.TakeProfit2.Date >= cutoff)))
{
profitLast24h += position.ProfitAndLoss != null ? position.ProfitAndLoss.Realized : 0;
investmentLast24h += position.Open.Quantity * position.Open.Price;
}
}
// Avoid division by zero
if (investmentLast24h == 0)
return 0;
return (profitLast24h / investmentLast24h) * 100;
}
@@ -339,10 +339,10 @@ public static class TradingBox
.Where(p => p.IsFinished() && p.ProfitAndLoss != null)
.Sum(p => p.ProfitAndLoss.Realized);
}
// Convert time filter to a DateTime
DateTime cutoffDate = DateTime.UtcNow;
switch (timeFilter)
{
case "24H":
@@ -361,17 +361,18 @@ public static class TradingBox
cutoffDate = DateTime.UtcNow.AddYears(-1);
break;
}
// Include positions that were closed within the time range
return positions
.Where(p => p.IsFinished() && p.ProfitAndLoss != null &&
(p.Date >= cutoffDate ||
(p.StopLoss.Status == TradeStatus.Filled && p.StopLoss.Date >= cutoffDate) ||
(p.TakeProfit1.Status == TradeStatus.Filled && p.TakeProfit1.Date >= cutoffDate) ||
(p.TakeProfit2 != null && p.TakeProfit2.Status == TradeStatus.Filled && p.TakeProfit2.Date >= cutoffDate)))
(p.Date >= cutoffDate ||
(p.StopLoss.Status == TradeStatus.Filled && p.StopLoss.Date >= cutoffDate) ||
(p.TakeProfit1.Status == TradeStatus.Filled && p.TakeProfit1.Date >= cutoffDate) ||
(p.TakeProfit2 != null && p.TakeProfit2.Status == TradeStatus.Filled &&
p.TakeProfit2.Date >= cutoffDate)))
.Sum(p => p.ProfitAndLoss.Realized);
}
/// <summary>
/// Calculates ROI for positions within a specific time range
/// </summary>
@@ -385,10 +386,10 @@ public static class TradingBox
{
return 0;
}
// Convert time filter to a DateTime
DateTime cutoffDate = DateTime.UtcNow;
if (timeFilter != "Total")
{
switch (timeFilter)
@@ -410,29 +411,30 @@ public static class TradingBox
break;
}
}
// Filter positions in the time range
var filteredPositions = timeFilter == "Total"
var filteredPositions = timeFilter == "Total"
? positions.Where(p => p.IsFinished() && p.ProfitAndLoss != null)
: positions.Where(p => p.IsFinished() && p.ProfitAndLoss != null &&
(p.Date >= cutoffDate ||
(p.StopLoss.Status == TradeStatus.Filled && p.StopLoss.Date >= cutoffDate) ||
(p.TakeProfit1.Status == TradeStatus.Filled && p.TakeProfit1.Date >= cutoffDate) ||
(p.TakeProfit2 != null && p.TakeProfit2.Status == TradeStatus.Filled && p.TakeProfit2.Date >= cutoffDate)));
(p.Date >= cutoffDate ||
(p.StopLoss.Status == TradeStatus.Filled && p.StopLoss.Date >= cutoffDate) ||
(p.TakeProfit1.Status == TradeStatus.Filled && p.TakeProfit1.Date >= cutoffDate) ||
(p.TakeProfit2 != null && p.TakeProfit2.Status == TradeStatus.Filled &&
p.TakeProfit2.Date >= cutoffDate)));
// Calculate investment and profit
decimal totalInvestment = filteredPositions.Sum(p => p.Open.Quantity * p.Open.Price);
decimal totalProfit = filteredPositions.Sum(p => p.ProfitAndLoss.Realized);
// Calculate ROI
if (totalInvestment == 0)
{
return 0;
}
return (totalProfit / totalInvestment) * 100;
}
/// <summary>
/// Gets the win/loss counts from positions in a specific time range
/// </summary>
@@ -443,7 +445,7 @@ public static class TradingBox
{
// Convert time filter to a DateTime
DateTime cutoffDate = DateTime.UtcNow;
if (timeFilter != "Total")
{
switch (timeFilter)
@@ -465,19 +467,20 @@ public static class TradingBox
break;
}
}
// Filter positions in the time range
var filteredPositions = timeFilter == "Total"
var filteredPositions = timeFilter == "Total"
? positions.Where(p => p.IsFinished())
: positions.Where(p => p.IsFinished() &&
(p.Date >= cutoffDate ||
(p.StopLoss.Status == TradeStatus.Filled && p.StopLoss.Date >= cutoffDate) ||
(p.TakeProfit1.Status == TradeStatus.Filled && p.TakeProfit1.Date >= cutoffDate) ||
(p.TakeProfit2 != null && p.TakeProfit2.Status == TradeStatus.Filled && p.TakeProfit2.Date >= cutoffDate)));
(p.Date >= cutoffDate ||
(p.StopLoss.Status == TradeStatus.Filled && p.StopLoss.Date >= cutoffDate) ||
(p.TakeProfit1.Status == TradeStatus.Filled && p.TakeProfit1.Date >= cutoffDate) ||
(p.TakeProfit2 != null && p.TakeProfit2.Status == TradeStatus.Filled &&
p.TakeProfit2.Date >= cutoffDate)));
int wins = 0;
int losses = 0;
foreach (var position in filteredPositions)
{
if (position.ProfitAndLoss != null && position.ProfitAndLoss.Realized > 0)
@@ -489,7 +492,7 @@ public static class TradingBox
losses++;
}
}
return (wins, losses);
}
}

View File

@@ -1,5 +1,4 @@
using Managing.Core;
using Managing.Domain.Accounts;
using Managing.Domain.Accounts;
using Managing.Domain.Backtests;
using Managing.Domain.Bots;
using Managing.Domain.Candles;
@@ -12,9 +11,8 @@ using Managing.Domain.Users;
using Managing.Domain.Workers;
using Managing.Domain.Workflows.Synthetics;
using Managing.Infrastructure.Databases.MongoDb.Collections;
using static Managing.Common.Enums;
using MongoDB.Bson;
using Managing.Domain.Shared.Helpers;
using static Managing.Common.Enums;
namespace Managing.Infrastructure.Databases.MongoDb;
@@ -486,7 +484,6 @@ public static class MongoMappers
return new MoneyManagementDto
{
Timeframe = request.Timeframe,
BalanceAtRisk = request.BalanceAtRisk,
StopLoss = request.StopLoss,
TakeProfit = request.TakeProfit,
Leverage = request.Leverage,
@@ -503,7 +500,6 @@ public static class MongoMappers
return new MoneyManagement
{
Timeframe = request.Timeframe,
BalanceAtRisk = request.BalanceAtRisk,
StopLoss = request.StopLoss,
TakeProfit = request.TakeProfit,
Leverage = request.Leverage,

View File

@@ -69,12 +69,11 @@ namespace Managing.Infrastructure.Messengers.Discord
var moneymanagement = new MoneyManagement
{
BalanceAtRisk = 5,
StopLoss = stopLoss.GetValueOrDefault(),
TakeProfit = takeProfit.GetValueOrDefault(),
};
var tradeCommand = new OpenPositionRequest(accountName, moneymanagement, direction, ticker,
PositionInitiator.User, DateTime.UtcNow, new User());
PositionInitiator.User, DateTime.UtcNow, new User(), 100m);
var result = await _openTradeCommandHandler.Handle(tradeCommand);
var builder = new ComponentBuilder().WithButton("Close Position",
$"{Constants.DiscordButtonAction.ClosePosition}|{result.Open.ExchangeOrderId}");

View File

@@ -12,6 +12,7 @@ using Managing.Core;
using Managing.Domain.MoneyManagements;
using Managing.Domain.Statistics;
using Managing.Domain.Trades;
using Managing.Domain.Users;
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Logging;
using Newtonsoft.Json;
@@ -264,7 +265,7 @@ namespace Managing.Infrastructure.Messengers.Discord
var tradingService = (ITradingService)_services.GetService(typeof(ITradingService));
// Create default user for Discord bot operations
var defaultUser = new Domain.Users.User { Name = "DiscordBot" };
var defaultUser = new User { Name = "DiscordBot" };
var tradeCommand = new OpenPositionRequest(
accountName,
@@ -274,7 +275,7 @@ namespace Managing.Infrastructure.Messengers.Discord
initiator,
DateTime.UtcNow,
defaultUser,
ignoreSLTP: ignoreSLTP);
100m);
var position = await new OpenPositionCommandHandler(exchangeService, accountService, tradingService)
.Handle(tradeCommand);
@@ -416,7 +417,6 @@ namespace Managing.Infrastructure.Messengers.Discord
new MoneyManagement
{
Name = "MediumRisk",
BalanceAtRisk = 0.05m,
Leverage = 1,
StopLoss = 0.021m,
TakeProfit = 0.042m,

View File

@@ -1,14 +1,14 @@
import React, { useState, useEffect } from 'react'
import React, {useEffect, useState} from 'react'
import type { Backtest, MoneyManagement } from '../../../generated/ManagingApi'
import type { IModalProps } from '../../../global/type'
import type {Backtest, MoneyManagement} from '../../../generated/ManagingApi'
import type {IModalProps} from '../../../global/type'
import Modal from './Modal'
interface IBotNameModalProps extends IModalProps {
backtest: Backtest
isForWatchOnly: boolean
onSubmitBotName: (botName: string, backtest: Backtest, isForWatchOnly: boolean, moneyManagementName: string) => void
onSubmitBotName: (botName: string, backtest: Backtest, isForWatchOnly: boolean, moneyManagementName: string, initialTradingBalance: number) => void
moneyManagements: MoneyManagement[]
selectedMoneyManagement: string
setSelectedMoneyManagement: (name: string) => void
@@ -25,6 +25,7 @@ const BotNameModal: React.FC<IBotNameModalProps> = ({
setSelectedMoneyManagement,
}) => {
const [botName, setBotName] = useState<string>('')
const [initialTradingBalance, setInitialTradingBalance] = useState<number>(1000)
// Initialize botName when backtest changes
useEffect(() => {
@@ -35,8 +36,8 @@ const BotNameModal: React.FC<IBotNameModalProps> = ({
const handleSubmit = (e: React.FormEvent) => {
e.preventDefault()
if (botName.trim()) {
onSubmitBotName(botName, backtest, isForWatchOnly, selectedMoneyManagement)
if (botName.trim() && initialTradingBalance > 0) {
onSubmitBotName(botName, backtest, isForWatchOnly, selectedMoneyManagement, initialTradingBalance)
}
}
@@ -85,6 +86,22 @@ const BotNameModal: React.FC<IBotNameModalProps> = ({
</select>
</div>
<div className="form-control w-full mb-4">
<label className="label">
<span className="label-text">Initial Trading Balance ($)</span>
</label>
<input
type="number"
placeholder="Enter initial trading balance"
className="input input-bordered w-full"
value={initialTradingBalance}
onChange={(e) => setInitialTradingBalance(Number(e.target.value))}
min="10"
step="0.01"
required
/>
</div>
<p className="text-xs text-gray-500 mt-2 mb-4">
{isForWatchOnly
? 'The bot will run in watch-only mode and will not execute trades.'

View File

@@ -1,22 +1,13 @@
import { DotsVerticalIcon, TrashIcon } from '@heroicons/react/solid'
import {DotsVerticalIcon, TrashIcon} from '@heroicons/react/solid'
import moment from 'moment'
import React from 'react'
import useApiUrlStore from '../../../app/store/apiStore'
import type {
Backtest,
MoneyManagement,
StartBotRequest,
Ticker,
} from '../../../generated/ManagingApi'
import {
BacktestClient,
BotClient,
BotType,
} from '../../../generated/ManagingApi'
import type { IBacktestCards } from '../../../global/type'
import type {Backtest, MoneyManagement, StartBotRequest, Ticker,} from '../../../generated/ManagingApi'
import {BacktestClient, BotClient, BotType,} from '../../../generated/ManagingApi'
import type {IBacktestCards} from '../../../global/type'
import MoneyManagementModal from '../../../pages/settingsPage/moneymanagement/moneyManagementModal'
import { CardPosition, CardText, Toast } from '../../mollecules'
import {CardPosition, CardText, Toast} from '../../mollecules'
import CardPositionItem from '../Trading/CardPositionItem'
import TradeChart from '../Trading/TradeChart/TradeChart'
@@ -73,6 +64,7 @@ const BacktestCards: React.FC<IBacktestCards> = ({ list, setBacktests }) => {
scenario: backtest.scenario,
ticker: backtest.ticker as Ticker,
timeframe: backtest.timeframe,
initialTradingBalance: 1000,
}
await client
@@ -89,6 +81,10 @@ const BacktestCards: React.FC<IBacktestCards> = ({ list, setBacktests }) => {
const t = new Toast('Optimized backtest is running')
const client = new BacktestClient({}, apiUrl)
// Calculate dates for the API call
const startDate = backtest.candles[0].date
const endDate = backtest.candles[backtest.candles.length - 1].date
await client
.backtest_Run(
backtest.accountName,
@@ -96,12 +92,13 @@ const BacktestCards: React.FC<IBacktestCards> = ({ list, setBacktests }) => {
backtest.ticker as Ticker,
backtest.scenario,
backtest.timeframe,
false,
daysBetween(backtest.candles[0].date),
backtest.walletBalances[0].value,
'',
false,
backtest.optimizedMoneyManagement
false, // watchOnly
backtest.walletBalances[0].value, // balance
'', // moneyManagementName (empty since we're passing the optimized moneyManagement object)
startDate, // startDate
endDate, // endDate
false, // save
backtest.optimizedMoneyManagement // moneyManagement object
)
.then((backtest: Backtest) => {
t.update('success', `${backtest.ticker} Backtest Succeeded`)

View File

@@ -1,28 +1,23 @@
import { useQuery } from '@tanstack/react-query'
import React, { useEffect, useState } from 'react'
import { useForm, type SubmitHandler } from 'react-hook-form'
import {useQuery} from '@tanstack/react-query'
import React, {useEffect, useState} from 'react'
import {type SubmitHandler, useForm} from 'react-hook-form'
import useApiUrlStore from '../../../app/store/apiStore'
import {
Backtest,
MoneyManagement,
Ticker,
AccountClient,
Backtest,
BacktestClient,
BotType,
DataClient,
MoneyManagement,
MoneyManagementClient,
ScenarioClient,
Ticker,
Timeframe,
} from '../../../generated/ManagingApi'
import {
AccountClient,
BacktestClient,
BotType,
DataClient,
MoneyManagementClient,
ScenarioClient,
Timeframe,
} from '../../../generated/ManagingApi'
import type {
BacktestModalProps,
IBacktestsFormInput,
} from '../../../global/type'
import { Loader, Slider } from '../../atoms'
import { Modal, Toast } from '../../mollecules'
import type {BacktestModalProps, IBacktestsFormInput,} from '../../../global/type'
import {Loader, Slider} from '../../atoms'
import {Modal, Toast} from '../../mollecules'
import FormInput from '../../mollecules/FormInput/FormInput'
import CustomMoneyManagement from '../CustomMoneyManagement/CustomMoneyManagement'
@@ -100,6 +95,8 @@ const BacktestModal: React.FC<BacktestModalProps> = ({
// Use the name of the money management strategy if custom is not provided
const moneyManagementName = customMoneyManagement ? undefined : selectedMoneyManagement
console.log(customMoneyManagement)
backtestClient
.backtest_Run(
form.accountName,
@@ -122,7 +119,6 @@ const BacktestModal: React.FC<BacktestModalProps> = ({
if (showLoopSlider && selectedLoopQuantity > loopCount) {
const nextCount = loopCount + 1
const mm: MoneyManagement = {
balanceAtRisk: backtest.optimizedMoneyManagement.balanceAtRisk,
leverage: backtest.optimizedMoneyManagement.leverage,
name: backtest.optimizedMoneyManagement.name + nextCount,
stopLoss: backtest.optimizedMoneyManagement.stopLoss,
@@ -148,6 +144,7 @@ const BacktestModal: React.FC<BacktestModalProps> = ({
function onMoneyManagementChange(e: any) {
if (e.target.value === 'custom') {
setShowCustomMoneyManagement(true)
setCustomMoneyManagement(e.target.value)
} else {
setShowCustomMoneyManagement(false)
setCustomMoneyManagement(undefined)
@@ -280,6 +277,32 @@ const BacktestModal: React.FC<BacktestModalProps> = ({
</select>
</FormInput>
{/* Losing streak info */}
{(() => {
// Find the selected money management object
let mm: MoneyManagement | undefined = undefined;
if (showCustomMoneyManagement && customMoneyManagement) {
mm = { ...customMoneyManagement, stopLoss: customMoneyManagement.stopLoss / 100 };
} else if (selectedMoneyManagement) {
mm = moneyManagements.find((m) => m.name === selectedMoneyManagement);
}
// Use actual initial balance and a minimum threshold
const initialBalance = balance;
const minBalance = 10; // You can make this configurable if needed
if (mm && mm.leverage && mm.stopLoss && initialBalance > minBalance) {
const perLoss = mm.leverage * mm.stopLoss;
if (perLoss > 0 && perLoss < 1) {
const streak = Math.floor(Math.log(minBalance / initialBalance) / Math.log(1 - perLoss));
return (
<div className="text-sm text-info mt-1">
Max losing streak before balance drops below {minBalance}: <b>{streak}</b> trades (with leverage x{mm.leverage} and stop loss {Math.round(mm.stopLoss * 10000) / 100}% per trade, starting from {initialBalance})
</div>
);
}
}
return null;
})()}
{showCustomMoneyManagement && (
<div className="mt-6">
<CustomMoneyManagement

View File

@@ -1,23 +1,12 @@
import {
ChevronDownIcon,
ChevronRightIcon,
PlayIcon,
TrashIcon,
EyeIcon
} from '@heroicons/react/solid'
import React, { useEffect, useState } from 'react'
import { useQuery } from '@tanstack/react-query'
import {ChevronDownIcon, ChevronRightIcon, EyeIcon, PlayIcon, TrashIcon} from '@heroicons/react/solid'
import React, {useEffect, useState} from 'react'
import {useQuery} from '@tanstack/react-query'
import useApiUrlStore from '../../../app/store/apiStore'
import type {
Backtest,
MoneyManagement,
StartBotRequest,
Ticker,
} from '../../../generated/ManagingApi'
import { BacktestClient, BotClient, MoneyManagementClient } from '../../../generated/ManagingApi'
import type { IBacktestCards } from '../../../global/type'
import { Toast, SelectColumnFilter, Table, CardText } from '../../mollecules'
import type {Backtest, StartBotRequest, Ticker,} from '../../../generated/ManagingApi'
import {BacktestClient, BotClient, MoneyManagementClient} from '../../../generated/ManagingApi'
import type {IBacktestCards} from '../../../global/type'
import {CardText, SelectColumnFilter, Table, Toast} from '../../mollecules'
import BotNameModal from '../../mollecules/Modal/BotNameModal'
import BacktestRowDetails from './backtestRowDetails'
@@ -50,7 +39,7 @@ const BacktestTable: React.FC<IBacktestCards> = ({ list, isFetching, setBacktest
}
}, [moneyManagements])
async function runBot(botName: string, backtest: Backtest, isForWatchOnly: boolean, moneyManagementName: string) {
async function runBot(botName: string, backtest: Backtest, isForWatchOnly: boolean, moneyManagementName: string, initialTradingBalance: number) {
const t = new Toast('Bot is starting')
const client = new BotClient({}, apiUrl)
@@ -70,6 +59,7 @@ const BacktestTable: React.FC<IBacktestCards> = ({ list, isFetching, setBacktest
scenario: backtest.scenario,
ticker: backtest.ticker as Ticker,
timeframe: backtest.timeframe,
initialTradingBalance: initialTradingBalance,
}
await client
@@ -92,8 +82,8 @@ const BacktestTable: React.FC<IBacktestCards> = ({ list, isFetching, setBacktest
setShowBotNameModal(false)
}
const handleSubmitBotName = (botName: string, backtest: Backtest, isForWatchOnly: boolean, moneyManagementName: string) => {
runBot(botName, backtest, isForWatchOnly, moneyManagementName)
const handleSubmitBotName = (botName: string, backtest: Backtest, isForWatchOnly: boolean, moneyManagementName: string, initialTradingBalance: number) => {
runBot(botName, backtest, isForWatchOnly, moneyManagementName, initialTradingBalance)
setShowBotNameModal(false)
}
@@ -364,8 +354,8 @@ const BacktestTable: React.FC<IBacktestCards> = ({ list, isFetching, setBacktest
onClose={handleCloseBotNameModal}
backtest={currentBacktest}
isForWatchOnly={isForWatchOnly}
onSubmitBotName={(botName, backtest, isForWatchOnly, moneyManagementName) =>
handleSubmitBotName(botName, backtest, isForWatchOnly, moneyManagementName)
onSubmitBotName={(botName, backtest, isForWatchOnly, moneyManagementName, initialTradingBalance) =>
handleSubmitBotName(botName, backtest, isForWatchOnly, moneyManagementName, initialTradingBalance)
}
moneyManagements={moneyManagements}
selectedMoneyManagement={selectedMoneyManagement}

View File

@@ -1,9 +1,8 @@
import React, { useEffect, useState } from 'react'
import React, {useEffect, useState} from 'react'
import type { MoneyManagement, Timeframe } from '../../../generated/ManagingApi'
import { Slider } from '../../atoms'
import type {MoneyManagement, Timeframe} from '../../../generated/ManagingApi'
import FormInput from '../../mollecules/FormInput/FormInput'
import { useCustomMoneyManagement } from '../../../app/store/customMoneyManagement'
import {useCustomMoneyManagement} from '../../../app/store/customMoneyManagement'
type ICustomMoneyManagement = {
onCreateMoneyManagement: (moneyManagement: MoneyManagement) => void
@@ -18,14 +17,12 @@ const CustomMoneyManagement: React.FC<ICustomMoneyManagement> = ({
}) => {
const { moneyManagement, setCustomMoneyManagement } = useCustomMoneyManagement()
const [balanceAtRisk, setBalanceAtRisk] = useState<number>(moneyManagement?.balanceAtRisk || 5)
const [leverage, setLeverage] = useState<number>(moneyManagement?.leverage || 1)
const [takeProfit, setTakeProfit] = useState<number>(moneyManagement?.takeProfit || 2)
const [stopLoss, setStopLoss] = useState<number>(moneyManagement?.stopLoss || 1)
const handleCreateMoneyManagement = () => {
const moneyManagement: MoneyManagement = {
balanceAtRisk,
leverage,
name: 'custom',
stopLoss,
@@ -38,7 +35,7 @@ const CustomMoneyManagement: React.FC<ICustomMoneyManagement> = ({
useEffect(() => {
handleCreateMoneyManagement()
}, [balanceAtRisk, leverage, takeProfit, stopLoss])
}, [leverage, takeProfit, stopLoss])
return (
<>
@@ -49,23 +46,6 @@ const CustomMoneyManagement: React.FC<ICustomMoneyManagement> = ({
Custom MoneyManagement
</div>
<div className="collapse-content">
<FormInput
label="Balance at risk"
htmlFor="balanceAtRisk"
inline={false}
>
<input
id="balanceAtRisk"
value={balanceAtRisk}
onChange={(e) => setBalanceAtRisk(parseInt(e.target.value))}
min="1"
max="100"
step="1"
type='number'
className='input input-bordered'
></input>
</FormInput>
<FormInput label="Leverage" htmlFor="leverage" inline={false}>
<input
id="leverage"

View File

@@ -2572,7 +2572,6 @@ export enum TradeDirection {
export interface MoneyManagement {
name: string;
timeframe: Timeframe;
balanceAtRisk: number;
stopLoss: number;
takeProfit: number;
leverage: number;
@@ -2842,6 +2841,7 @@ export interface StartBotRequest {
scenario: string;
accountName: string;
moneyManagementName: string;
initialTradingBalance: number;
}
export interface TradingBot {

View File

@@ -1,207 +1,183 @@
import { useEffect, useState } from 'react'
import type { SubmitHandler } from 'react-hook-form'
import { useForm } from 'react-hook-form'
import {useEffect, useState} from 'react'
import type {SubmitHandler} from 'react-hook-form'
import {useForm} from 'react-hook-form'
import useApiUrlStore from '../../../app/store/apiStore'
import { Slider } from '../../../components/atoms'
import { FormInput, Modal, Toast } from '../../../components/mollecules'
import type { MoneyManagement } from '../../../generated/ManagingApi'
import {
MoneyManagementClient,
Timeframe,
} from '../../../generated/ManagingApi'
import type {
IMoneyManagementModalProps,
IMoneyManagementFormInput,
} from '../../../global/type'
import {Slider} from '../../../components/atoms'
import {FormInput, Modal, Toast} from '../../../components/mollecules'
import type {MoneyManagement} from '../../../generated/ManagingApi'
import {MoneyManagementClient, Timeframe,} from '../../../generated/ManagingApi'
import type {IMoneyManagementFormInput, IMoneyManagementModalProps,} from '../../../global/type'
const MoneyManagementModal: React.FC<IMoneyManagementModalProps> = ({
showModal,
onClose,
moneyManagement,
disableInputs = false,
}) => {
const [balanceAtRisk, setBalanceAtRisk] = useState<number>(5)
const [takeProfit, setTakeProfit] = useState<number>(20)
const [name, setName] = useState<string>('')
const [stopLoss, setStopLoss] = useState<number>(10)
const [leverage, setLeverage] = useState<number>(1)
const [timeframe, setTimeframe] = useState<Timeframe>(
Timeframe.FifteenMinutes
)
const { reset, register, handleSubmit } = useForm<IMoneyManagementFormInput>()
const { apiUrl } = useApiUrlStore()
showModal,
onClose,
moneyManagement,
disableInputs = false,
}) => {
const [takeProfit, setTakeProfit] = useState<number>(20)
const [name, setName] = useState<string>('')
const [stopLoss, setStopLoss] = useState<number>(10)
const [leverage, setLeverage] = useState<number>(1)
const [timeframe, setTimeframe] = useState<Timeframe>(
Timeframe.FifteenMinutes
)
const {reset, register, handleSubmit} = useForm<IMoneyManagementFormInput>()
const {apiUrl} = useApiUrlStore()
async function createMoneyManagement(form: IMoneyManagementFormInput) {
const t = new Toast('Creating settings')
const client = new MoneyManagementClient({}, apiUrl)
const mm: MoneyManagement = {
balanceAtRisk: balanceAtRisk / 100,
leverage: leverage,
name: name,
stopLoss: stopLoss / 100,
takeProfit: takeProfit / 100,
timeframe: form.timeframe,
async function createMoneyManagement(form: IMoneyManagementFormInput) {
const t = new Toast('Creating settings')
const client = new MoneyManagementClient({}, apiUrl)
const mm: MoneyManagement = {
leverage: leverage,
name: name,
stopLoss: stopLoss / 100,
takeProfit: takeProfit / 100,
timeframe: form.timeframe,
}
await client
.moneyManagement_PostMoneyManagement(mm)
.then(() => {
t.update('success', 'Settings created')
})
.catch((err) => {
t.update('error', 'Error :' + err)
})
}
await client
.moneyManagement_PostMoneyManagement(mm)
.then(() => {
t.update('success', 'Settings created')
})
.catch((err) => {
t.update('error', 'Error :' + err)
})
}
const onSubmit: SubmitHandler<IMoneyManagementFormInput> = async (form) => {
// @ts-ignore
await createMoneyManagement(form)
onClose()
}
function onChangeName(e: any) {
setName(e.target.value)
}
function onTakeProfitChange(e: any) {
setTakeProfit(e.target.value)
}
function onStopLossChange(e: any) {
setStopLoss(e.target.value)
}
function onLeverageChange(e: any) {
setLeverage(e.target.value)
}
function onBalanceAtRiskChange(e: any) {
setBalanceAtRisk(e.target.value)
}
useEffect(() => {
if (moneyManagement) {
console.log(moneyManagement)
setBalanceAtRisk(moneyManagement.balanceAtRisk * 100)
setTakeProfit(moneyManagement.takeProfit * 100)
setStopLoss(moneyManagement.stopLoss * 100)
setLeverage(moneyManagement.leverage)
setTimeframe(moneyManagement.timeframe)
setName(moneyManagement.name)
const defaultValues: MoneyManagement = {
balanceAtRisk: moneyManagement.balanceAtRisk,
leverage: moneyManagement.leverage,
name: moneyManagement.name || '',
stopLoss: moneyManagement.stopLoss,
takeProfit: moneyManagement.takeProfit,
timeframe: moneyManagement.timeframe,
}
reset({ ...defaultValues })
const onSubmit: SubmitHandler<IMoneyManagementFormInput> = async (form) => {
// @ts-ignore
await createMoneyManagement(form)
onClose()
}
}, [showModal, moneyManagement])
return (
<Modal
titleHeader="Money Management"
showModal={showModal}
onClose={onClose}
onSubmit={handleSubmit(onSubmit)}
>
<FormInput label="Name" htmlFor="name">
<input
value={name}
type="text"
placeholder="Ex: HighRiskMoneyManagement"
className="input input-bordered w-full max-w-xs"
{...register('name')}
onChange={onChangeName}
disabled={disableInputs}
/>
</FormInput>
function onChangeName(e: any) {
setName(e.target.value)
}
<FormInput label="Timeframe" htmlFor="timeframe">
<select
className="select w-full max-w-xs"
{...register('timeframe', {
disabled: disableInputs,
value: timeframe,
})}
function onTakeProfitChange(e: any) {
setTakeProfit(e.target.value)
}
function onStopLossChange(e: any) {
setStopLoss(e.target.value)
}
function onLeverageChange(e: any) {
setLeverage(e.target.value)
}
useEffect(() => {
if (moneyManagement) {
console.log(moneyManagement)
setTakeProfit(moneyManagement.takeProfit * 100)
setStopLoss(moneyManagement.stopLoss * 100)
setLeverage(moneyManagement.leverage)
setTimeframe(moneyManagement.timeframe)
setName(moneyManagement.name)
const defaultValues: MoneyManagement = {
leverage: moneyManagement.leverage,
name: moneyManagement.name || '',
stopLoss: moneyManagement.stopLoss,
takeProfit: moneyManagement.takeProfit,
timeframe: moneyManagement.timeframe,
}
reset({...defaultValues})
}
}, [showModal, moneyManagement])
return (
<Modal
titleHeader="Money Management"
showModal={showModal}
onClose={onClose}
onSubmit={handleSubmit(onSubmit)}
>
{Object.keys(Timeframe).map((item) => (
<option key={item} value={item}>
{item}
</option>
))}
</select>
</FormInput>
<FormInput label="Name" htmlFor="name">
<input
value={name}
type="text"
placeholder="Ex: HighRiskMoneyManagement"
className="input input-bordered w-full max-w-xs"
{...register('name')}
onChange={onChangeName}
disabled={disableInputs}
/>
</FormInput>
<FormInput label="Balance at risk" htmlFor="balanceAtRisk">
<Slider
id="balanceAtRisk"
value={balanceAtRisk}
onChange={onBalanceAtRiskChange}
step="1"
max="100"
suffixValue="%"
disabled={disableInputs}
></Slider>
</FormInput>
<FormInput label="Timeframe" htmlFor="timeframe">
<select
className="select w-full max-w-xs"
{...register('timeframe', {
disabled: disableInputs,
value: timeframe,
})}
>
{Object.keys(Timeframe).map((item) => (
<option key={item} value={item}>
{item}
</option>
))}
</select>
</FormInput>
<FormInput label="TP%" htmlFor="takeProfit">
<Slider
id="takeProfit"
value={takeProfit}
onChange={onTakeProfitChange}
step="0.01"
max="15"
suffixValue="%"
disabled={disableInputs}
></Slider>
</FormInput>
<FormInput label="TP%" htmlFor="takeProfit">
<Slider
id="takeProfit"
value={takeProfit}
onChange={onTakeProfitChange}
step="0.01"
max="15"
suffixValue="%"
disabled={disableInputs}
></Slider>
</FormInput>
<FormInput label="SL%" htmlFor="stopLoss">
<Slider
id="stopLoss"
value={stopLoss}
onChange={onStopLossChange}
step="0.01"
max="15"
suffixValue="%"
disabled={disableInputs}
></Slider>
</FormInput>
<FormInput label="SL%" htmlFor="stopLoss">
<Slider
id="stopLoss"
value={stopLoss}
onChange={onStopLossChange}
step="0.01"
max="15"
suffixValue="%"
disabled={disableInputs}
></Slider>
</FormInput>
<FormInput label="Leverage" htmlFor="leverage">
<Slider
id="leverage"
value={leverage}
onChange={onLeverageChange}
step="1"
min="1"
max="50"
prefixValue="x"
disabled={disableInputs}
></Slider>
</FormInput>
<FormInput label="Leverage" htmlFor="leverage">
<Slider
id="leverage"
value={leverage}
onChange={onLeverageChange}
step="1"
min="1"
max="50"
prefixValue="x"
disabled={disableInputs}
></Slider>
</FormInput>
<div className="form-control mb-5">
<div className="input-group">
<label htmlFor="leverage" className="form-label">
Risk reward
</label>
<div className="ml-4">{(takeProfit / stopLoss).toFixed(2)}</div>
</div>
</div>
{disableInputs ? null : (
<div className="modal-action">
<button type="submit" className="btn">
Build
</button>
</div>
)}
</Modal>
)
<div className="form-control mb-5">
<div className="input-group">
<label htmlFor="leverage" className="form-label">
Risk reward
</label>
<div className="ml-4">{(takeProfit / stopLoss).toFixed(2)}</div>
</div>
</div>
{disableInputs ? null : (
<div className="modal-action">
<button type="submit" className="btn">
Build
</button>
</div>
)}
</Modal>
)
}
export default MoneyManagementModal

View File

@@ -1,15 +1,11 @@
import { PencilAltIcon, TrashIcon } from '@heroicons/react/solid'
import React, { useEffect, useState } from 'react'
import {PencilAltIcon, TrashIcon} from '@heroicons/react/solid'
import React, {useEffect, useState} from 'react'
import useApiUrlStore from '../../../app/store/apiStore'
import {
Toast,
SelectColumnFilter,
Table,
} from '../../../components/mollecules'
import type { MoneyManagement } from '../../../generated/ManagingApi'
import { MoneyManagementClient } from '../../../generated/ManagingApi'
import type { IMoneyManagementList } from '../../../global/type'
import {SelectColumnFilter, Table, Toast,} from '../../../components/mollecules'
import type {MoneyManagement} from '../../../generated/ManagingApi'
import {MoneyManagementClient} from '../../../generated/ManagingApi'
import type {IMoneyManagementList} from '../../../global/type'
import MoneyManagementModal from './moneyManagementModal'
@@ -63,13 +59,7 @@ const MoneyManagementTable: React.FC<IMoneyManagementList> = ({ list }) => {
disableSortBy: true,
},
{
Cell: ({ cell }: any) => <>{cell.row.values.balanceAtRisk * 100} %</>,
Header: 'Balance used',
accessor: 'balanceAtRisk',
disableFilters: true,
},
{
Cell: ({ cell }) => <>{cell.row.values.stopLoss * 100} %</>,
Cell: ({ cell }: any) => <>{cell.row.values.stopLoss * 100} %</>,
Header: 'SL',
accessor: 'stopLoss',
disableFilters: true,