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:
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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; }
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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>()))
|
||||
|
||||
@@ -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
@@ -12,7 +12,6 @@ namespace Managing.Application.Tests
|
||||
{
|
||||
_moneyManagement = new MoneyManagement()
|
||||
{
|
||||
BalanceAtRisk = 0.05m,
|
||||
Leverage = 1,
|
||||
Timeframe = Timeframe.OneDay,
|
||||
StopLoss = 0.008m,
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -84,6 +84,8 @@
|
||||
{
|
||||
public const int USD = 30;
|
||||
}
|
||||
|
||||
public const decimal MinimumPositionAmount = 10m;
|
||||
}
|
||||
|
||||
public class TokenAddress
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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,
|
||||
|
||||
@@ -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}");
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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.'
|
||||
|
||||
@@ -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`)
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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}
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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,
|
||||
|
||||
Reference in New Issue
Block a user