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));
|
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>
|
/// <summary>
|
||||||
/// Runs a backtest with the specified parameters.
|
/// Runs a backtest with the specified parameters.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ using Managing.Application.Abstractions;
|
|||||||
using Managing.Application.Abstractions.Services;
|
using Managing.Application.Abstractions.Services;
|
||||||
using Managing.Application.Hubs;
|
using Managing.Application.Hubs;
|
||||||
using Managing.Application.ManageBot.Commands;
|
using Managing.Application.ManageBot.Commands;
|
||||||
|
using Managing.Common;
|
||||||
using Managing.Domain.Trades;
|
using Managing.Domain.Trades;
|
||||||
using MediatR;
|
using MediatR;
|
||||||
using Microsoft.AspNetCore.Authorization;
|
using Microsoft.AspNetCore.Authorization;
|
||||||
@@ -123,9 +124,16 @@ public class BotController : BaseController
|
|||||||
return BadRequest("Money management not found");
|
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,
|
var result = await _mediator.Send(new StartBotCommand(request.BotType, request.BotName, request.Ticker,
|
||||||
request.Scenario, request.Timeframe, request.AccountName, request.MoneyManagementName, user,
|
request.Scenario, request.Timeframe, request.AccountName, request.MoneyManagementName, user,
|
||||||
request.IsForWatchOnly));
|
request.IsForWatchOnly, request.InitialTradingBalance));
|
||||||
|
|
||||||
await NotifyBotSubscriberAsync();
|
await NotifyBotSubscriberAsync();
|
||||||
return Ok(result);
|
return Ok(result);
|
||||||
|
|||||||
@@ -150,6 +150,7 @@ public class TradingController : BaseController
|
|||||||
PositionInitiator.User,
|
PositionInitiator.User,
|
||||||
DateTime.UtcNow,
|
DateTime.UtcNow,
|
||||||
user,
|
user,
|
||||||
|
100m, // Default trading balance for user-initiated trades
|
||||||
isForPaperTrading: isForPaperTrading,
|
isForPaperTrading: isForPaperTrading,
|
||||||
price: openPrice);
|
price: openPrice);
|
||||||
var result = await _openTradeCommandHandler.Handle(command);
|
var result = await _openTradeCommandHandler.Handle(command);
|
||||||
|
|||||||
@@ -5,21 +5,20 @@ namespace Managing.Api.Models.Requests
|
|||||||
{
|
{
|
||||||
public class StartBotRequest
|
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]
|
[Required]
|
||||||
public BotType BotType { get; set; }
|
[Range(10.00, double.MaxValue, ErrorMessage = "Initial trading balance must be greater than ten")]
|
||||||
[Required]
|
public decimal InitialTradingBalance { get; set; }
|
||||||
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; }
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -2,6 +2,7 @@
|
|||||||
using Managing.Application.Abstractions.Services;
|
using Managing.Application.Abstractions.Services;
|
||||||
using Managing.Domain.Accounts;
|
using Managing.Domain.Accounts;
|
||||||
using Managing.Domain.MoneyManagements;
|
using Managing.Domain.MoneyManagements;
|
||||||
|
using Managing.Domain.Users;
|
||||||
using Managing.Infrastructure.Tests;
|
using Managing.Infrastructure.Tests;
|
||||||
using Moq;
|
using Moq;
|
||||||
using static Managing.Common.Enums;
|
using static Managing.Common.Enums;
|
||||||
@@ -24,7 +25,6 @@ public class BaseTests
|
|||||||
_moneyManagementService = new Mock<IMoneyManagementService>();
|
_moneyManagementService = new Mock<IMoneyManagementService>();
|
||||||
MoneyManagement = new MoneyManagement()
|
MoneyManagement = new MoneyManagement()
|
||||||
{
|
{
|
||||||
BalanceAtRisk = 1m, // 30%
|
|
||||||
Leverage = 2, // x2
|
Leverage = 2, // x2
|
||||||
Timeframe = Timeframe.FifteenMinutes,
|
Timeframe = Timeframe.FifteenMinutes,
|
||||||
StopLoss = 0.008m, // 0.8%
|
StopLoss = 0.008m, // 0.8%
|
||||||
@@ -32,7 +32,7 @@ public class BaseTests
|
|||||||
Name = "Default MM"
|
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));
|
.Returns(Task.FromResult(MoneyManagement));
|
||||||
|
|
||||||
_accountService.Setup(a => a.GetAccount(It.IsAny<string>(), It.IsAny<bool>(), It.IsAny<bool>()))
|
_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.Candles;
|
||||||
using Managing.Domain.MoneyManagements;
|
using Managing.Domain.MoneyManagements;
|
||||||
using Managing.Domain.Scenarios;
|
using Managing.Domain.Scenarios;
|
||||||
using MongoDB.Driver.Linq;
|
|
||||||
using Moq;
|
using Moq;
|
||||||
using Newtonsoft.Json;
|
using Newtonsoft.Json;
|
||||||
using Xunit;
|
using Xunit;
|
||||||
@@ -115,7 +114,6 @@ namespace Managing.Application.Tests
|
|||||||
|
|
||||||
var moneyManagement = new MoneyManagement()
|
var moneyManagement = new MoneyManagement()
|
||||||
{
|
{
|
||||||
BalanceAtRisk = 0.05m,
|
|
||||||
Leverage = 1,
|
Leverage = 1,
|
||||||
Timeframe = timeframe,
|
Timeframe = timeframe,
|
||||||
StopLoss = 0.01m,
|
StopLoss = 0.01m,
|
||||||
@@ -179,7 +177,6 @@ namespace Managing.Application.Tests
|
|||||||
{
|
{
|
||||||
var moneyManagement = new MoneyManagement()
|
var moneyManagement = new MoneyManagement()
|
||||||
{
|
{
|
||||||
BalanceAtRisk = 0.05m,
|
|
||||||
Leverage = 1,
|
Leverage = 1,
|
||||||
Timeframe = timeframe,
|
Timeframe = timeframe,
|
||||||
StopLoss = s,
|
StopLoss = s,
|
||||||
@@ -293,7 +290,6 @@ namespace Managing.Application.Tests
|
|||||||
{
|
{
|
||||||
var moneyManagement = new MoneyManagement()
|
var moneyManagement = new MoneyManagement()
|
||||||
{
|
{
|
||||||
BalanceAtRisk = 0.05m,
|
|
||||||
Leverage = 1,
|
Leverage = 1,
|
||||||
Timeframe = timeframe,
|
Timeframe = timeframe,
|
||||||
StopLoss = s,
|
StopLoss = s,
|
||||||
|
|||||||
File diff suppressed because one or more lines are too long
@@ -12,7 +12,6 @@ namespace Managing.Application.Tests
|
|||||||
{
|
{
|
||||||
_moneyManagement = new MoneyManagement()
|
_moneyManagement = new MoneyManagement()
|
||||||
{
|
{
|
||||||
BalanceAtRisk = 0.05m,
|
|
||||||
Leverage = 1,
|
Leverage = 1,
|
||||||
Timeframe = Timeframe.OneDay,
|
Timeframe = Timeframe.OneDay,
|
||||||
StopLoss = 0.008m,
|
StopLoss = 0.008m,
|
||||||
|
|||||||
@@ -2,7 +2,6 @@
|
|||||||
using Managing.Application.Abstractions.Services;
|
using Managing.Application.Abstractions.Services;
|
||||||
using Managing.Application.Workers.Abstractions;
|
using Managing.Application.Workers.Abstractions;
|
||||||
using Managing.Domain.Accounts;
|
using Managing.Domain.Accounts;
|
||||||
using Managing.Domain.Candles;
|
|
||||||
using Managing.Domain.MoneyManagements;
|
using Managing.Domain.MoneyManagements;
|
||||||
using Managing.Domain.Scenarios;
|
using Managing.Domain.Scenarios;
|
||||||
using Managing.Domain.Shared.Helpers;
|
using Managing.Domain.Shared.Helpers;
|
||||||
@@ -243,7 +242,6 @@ public class StatisticService : IStatisticService
|
|||||||
{
|
{
|
||||||
var moneyManagement = new MoneyManagement()
|
var moneyManagement = new MoneyManagement()
|
||||||
{
|
{
|
||||||
BalanceAtRisk = 0.05m,
|
|
||||||
Leverage = 1,
|
Leverage = 1,
|
||||||
Timeframe = timeframe,
|
Timeframe = timeframe,
|
||||||
StopLoss = 0.008m,
|
StopLoss = 0.008m,
|
||||||
|
|||||||
@@ -7,10 +7,10 @@ namespace Managing.Application.Abstractions
|
|||||||
{
|
{
|
||||||
public interface IBotFactory
|
public interface IBotFactory
|
||||||
{
|
{
|
||||||
ITradingBot CreateScalpingBot(string accountName, MoneyManagement moneyManagement, string name, 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);
|
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);
|
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);
|
ITradingBot CreateBacktestFlippingBot(string accountName, MoneyManagement moneyManagement, Ticker ticker, string scenario, Timeframe interval, bool isForWatchingOnly, decimal initialTradingBalance);
|
||||||
IBot CreateSimpleBot(string botName, Workflow workflow);
|
IBot CreateSimpleBot(string botName, Workflow workflow);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,4 @@
|
|||||||
using Managing.Common;
|
using Managing.Common;
|
||||||
using Managing.Domain.Accounts;
|
|
||||||
using Managing.Domain.Bots;
|
using Managing.Domain.Bots;
|
||||||
using Managing.Domain.MoneyManagements;
|
using Managing.Domain.MoneyManagements;
|
||||||
using Managing.Domain.Workflows;
|
using Managing.Domain.Workflows;
|
||||||
@@ -18,16 +17,16 @@ public interface IBotService
|
|||||||
BotBackup GetBotBackup(string name);
|
BotBackup GetBotBackup(string name);
|
||||||
|
|
||||||
ITradingBot CreateScalpingBot(string accountName, MoneyManagement moneyManagement, string name, Enums.Ticker ticker,
|
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,
|
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,
|
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,
|
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);
|
IBot CreateSimpleBot(string botName, Workflow workflow);
|
||||||
Task<string> StopBot(string requestName);
|
Task<string> StopBot(string requestName);
|
||||||
|
|||||||
@@ -11,10 +11,10 @@ using Managing.Domain.Scenarios;
|
|||||||
using Managing.Domain.Shared.Helpers;
|
using Managing.Domain.Shared.Helpers;
|
||||||
using Managing.Domain.Strategies;
|
using Managing.Domain.Strategies;
|
||||||
using Managing.Domain.Strategies.Base;
|
using Managing.Domain.Strategies.Base;
|
||||||
|
using Managing.Domain.Users;
|
||||||
using Managing.Domain.Workflows;
|
using Managing.Domain.Workflows;
|
||||||
using Microsoft.Extensions.Logging;
|
using Microsoft.Extensions.Logging;
|
||||||
using static Managing.Common.Enums;
|
using static Managing.Common.Enums;
|
||||||
using Managing.Domain.Users;
|
|
||||||
|
|
||||||
namespace Managing.Application.Backtesting
|
namespace Managing.Application.Backtesting
|
||||||
{
|
{
|
||||||
@@ -67,7 +67,7 @@ namespace Managing.Application.Backtesting
|
|||||||
List<Candle> initialCandles = null)
|
List<Candle> initialCandles = null)
|
||||||
{
|
{
|
||||||
var scalpingBot = _botFactory.CreateBacktestScalpingBot(account.Name, moneyManagement, ticker, "scenario",
|
var scalpingBot = _botFactory.CreateBacktestScalpingBot(account.Name, moneyManagement, ticker, "scenario",
|
||||||
timeframe, isForWatchingOnly);
|
timeframe, isForWatchingOnly, balance);
|
||||||
scalpingBot.LoadScenario(scenario.Name);
|
scalpingBot.LoadScenario(scenario.Name);
|
||||||
await scalpingBot.LoadAccount();
|
await scalpingBot.LoadAccount();
|
||||||
var candles = initialCandles ?? GetCandles(account, ticker, timeframe, startDate, endDate);
|
var candles = initialCandles ?? GetCandles(account, ticker, timeframe, startDate, endDate);
|
||||||
@@ -121,7 +121,7 @@ namespace Managing.Application.Backtesting
|
|||||||
List<Candle> initialCandles = null)
|
List<Candle> initialCandles = null)
|
||||||
{
|
{
|
||||||
var flippingBot = _botFactory.CreateBacktestFlippingBot(account.Name, moneyManagement, ticker, "scenario",
|
var flippingBot = _botFactory.CreateBacktestFlippingBot(account.Name, moneyManagement, ticker, "scenario",
|
||||||
timeframe, false);
|
timeframe, false, balance);
|
||||||
flippingBot.LoadScenario(scenario.Name);
|
flippingBot.LoadScenario(scenario.Name);
|
||||||
await flippingBot.LoadAccount();
|
await flippingBot.LoadAccount();
|
||||||
|
|
||||||
@@ -152,7 +152,7 @@ namespace Managing.Application.Backtesting
|
|||||||
{
|
{
|
||||||
var ticker = MiscExtensions.ParseEnum<Ticker>(candles.FirstOrDefault().Ticker);
|
var ticker = MiscExtensions.ParseEnum<Ticker>(candles.FirstOrDefault().Ticker);
|
||||||
var bot = _botFactory.CreateBacktestScalpingBot(account.Name, moneyManagement, ticker, "scenario",
|
var bot = _botFactory.CreateBacktestScalpingBot(account.Name, moneyManagement, ticker, "scenario",
|
||||||
timeframe, false);
|
timeframe, false, balance);
|
||||||
bot.LoadScenario(scenario.Name);
|
bot.LoadScenario(scenario.Name);
|
||||||
await bot.LoadAccount();
|
await bot.LoadAccount();
|
||||||
|
|
||||||
@@ -173,7 +173,7 @@ namespace Managing.Application.Backtesting
|
|||||||
{
|
{
|
||||||
var ticker = MiscExtensions.ParseEnum<Ticker>(candles.FirstOrDefault().Ticker);
|
var ticker = MiscExtensions.ParseEnum<Ticker>(candles.FirstOrDefault().Ticker);
|
||||||
var bot = _botFactory.CreateBacktestFlippingBot(account.Name, moneyManagement, ticker, "scenario",
|
var bot = _botFactory.CreateBacktestFlippingBot(account.Name, moneyManagement, ticker, "scenario",
|
||||||
timeframe, false);
|
timeframe, false, balance);
|
||||||
bot.LoadScenario(scenario.Name);
|
bot.LoadScenario(scenario.Name);
|
||||||
await bot.LoadAccount();
|
await bot.LoadAccount();
|
||||||
|
|
||||||
|
|||||||
@@ -1,10 +1,10 @@
|
|||||||
using Microsoft.Extensions.Logging;
|
using Managing.Application.Abstractions;
|
||||||
using static Managing.Common.Enums;
|
|
||||||
using Managing.Application.Abstractions;
|
|
||||||
using Managing.Application.Abstractions.Services;
|
using Managing.Application.Abstractions.Services;
|
||||||
|
using Managing.Domain.Bots;
|
||||||
using Managing.Domain.MoneyManagements;
|
using Managing.Domain.MoneyManagements;
|
||||||
using Managing.Domain.Workflows;
|
using Managing.Domain.Workflows;
|
||||||
using Managing.Domain.Bots;
|
using Microsoft.Extensions.Logging;
|
||||||
|
using static Managing.Common.Enums;
|
||||||
|
|
||||||
namespace Managing.Application.Bots.Base
|
namespace Managing.Application.Bots.Base
|
||||||
{
|
{
|
||||||
@@ -38,7 +38,7 @@ namespace Managing.Application.Bots.Base
|
|||||||
return new SimpleBot(botName, _tradingBotLogger, workflow, _botService);
|
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(
|
return new ScalpingBot(
|
||||||
accountName,
|
accountName,
|
||||||
@@ -53,10 +53,11 @@ namespace Managing.Application.Bots.Base
|
|||||||
_accountService,
|
_accountService,
|
||||||
_messengerService,
|
_messengerService,
|
||||||
_botService,
|
_botService,
|
||||||
|
initialTradingBalance,
|
||||||
isForWatchingOnly: isForWatchingOnly);
|
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(
|
return new ScalpingBot(
|
||||||
accountName,
|
accountName,
|
||||||
@@ -71,11 +72,12 @@ namespace Managing.Application.Bots.Base
|
|||||||
_accountService,
|
_accountService,
|
||||||
_messengerService,
|
_messengerService,
|
||||||
_botService,
|
_botService,
|
||||||
true,
|
initialTradingBalance,
|
||||||
isForWatchingOnly);
|
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(
|
return new FlippingBot(
|
||||||
accountName,
|
accountName,
|
||||||
@@ -90,10 +92,11 @@ namespace Managing.Application.Bots.Base
|
|||||||
_accountService,
|
_accountService,
|
||||||
_messengerService,
|
_messengerService,
|
||||||
_botService,
|
_botService,
|
||||||
|
initialTradingBalance,
|
||||||
isForWatchingOnly: isForWatchingOnly);
|
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(
|
return new FlippingBot(
|
||||||
accountName,
|
accountName,
|
||||||
@@ -108,8 +111,9 @@ namespace Managing.Application.Bots.Base
|
|||||||
_accountService,
|
_accountService,
|
||||||
_messengerService,
|
_messengerService,
|
||||||
_botService,
|
_botService,
|
||||||
true,
|
initialTradingBalance,
|
||||||
isForWatchingOnly);
|
isForBacktest: true,
|
||||||
|
isForWatchingOnly: isForWatchingOnly);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -20,6 +20,7 @@ namespace Managing.Application.Bots
|
|||||||
IAccountService accountService,
|
IAccountService accountService,
|
||||||
IMessengerService messengerService,
|
IMessengerService messengerService,
|
||||||
IBotService botService,
|
IBotService botService,
|
||||||
|
decimal initialTradingBalance,
|
||||||
bool isForBacktest = false,
|
bool isForBacktest = false,
|
||||||
bool isForWatchingOnly = false)
|
bool isForWatchingOnly = false)
|
||||||
: base(accountName,
|
: base(accountName,
|
||||||
@@ -34,6 +35,7 @@ namespace Managing.Application.Bots
|
|||||||
accountService,
|
accountService,
|
||||||
messengerService,
|
messengerService,
|
||||||
botService,
|
botService,
|
||||||
|
initialTradingBalance,
|
||||||
isForBacktest,
|
isForBacktest,
|
||||||
isForWatchingOnly,
|
isForWatchingOnly,
|
||||||
flipPosition: true)
|
flipPosition: true)
|
||||||
|
|||||||
@@ -20,6 +20,7 @@ namespace Managing.Application.Bots
|
|||||||
IAccountService accountService,
|
IAccountService accountService,
|
||||||
IMessengerService messengerService,
|
IMessengerService messengerService,
|
||||||
IBotService botService,
|
IBotService botService,
|
||||||
|
decimal initialTradingBalance,
|
||||||
bool isForBacktest = false,
|
bool isForBacktest = false,
|
||||||
bool isForWatchingOnly = false)
|
bool isForWatchingOnly = false)
|
||||||
: base(accountName,
|
: base(accountName,
|
||||||
@@ -34,6 +35,7 @@ namespace Managing.Application.Bots
|
|||||||
accountService,
|
accountService,
|
||||||
messengerService,
|
messengerService,
|
||||||
botService,
|
botService,
|
||||||
|
initialTradingBalance,
|
||||||
isForBacktest,
|
isForBacktest,
|
||||||
isForWatchingOnly)
|
isForWatchingOnly)
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -2,6 +2,7 @@
|
|||||||
using Managing.Application.Abstractions.Services;
|
using Managing.Application.Abstractions.Services;
|
||||||
using Managing.Application.Trading;
|
using Managing.Application.Trading;
|
||||||
using Managing.Application.Trading.Commands;
|
using Managing.Application.Trading.Commands;
|
||||||
|
using Managing.Common;
|
||||||
using Managing.Core.FixedSizedQueue;
|
using Managing.Core.FixedSizedQueue;
|
||||||
using Managing.Domain.Accounts;
|
using Managing.Domain.Accounts;
|
||||||
using Managing.Domain.Bots;
|
using Managing.Domain.Bots;
|
||||||
@@ -50,6 +51,11 @@ public class TradingBot : Bot, ITradingBot
|
|||||||
public Dictionary<StrategyType, StrategiesResultBase> StrategiesValues { get; set; }
|
public Dictionary<StrategyType, StrategiesResultBase> StrategiesValues { get; set; }
|
||||||
public DateTime StartupTime { 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(
|
public TradingBot(
|
||||||
string accountName,
|
string accountName,
|
||||||
MoneyManagement moneyManagement,
|
MoneyManagement moneyManagement,
|
||||||
@@ -63,6 +69,7 @@ public class TradingBot : Bot, ITradingBot
|
|||||||
IAccountService accountService,
|
IAccountService accountService,
|
||||||
IMessengerService messengerService,
|
IMessengerService messengerService,
|
||||||
IBotService botService,
|
IBotService botService,
|
||||||
|
decimal initialTradingBalance,
|
||||||
bool isForBacktest = false,
|
bool isForBacktest = false,
|
||||||
bool isForWatchingOnly = false,
|
bool isForWatchingOnly = false,
|
||||||
bool flipPosition = false)
|
bool flipPosition = false)
|
||||||
@@ -74,6 +81,13 @@ public class TradingBot : Bot, ITradingBot
|
|||||||
TradingService = tradingService;
|
TradingService = tradingService;
|
||||||
BotService = botService;
|
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;
|
IsForWatchingOnly = isForWatchingOnly;
|
||||||
FlipPosition = flipPosition;
|
FlipPosition = flipPosition;
|
||||||
AccountName = accountName;
|
AccountName = accountName;
|
||||||
@@ -83,6 +97,7 @@ public class TradingBot : Bot, ITradingBot
|
|||||||
Timeframe = timeframe;
|
Timeframe = timeframe;
|
||||||
IsForBacktest = isForBacktest;
|
IsForBacktest = isForBacktest;
|
||||||
Logger = logger;
|
Logger = logger;
|
||||||
|
BotTradingBalance = initialTradingBalance;
|
||||||
|
|
||||||
Strategies = new HashSet<IStrategy>();
|
Strategies = new HashSet<IStrategy>();
|
||||||
Signals = new HashSet<Signal>();
|
Signals = new HashSet<Signal>();
|
||||||
@@ -172,6 +187,17 @@ public class TradingBot : Bot, ITradingBot
|
|||||||
{
|
{
|
||||||
if (!IsForBacktest)
|
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($"____________________{Name}____________________");
|
||||||
Logger.LogInformation(
|
Logger.LogInformation(
|
||||||
$"Time : {DateTime.Now} - Server time {DateTime.Now.ToUniversalTime()} - Last candle : {OptimizedCandles.Last().Date} - Bot : {Name} - Type {BotType} - Ticker : {Ticker}");
|
$"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)
|
if (WalletBalances.Count == 0)
|
||||||
{
|
{
|
||||||
WalletBalances[date] = await ExchangeService.GetBalance(Account, IsForBacktest);
|
// WalletBalances[date] = await ExchangeService.GetBalance(Account, IsForBacktest);
|
||||||
|
WalletBalances[date] = BotTradingBalance;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -555,10 +582,9 @@ public class TradingBot : Bot, ITradingBot
|
|||||||
PositionInitiator.Bot,
|
PositionInitiator.Bot,
|
||||||
signal.Date,
|
signal.Date,
|
||||||
User,
|
User,
|
||||||
|
BotTradingBalance,
|
||||||
IsForBacktest,
|
IsForBacktest,
|
||||||
lastPrice,
|
lastPrice,
|
||||||
balance: WalletBalances.LastOrDefault().Value,
|
|
||||||
fee: Fee,
|
|
||||||
signalIdentifier: signal.Identifier);
|
signalIdentifier: signal.Identifier);
|
||||||
|
|
||||||
var position = await new OpenPositionCommandHandler(ExchangeService, AccountService, TradingService)
|
var position = await new OpenPositionCommandHandler(ExchangeService, AccountService, TradingService)
|
||||||
@@ -591,7 +617,6 @@ public class TradingBot : Bot, ITradingBot
|
|||||||
// Keep signal open for debug purpose
|
// Keep signal open for debug purpose
|
||||||
//SetSignalStatus(signal.Identifier, SignalStatus.Expired);
|
//SetSignalStatus(signal.Identifier, SignalStatus.Expired);
|
||||||
SetSignalStatus(signal.Identifier, SignalStatus.Expired);
|
SetSignalStatus(signal.Identifier, SignalStatus.Expired);
|
||||||
|
|
||||||
await LogWarning($"Cannot open trade : {ex.Message}, stackTrace : {ex.StackTrace}");
|
await LogWarning($"Cannot open trade : {ex.Message}, stackTrace : {ex.StackTrace}");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -599,8 +624,9 @@ public class TradingBot : Bot, ITradingBot
|
|||||||
|
|
||||||
private bool CanOpenPosition(Signal signal)
|
private bool CanOpenPosition(Signal signal)
|
||||||
{
|
{
|
||||||
if (ExecutionCount < 1)
|
if (!IsForBacktest && ExecutionCount < 1)
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
if (Positions.Count == 0)
|
if (Positions.Count == 0)
|
||||||
return true;
|
return true;
|
||||||
|
|
||||||
@@ -639,7 +665,6 @@ public class TradingBot : Bot, ITradingBot
|
|||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
position.Initiator = PositionInitiator.Bot;
|
|
||||||
var command = new ClosePositionCommand(position, lastPrice);
|
var command = new ClosePositionCommand(position, lastPrice);
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
@@ -681,6 +706,18 @@ public class TradingBot : Bot, ITradingBot
|
|||||||
await SetPositionStatus(position.SignalIdentifier, PositionStatus.Finished);
|
await SetPositionStatus(position.SignalIdentifier, PositionStatus.Finished);
|
||||||
Logger.LogInformation(
|
Logger.LogInformation(
|
||||||
$"Position {position.SignalIdentifier} type correctly close. Pnl on position : {position.ProfitAndLoss?.Realized}");
|
$"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
|
else
|
||||||
{
|
{
|
||||||
@@ -734,9 +771,13 @@ public class TradingBot : Bot, ITradingBot
|
|||||||
}
|
}
|
||||||
|
|
||||||
private async Task SetPositionStatus(string signalIdentifier, PositionStatus positionStatus)
|
private async Task SetPositionStatus(string signalIdentifier, PositionStatus positionStatus)
|
||||||
|
{
|
||||||
|
if (!Positions.First(p => p.SignalIdentifier == signalIdentifier).Status.Equals(positionStatus))
|
||||||
{
|
{
|
||||||
await LogInformation($"Position {signalIdentifier} is now {positionStatus}");
|
await LogInformation($"Position {signalIdentifier} is now {positionStatus}");
|
||||||
Positions.First(p => p.SignalIdentifier == signalIdentifier).Status = positionStatus;
|
Positions.First(p => p.SignalIdentifier == signalIdentifier).Status = positionStatus;
|
||||||
|
}
|
||||||
|
|
||||||
SetSignalStatus(signalIdentifier,
|
SetSignalStatus(signalIdentifier,
|
||||||
positionStatus == PositionStatus.Filled ? SignalStatus.PositionOpen : SignalStatus.Expired);
|
positionStatus == PositionStatus.Filled ? SignalStatus.PositionOpen : SignalStatus.Expired);
|
||||||
}
|
}
|
||||||
@@ -799,6 +840,29 @@ public class TradingBot : Bot, ITradingBot
|
|||||||
return fees;
|
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()
|
public async Task ToggleIsForWatchOnly()
|
||||||
{
|
{
|
||||||
IsForWatchingOnly = (!IsForWatchingOnly);
|
IsForWatchingOnly = (!IsForWatchingOnly);
|
||||||
@@ -813,6 +877,7 @@ public class TradingBot : Bot, ITradingBot
|
|||||||
|
|
||||||
private async Task LogWarning(string message)
|
private async Task LogWarning(string message)
|
||||||
{
|
{
|
||||||
|
message = $"[{Name}][{Identifier}] {message}";
|
||||||
Logger.LogWarning(message);
|
Logger.LogWarning(message);
|
||||||
SentrySdk.CaptureException(new Exception(message));
|
SentrySdk.CaptureException(new Exception(message));
|
||||||
await SendTradeMessage(message, true);
|
await SendTradeMessage(message, true);
|
||||||
@@ -841,6 +906,7 @@ public class TradingBot : Bot, ITradingBot
|
|||||||
IsForWatchingOnly = IsForWatchingOnly,
|
IsForWatchingOnly = IsForWatchingOnly,
|
||||||
WalletBalances = WalletBalances,
|
WalletBalances = WalletBalances,
|
||||||
MoneyManagement = MoneyManagement,
|
MoneyManagement = MoneyManagement,
|
||||||
|
BotTradingBalance = BotTradingBalance,
|
||||||
StartupTime = StartupTime,
|
StartupTime = StartupTime,
|
||||||
};
|
};
|
||||||
BotService.SaveOrUpdateBotBackup(Name, BotType, JsonConvert.SerializeObject(data));
|
BotService.SaveOrUpdateBotBackup(Name, BotType, JsonConvert.SerializeObject(data));
|
||||||
@@ -858,6 +924,7 @@ public class TradingBot : Bot, ITradingBot
|
|||||||
ScenarioName = data.ScenarioName;
|
ScenarioName = data.ScenarioName;
|
||||||
AccountName = data.AccountName;
|
AccountName = data.AccountName;
|
||||||
IsForWatchingOnly = data.IsForWatchingOnly;
|
IsForWatchingOnly = data.IsForWatchingOnly;
|
||||||
|
BotTradingBalance = data.BotTradingBalance;
|
||||||
|
|
||||||
// Restore the startup time if it was previously saved
|
// Restore the startup time if it was previously saved
|
||||||
if (data.StartupTime != DateTime.MinValue)
|
if (data.StartupTime != DateTime.MinValue)
|
||||||
@@ -902,10 +969,6 @@ public class TradingBot : Bot, ITradingBot
|
|||||||
throw new Exception("Failed to open position");
|
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}");
|
Logger.LogInformation($"Manually opened position {position.Identifier} for signal {signal.Identifier}");
|
||||||
return position;
|
return position;
|
||||||
}
|
}
|
||||||
@@ -925,4 +988,5 @@ public class TradingBotBackup
|
|||||||
public Dictionary<DateTime, decimal> WalletBalances { get; set; }
|
public Dictionary<DateTime, decimal> WalletBalances { get; set; }
|
||||||
public MoneyManagement MoneyManagement { get; set; }
|
public MoneyManagement MoneyManagement { get; set; }
|
||||||
public DateTime StartupTime { get; set; }
|
public DateTime StartupTime { get; set; }
|
||||||
|
public decimal BotTradingBalance { get; set; }
|
||||||
}
|
}
|
||||||
@@ -136,7 +136,8 @@ namespace Managing.Application.ManageBot
|
|||||||
scalpingBotData.Ticker,
|
scalpingBotData.Ticker,
|
||||||
scalpingBotData.ScenarioName,
|
scalpingBotData.ScenarioName,
|
||||||
scalpingBotData.Timeframe,
|
scalpingBotData.Timeframe,
|
||||||
scalpingBotData.IsForWatchingOnly);
|
scalpingBotData.IsForWatchingOnly,
|
||||||
|
scalpingBotData.BotTradingBalance);
|
||||||
botTask = Task.Run(() => InitBot((ITradingBot)bot, backupBot));
|
botTask = Task.Run(() => InitBot((ITradingBot)bot, backupBot));
|
||||||
break;
|
break;
|
||||||
case Enums.BotType.FlippingBot:
|
case Enums.BotType.FlippingBot:
|
||||||
@@ -150,7 +151,8 @@ namespace Managing.Application.ManageBot
|
|||||||
flippingBotData.Ticker,
|
flippingBotData.Ticker,
|
||||||
flippingBotData.ScenarioName,
|
flippingBotData.ScenarioName,
|
||||||
flippingBotData.Timeframe,
|
flippingBotData.Timeframe,
|
||||||
flippingBotData.IsForWatchingOnly);
|
flippingBotData.IsForWatchingOnly,
|
||||||
|
flippingBotData.BotTradingBalance);
|
||||||
botTask = Task.Run(InitBot((ITradingBot)bot, backupBot));
|
botTask = Task.Run(InitBot((ITradingBot)bot, backupBot));
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
@@ -243,7 +245,8 @@ namespace Managing.Application.ManageBot
|
|||||||
}
|
}
|
||||||
|
|
||||||
public ITradingBot CreateScalpingBot(string accountName, MoneyManagement moneyManagement, string name,
|
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(
|
return new ScalpingBot(
|
||||||
accountName,
|
accountName,
|
||||||
@@ -258,11 +261,13 @@ namespace Managing.Application.ManageBot
|
|||||||
_accountService,
|
_accountService,
|
||||||
_messengerService,
|
_messengerService,
|
||||||
this,
|
this,
|
||||||
|
initialTradingBalance,
|
||||||
isForWatchingOnly: isForWatchingOnly);
|
isForWatchingOnly: isForWatchingOnly);
|
||||||
}
|
}
|
||||||
|
|
||||||
public ITradingBot CreateBacktestScalpingBot(string accountName, MoneyManagement moneyManagement,
|
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(
|
return new ScalpingBot(
|
||||||
accountName,
|
accountName,
|
||||||
@@ -277,12 +282,14 @@ namespace Managing.Application.ManageBot
|
|||||||
_accountService,
|
_accountService,
|
||||||
_messengerService,
|
_messengerService,
|
||||||
this,
|
this,
|
||||||
true,
|
initialTradingBalance,
|
||||||
isForWatchingOnly);
|
isForBacktest: true,
|
||||||
|
isForWatchingOnly: isForWatchingOnly);
|
||||||
}
|
}
|
||||||
|
|
||||||
public ITradingBot CreateFlippingBot(string accountName, MoneyManagement moneyManagement, string name,
|
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(
|
return new FlippingBot(
|
||||||
accountName,
|
accountName,
|
||||||
@@ -297,11 +304,13 @@ namespace Managing.Application.ManageBot
|
|||||||
_accountService,
|
_accountService,
|
||||||
_messengerService,
|
_messengerService,
|
||||||
this,
|
this,
|
||||||
|
initialTradingBalance,
|
||||||
isForWatchingOnly: isForWatchingOnly);
|
isForWatchingOnly: isForWatchingOnly);
|
||||||
}
|
}
|
||||||
|
|
||||||
public ITradingBot CreateBacktestFlippingBot(string accountName, MoneyManagement moneyManagement,
|
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(
|
return new FlippingBot(
|
||||||
accountName,
|
accountName,
|
||||||
@@ -316,8 +325,9 @@ namespace Managing.Application.ManageBot
|
|||||||
_accountService,
|
_accountService,
|
||||||
_messengerService,
|
_messengerService,
|
||||||
this,
|
this,
|
||||||
true,
|
initialTradingBalance,
|
||||||
isForWatchingOnly);
|
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;
|
using static Managing.Common.Enums;
|
||||||
|
|
||||||
namespace Managing.Application.ManageBot.Commands
|
namespace Managing.Application.ManageBot.Commands
|
||||||
@@ -15,6 +15,7 @@ namespace Managing.Application.ManageBot.Commands
|
|||||||
public string AccountName { get; internal set; }
|
public string AccountName { get; internal set; }
|
||||||
public string MoneyManagementName { get; internal set; }
|
public string MoneyManagementName { get; internal set; }
|
||||||
public User User { get; internal set; }
|
public User User { get; internal set; }
|
||||||
|
public decimal InitialTradingBalance { get; internal set; }
|
||||||
|
|
||||||
public StartBotCommand(BotType botType,
|
public StartBotCommand(BotType botType,
|
||||||
string name,
|
string name,
|
||||||
@@ -24,7 +25,8 @@ namespace Managing.Application.ManageBot.Commands
|
|||||||
string accountName,
|
string accountName,
|
||||||
string moneyManagementName,
|
string moneyManagementName,
|
||||||
User user,
|
User user,
|
||||||
bool isForWatchingOnly = false)
|
bool isForWatchingOnly = false,
|
||||||
|
decimal initialTradingBalance = 0)
|
||||||
{
|
{
|
||||||
BotType = botType;
|
BotType = botType;
|
||||||
Name = name;
|
Name = name;
|
||||||
@@ -35,6 +37,8 @@ namespace Managing.Application.ManageBot.Commands
|
|||||||
AccountName = accountName;
|
AccountName = accountName;
|
||||||
MoneyManagementName = moneyManagementName;
|
MoneyManagementName = moneyManagementName;
|
||||||
User = user;
|
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 Managing.Application.Abstractions;
|
||||||
using static Managing.Common.Enums;
|
using Managing.Application.Abstractions.Services;
|
||||||
using Managing.Application.Abstractions;
|
|
||||||
using Managing.Application.ManageBot.Commands;
|
using Managing.Application.ManageBot.Commands;
|
||||||
|
using MediatR;
|
||||||
|
using static Managing.Common.Enums;
|
||||||
|
|
||||||
namespace Managing.Application.ManageBot
|
namespace Managing.Application.ManageBot
|
||||||
{
|
{
|
||||||
@@ -10,38 +11,61 @@ namespace Managing.Application.ManageBot
|
|||||||
private readonly IBotFactory _botFactory;
|
private readonly IBotFactory _botFactory;
|
||||||
private readonly IBotService _botService;
|
private readonly IBotService _botService;
|
||||||
private readonly IMoneyManagementService _moneyManagementService;
|
private readonly IMoneyManagementService _moneyManagementService;
|
||||||
|
private readonly IExchangeService _exchangeService;
|
||||||
|
private readonly IAccountService _accountService;
|
||||||
|
|
||||||
public StartBotCommandHandler(IBotFactory botFactory, IBotService botService,
|
public StartBotCommandHandler(IBotFactory botFactory, IBotService botService,
|
||||||
IMoneyManagementService moneyManagementService)
|
IMoneyManagementService moneyManagementService, IExchangeService exchangeService,
|
||||||
|
IAccountService accountService)
|
||||||
{
|
{
|
||||||
_botFactory = botFactory;
|
_botFactory = botFactory;
|
||||||
_botService = botService;
|
_botService = botService;
|
||||||
_moneyManagementService = moneyManagementService;
|
_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;
|
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)
|
switch (request.BotType)
|
||||||
{
|
{
|
||||||
case BotType.SimpleBot:
|
case BotType.SimpleBot:
|
||||||
var bot = _botFactory.CreateSimpleBot(request.Name, null);
|
var bot = _botFactory.CreateSimpleBot(request.Name, null);
|
||||||
_botService.AddSimpleBotToCache(bot);
|
_botService.AddSimpleBotToCache(bot);
|
||||||
return Task.FromResult(bot.GetStatus());
|
return bot.GetStatus();
|
||||||
case BotType.ScalpingBot:
|
case BotType.ScalpingBot:
|
||||||
var sBot = _botFactory.CreateScalpingBot(request.AccountName, moneyManagement, request.Name,
|
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);
|
_botService.AddTradingBotToCache(sBot);
|
||||||
return Task.FromResult(sBot.GetStatus());
|
return sBot.GetStatus();
|
||||||
case BotType.FlippingBot:
|
case BotType.FlippingBot:
|
||||||
var fBot = _botFactory.CreateFlippingBot(request.AccountName, moneyManagement, request.Name,
|
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);
|
_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 Managing.Application.Abstractions;
|
|
||||||
using Microsoft.Extensions.Logging;
|
|
||||||
using Managing.Application.Abstractions.Repositories;
|
using Managing.Application.Abstractions.Repositories;
|
||||||
|
using Managing.Domain.MoneyManagements;
|
||||||
using Managing.Domain.Users;
|
using Managing.Domain.Users;
|
||||||
|
using Microsoft.Extensions.Logging;
|
||||||
|
|
||||||
namespace Managing.Application.MoneyManagements;
|
namespace Managing.Application.MoneyManagements;
|
||||||
|
|
||||||
@@ -47,12 +47,12 @@ public class MoneyManagementService : IMoneyManagementService
|
|||||||
// Additional check to ensure user's ownership
|
// Additional check to ensure user's ownership
|
||||||
if (moneyManagement.User?.Name != user.Name)
|
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.StopLoss = request.StopLoss;
|
||||||
moneyManagement.TakeProfit = request.TakeProfit;
|
moneyManagement.TakeProfit = request.TakeProfit;
|
||||||
moneyManagement.BalanceAtRisk = request.BalanceAtRisk;
|
|
||||||
moneyManagement.Leverage = request.Leverage;
|
moneyManagement.Leverage = request.Leverage;
|
||||||
moneyManagement.Timeframe = request.Timeframe;
|
moneyManagement.Timeframe = request.Timeframe;
|
||||||
moneyManagement.User = user;
|
moneyManagement.User = user;
|
||||||
@@ -122,7 +122,8 @@ public class MoneyManagementService : IMoneyManagementService
|
|||||||
|
|
||||||
if (moneyManagement != null && moneyManagement.User?.Name != user.Name)
|
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);
|
_settingsRepository.DeleteMoneyManagement(name);
|
||||||
@@ -150,7 +151,8 @@ public class MoneyManagementService : IMoneyManagementService
|
|||||||
{
|
{
|
||||||
// This fallback is not ideal as it would delete all money managements regardless of user
|
// 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
|
// 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;
|
return true;
|
||||||
|
|||||||
@@ -200,7 +200,6 @@ public class SettingsService : ISettingsService
|
|||||||
{
|
{
|
||||||
Name = "Personal-Hourly",
|
Name = "Personal-Hourly",
|
||||||
Timeframe = Timeframe.OneHour,
|
Timeframe = Timeframe.OneHour,
|
||||||
BalanceAtRisk = 25, // 25%
|
|
||||||
StopLoss = 2, // 2%
|
StopLoss = 2, // 2%
|
||||||
TakeProfit = 4, // 4%
|
TakeProfit = 4, // 4%
|
||||||
Leverage = 1,
|
Leverage = 1,
|
||||||
|
|||||||
@@ -16,25 +16,28 @@ namespace Managing.Application.Trading.Commands
|
|||||||
PositionInitiator initiator,
|
PositionInitiator initiator,
|
||||||
DateTime date,
|
DateTime date,
|
||||||
User user,
|
User user,
|
||||||
|
decimal amountToTrade,
|
||||||
bool isForPaperTrading = false,
|
bool isForPaperTrading = false,
|
||||||
decimal? price = null,
|
decimal? price = null,
|
||||||
decimal? balance = 1000,
|
|
||||||
decimal? fee = null,
|
|
||||||
bool? ignoreSLTP = false,
|
|
||||||
string signalIdentifier = null)
|
string signalIdentifier = null)
|
||||||
{
|
{
|
||||||
AccountName = accountName;
|
AccountName = accountName;
|
||||||
MoneyManagement = moneyManagement;
|
MoneyManagement = moneyManagement;
|
||||||
Direction = direction;
|
Direction = direction;
|
||||||
Ticker = ticker;
|
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;
|
IsForPaperTrading = isForPaperTrading;
|
||||||
Price = price;
|
Price = price;
|
||||||
Date = date;
|
|
||||||
Balance = balance;
|
|
||||||
Initiator = initiator;
|
|
||||||
Fee = fee;
|
|
||||||
IgnoreSLTP = ignoreSLTP;
|
|
||||||
User = user;
|
|
||||||
SignalIdentifier = signalIdentifier;
|
SignalIdentifier = signalIdentifier;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -45,11 +48,9 @@ namespace Managing.Application.Trading.Commands
|
|||||||
public Ticker Ticker { get; }
|
public Ticker Ticker { get; }
|
||||||
public bool IsForPaperTrading { get; }
|
public bool IsForPaperTrading { get; }
|
||||||
public decimal? Price { get; }
|
public decimal? Price { get; }
|
||||||
public decimal? Fee { get; }
|
public decimal AmountToTrade { get; }
|
||||||
public bool? IgnoreSLTP { get; }
|
public DateTime Date { get; }
|
||||||
public decimal? Balance { get; }
|
public PositionInitiator Initiator { get; }
|
||||||
public DateTime Date { get; set; }
|
public User User { get; }
|
||||||
public PositionInitiator Initiator { get; internal set; }
|
|
||||||
public User User { get; internal set; }
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1,6 +1,7 @@
|
|||||||
using Managing.Application.Abstractions;
|
using Managing.Application.Abstractions;
|
||||||
using Managing.Application.Abstractions.Services;
|
using Managing.Application.Abstractions.Services;
|
||||||
using Managing.Application.Trading.Commands;
|
using Managing.Application.Trading.Commands;
|
||||||
|
using Managing.Common;
|
||||||
using Managing.Domain.Shared.Helpers;
|
using Managing.Domain.Shared.Helpers;
|
||||||
using Managing.Domain.Trades;
|
using Managing.Domain.Trades;
|
||||||
using static Managing.Common.Enums;
|
using static Managing.Common.Enums;
|
||||||
@@ -36,22 +37,20 @@ namespace Managing.Application.Trading
|
|||||||
position.SignalIdentifier = request.SignalIdentifier;
|
position.SignalIdentifier = request.SignalIdentifier;
|
||||||
}
|
}
|
||||||
|
|
||||||
var balance = request.IsForPaperTrading
|
// Always use BotTradingBalance directly as the balance to risk
|
||||||
? request.Balance.GetValueOrDefault()
|
decimal balanceToRisk = request.AmountToTrade;
|
||||||
: exchangeService.GetBalance(account, request.IsForPaperTrading).Result;
|
|
||||||
var balanceAtRisk = RiskHelpers.GetBalanceAtRisk(balance, request.MoneyManagement);
|
|
||||||
|
|
||||||
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
|
var price = request.IsForPaperTrading && request.Price.HasValue
|
||||||
? request.Price.Value
|
? request.Price.Value
|
||||||
: exchangeService.GetPrice(account, request.Ticker, DateTime.Now);
|
: exchangeService.GetPrice(account, request.Ticker, DateTime.Now);
|
||||||
var quantity = balanceAtRisk / price;
|
var quantity = balanceToRisk / price;
|
||||||
// var expectedStatus = GetExpectedStatus(request);
|
|
||||||
// position.Open = TradingPolicies.OpenPosition(expectedStatus).Execute(async () => { });
|
|
||||||
|
|
||||||
var openPrice = request.IsForPaperTrading || request.Price.HasValue
|
var openPrice = request.IsForPaperTrading || request.Price.HasValue
|
||||||
? request.Price.Value
|
? request.Price.Value
|
||||||
@@ -71,20 +70,19 @@ namespace Managing.Application.Trading
|
|||||||
TradeType.Limit,
|
TradeType.Limit,
|
||||||
isForPaperTrading: request.IsForPaperTrading,
|
isForPaperTrading: request.IsForPaperTrading,
|
||||||
currentDate: request.Date,
|
currentDate: request.Date,
|
||||||
stopLossPrice: stopLossPrice, // Pass determined SL price
|
stopLossPrice: stopLossPrice,
|
||||||
takeProfitPrice: takeProfitPrice); // Pass determined TP price
|
takeProfitPrice: takeProfitPrice);
|
||||||
|
|
||||||
//trade.Fee = TradingHelpers.GetFeeAmount(fee, openPrice * quantity, account.Exchange);
|
|
||||||
position.Open = trade;
|
position.Open = trade;
|
||||||
|
|
||||||
var closeDirection = request.Direction == TradeDirection.Long
|
var closeDirection = request.Direction == TradeDirection.Long
|
||||||
? TradeDirection.Short
|
? TradeDirection.Short
|
||||||
: TradeDirection.Long;
|
: TradeDirection.Long;
|
||||||
|
|
||||||
// Stop loss - Use the determined price
|
// Stop loss
|
||||||
position.StopLoss = exchangeService.BuildEmptyTrade(
|
position.StopLoss = exchangeService.BuildEmptyTrade(
|
||||||
request.Ticker,
|
request.Ticker,
|
||||||
stopLossPrice, // Use determined SL price
|
stopLossPrice,
|
||||||
position.Open.Quantity,
|
position.Open.Quantity,
|
||||||
closeDirection,
|
closeDirection,
|
||||||
request.MoneyManagement.Leverage,
|
request.MoneyManagement.Leverage,
|
||||||
@@ -92,13 +90,10 @@ namespace Managing.Application.Trading
|
|||||||
request.Date,
|
request.Date,
|
||||||
TradeStatus.Requested);
|
TradeStatus.Requested);
|
||||||
|
|
||||||
// position.StopLoss.Fee = TradingHelpers.GetFeeAmount(fee,
|
// Take profit
|
||||||
// position.StopLoss.Price * position.StopLoss.Quantity, account.Exchange);
|
|
||||||
|
|
||||||
// Take profit - Use the determined price
|
|
||||||
position.TakeProfit1 = exchangeService.BuildEmptyTrade(
|
position.TakeProfit1 = exchangeService.BuildEmptyTrade(
|
||||||
request.Ticker,
|
request.Ticker,
|
||||||
takeProfitPrice, // Use determined TP price
|
takeProfitPrice,
|
||||||
quantity,
|
quantity,
|
||||||
closeDirection,
|
closeDirection,
|
||||||
request.MoneyManagement.Leverage,
|
request.MoneyManagement.Leverage,
|
||||||
|
|||||||
@@ -147,10 +147,9 @@ public class OpenPosition : FlowBase
|
|||||||
PositionInitiator.Bot,
|
PositionInitiator.Bot,
|
||||||
signal.Date,
|
signal.Date,
|
||||||
account.User,
|
account.User,
|
||||||
|
100m, // Default bot trading balance
|
||||||
OpenPositionParameters.IsForBacktest,
|
OpenPositionParameters.IsForBacktest,
|
||||||
lastPrice,
|
lastPrice);
|
||||||
balance: WalletBalances.LastOrDefault().Value,
|
|
||||||
fee: Fee);
|
|
||||||
|
|
||||||
var position = await new OpenPositionCommandHandler(_exchangeService, _accountService, _tradingService)
|
var position = await new OpenPositionCommandHandler(_exchangeService, _accountService, _tradingService)
|
||||||
.Handle(command);
|
.Handle(command);
|
||||||
|
|||||||
@@ -84,6 +84,8 @@
|
|||||||
{
|
{
|
||||||
public const int USD = 30;
|
public const int USD = 30;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public const decimal MinimumPositionAmount = 10m;
|
||||||
}
|
}
|
||||||
|
|
||||||
public class TokenAddress
|
public class TokenAddress
|
||||||
|
|||||||
@@ -11,8 +11,6 @@ namespace Managing.Domain.MoneyManagements
|
|||||||
[Required]
|
[Required]
|
||||||
public Timeframe Timeframe { get; set; }
|
public Timeframe Timeframe { get; set; }
|
||||||
[Required]
|
[Required]
|
||||||
public decimal BalanceAtRisk { get; set; }
|
|
||||||
[Required]
|
|
||||||
public decimal StopLoss { get; set; }
|
public decimal StopLoss { get; set; }
|
||||||
[Required]
|
[Required]
|
||||||
public decimal TakeProfit { get; set; }
|
public decimal TakeProfit { get; set; }
|
||||||
@@ -25,7 +23,6 @@ namespace Managing.Domain.MoneyManagements
|
|||||||
{
|
{
|
||||||
StopLoss /= 100;
|
StopLoss /= 100;
|
||||||
TakeProfit /= 100;
|
TakeProfit /= 100;
|
||||||
BalanceAtRisk /= 100;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -12,18 +12,6 @@ namespace Managing.Domain.Shared.Helpers
|
|||||||
price += price * moneyManagement.StopLoss;
|
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)
|
public static decimal GetTakeProfitPrice(TradeDirection direction, decimal price, MoneyManagement moneyManagement, int count = 1)
|
||||||
{
|
{
|
||||||
decimal percentage = moneyManagement.TakeProfit * count;
|
decimal percentage = moneyManagement.TakeProfit * count;
|
||||||
|
|||||||
@@ -134,7 +134,6 @@ public static class TradingBox
|
|||||||
|
|
||||||
moneyManagement.StopLoss = stoplossPercentage.Average();
|
moneyManagement.StopLoss = stoplossPercentage.Average();
|
||||||
moneyManagement.TakeProfit = takeProfitsPercentage.Average();
|
moneyManagement.TakeProfit = takeProfitsPercentage.Average();
|
||||||
moneyManagement.BalanceAtRisk = originMoneyManagement.BalanceAtRisk * 100;
|
|
||||||
moneyManagement.Timeframe = originMoneyManagement.Timeframe;
|
moneyManagement.Timeframe = originMoneyManagement.Timeframe;
|
||||||
moneyManagement.Leverage = originMoneyManagement.Leverage;
|
moneyManagement.Leverage = originMoneyManagement.Leverage;
|
||||||
moneyManagement.Name = "Optimized";
|
moneyManagement.Name = "Optimized";
|
||||||
@@ -310,7 +309,8 @@ public static class TradingBox
|
|||||||
(position.Open.Date >= cutoff ||
|
(position.Open.Date >= cutoff ||
|
||||||
(position.StopLoss.Status == TradeStatus.Filled && position.StopLoss.Date >= cutoff) ||
|
(position.StopLoss.Status == TradeStatus.Filled && position.StopLoss.Date >= cutoff) ||
|
||||||
(position.TakeProfit1.Status == TradeStatus.Filled && position.TakeProfit1.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;
|
profitLast24h += position.ProfitAndLoss != null ? position.ProfitAndLoss.Realized : 0;
|
||||||
investmentLast24h += position.Open.Quantity * position.Open.Price;
|
investmentLast24h += position.Open.Quantity * position.Open.Price;
|
||||||
@@ -368,7 +368,8 @@ public static class TradingBox
|
|||||||
(p.Date >= cutoffDate ||
|
(p.Date >= cutoffDate ||
|
||||||
(p.StopLoss.Status == TradeStatus.Filled && p.StopLoss.Date >= cutoffDate) ||
|
(p.StopLoss.Status == TradeStatus.Filled && p.StopLoss.Date >= cutoffDate) ||
|
||||||
(p.TakeProfit1.Status == TradeStatus.Filled && p.TakeProfit1.Date >= cutoffDate) ||
|
(p.TakeProfit1.Status == TradeStatus.Filled && p.TakeProfit1.Date >= cutoffDate) ||
|
||||||
(p.TakeProfit2 != null && p.TakeProfit2.Status == TradeStatus.Filled && p.TakeProfit2.Date >= cutoffDate)))
|
(p.TakeProfit2 != null && p.TakeProfit2.Status == TradeStatus.Filled &&
|
||||||
|
p.TakeProfit2.Date >= cutoffDate)))
|
||||||
.Sum(p => p.ProfitAndLoss.Realized);
|
.Sum(p => p.ProfitAndLoss.Realized);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -418,7 +419,8 @@ public static class TradingBox
|
|||||||
(p.Date >= cutoffDate ||
|
(p.Date >= cutoffDate ||
|
||||||
(p.StopLoss.Status == TradeStatus.Filled && p.StopLoss.Date >= cutoffDate) ||
|
(p.StopLoss.Status == TradeStatus.Filled && p.StopLoss.Date >= cutoffDate) ||
|
||||||
(p.TakeProfit1.Status == TradeStatus.Filled && p.TakeProfit1.Date >= cutoffDate) ||
|
(p.TakeProfit1.Status == TradeStatus.Filled && p.TakeProfit1.Date >= cutoffDate) ||
|
||||||
(p.TakeProfit2 != null && p.TakeProfit2.Status == TradeStatus.Filled && p.TakeProfit2.Date >= cutoffDate)));
|
(p.TakeProfit2 != null && p.TakeProfit2.Status == TradeStatus.Filled &&
|
||||||
|
p.TakeProfit2.Date >= cutoffDate)));
|
||||||
|
|
||||||
// Calculate investment and profit
|
// Calculate investment and profit
|
||||||
decimal totalInvestment = filteredPositions.Sum(p => p.Open.Quantity * p.Open.Price);
|
decimal totalInvestment = filteredPositions.Sum(p => p.Open.Quantity * p.Open.Price);
|
||||||
@@ -473,7 +475,8 @@ public static class TradingBox
|
|||||||
(p.Date >= cutoffDate ||
|
(p.Date >= cutoffDate ||
|
||||||
(p.StopLoss.Status == TradeStatus.Filled && p.StopLoss.Date >= cutoffDate) ||
|
(p.StopLoss.Status == TradeStatus.Filled && p.StopLoss.Date >= cutoffDate) ||
|
||||||
(p.TakeProfit1.Status == TradeStatus.Filled && p.TakeProfit1.Date >= cutoffDate) ||
|
(p.TakeProfit1.Status == TradeStatus.Filled && p.TakeProfit1.Date >= cutoffDate) ||
|
||||||
(p.TakeProfit2 != null && p.TakeProfit2.Status == TradeStatus.Filled && p.TakeProfit2.Date >= cutoffDate)));
|
(p.TakeProfit2 != null && p.TakeProfit2.Status == TradeStatus.Filled &&
|
||||||
|
p.TakeProfit2.Date >= cutoffDate)));
|
||||||
|
|
||||||
int wins = 0;
|
int wins = 0;
|
||||||
int losses = 0;
|
int losses = 0;
|
||||||
|
|||||||
@@ -1,5 +1,4 @@
|
|||||||
using Managing.Core;
|
using Managing.Domain.Accounts;
|
||||||
using Managing.Domain.Accounts;
|
|
||||||
using Managing.Domain.Backtests;
|
using Managing.Domain.Backtests;
|
||||||
using Managing.Domain.Bots;
|
using Managing.Domain.Bots;
|
||||||
using Managing.Domain.Candles;
|
using Managing.Domain.Candles;
|
||||||
@@ -12,9 +11,8 @@ using Managing.Domain.Users;
|
|||||||
using Managing.Domain.Workers;
|
using Managing.Domain.Workers;
|
||||||
using Managing.Domain.Workflows.Synthetics;
|
using Managing.Domain.Workflows.Synthetics;
|
||||||
using Managing.Infrastructure.Databases.MongoDb.Collections;
|
using Managing.Infrastructure.Databases.MongoDb.Collections;
|
||||||
using static Managing.Common.Enums;
|
|
||||||
using MongoDB.Bson;
|
using MongoDB.Bson;
|
||||||
using Managing.Domain.Shared.Helpers;
|
using static Managing.Common.Enums;
|
||||||
|
|
||||||
namespace Managing.Infrastructure.Databases.MongoDb;
|
namespace Managing.Infrastructure.Databases.MongoDb;
|
||||||
|
|
||||||
@@ -486,7 +484,6 @@ public static class MongoMappers
|
|||||||
return new MoneyManagementDto
|
return new MoneyManagementDto
|
||||||
{
|
{
|
||||||
Timeframe = request.Timeframe,
|
Timeframe = request.Timeframe,
|
||||||
BalanceAtRisk = request.BalanceAtRisk,
|
|
||||||
StopLoss = request.StopLoss,
|
StopLoss = request.StopLoss,
|
||||||
TakeProfit = request.TakeProfit,
|
TakeProfit = request.TakeProfit,
|
||||||
Leverage = request.Leverage,
|
Leverage = request.Leverage,
|
||||||
@@ -503,7 +500,6 @@ public static class MongoMappers
|
|||||||
return new MoneyManagement
|
return new MoneyManagement
|
||||||
{
|
{
|
||||||
Timeframe = request.Timeframe,
|
Timeframe = request.Timeframe,
|
||||||
BalanceAtRisk = request.BalanceAtRisk,
|
|
||||||
StopLoss = request.StopLoss,
|
StopLoss = request.StopLoss,
|
||||||
TakeProfit = request.TakeProfit,
|
TakeProfit = request.TakeProfit,
|
||||||
Leverage = request.Leverage,
|
Leverage = request.Leverage,
|
||||||
|
|||||||
@@ -69,12 +69,11 @@ namespace Managing.Infrastructure.Messengers.Discord
|
|||||||
|
|
||||||
var moneymanagement = new MoneyManagement
|
var moneymanagement = new MoneyManagement
|
||||||
{
|
{
|
||||||
BalanceAtRisk = 5,
|
|
||||||
StopLoss = stopLoss.GetValueOrDefault(),
|
StopLoss = stopLoss.GetValueOrDefault(),
|
||||||
TakeProfit = takeProfit.GetValueOrDefault(),
|
TakeProfit = takeProfit.GetValueOrDefault(),
|
||||||
};
|
};
|
||||||
var tradeCommand = new OpenPositionRequest(accountName, moneymanagement, direction, ticker,
|
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 result = await _openTradeCommandHandler.Handle(tradeCommand);
|
||||||
var builder = new ComponentBuilder().WithButton("Close Position",
|
var builder = new ComponentBuilder().WithButton("Close Position",
|
||||||
$"{Constants.DiscordButtonAction.ClosePosition}|{result.Open.ExchangeOrderId}");
|
$"{Constants.DiscordButtonAction.ClosePosition}|{result.Open.ExchangeOrderId}");
|
||||||
|
|||||||
@@ -12,6 +12,7 @@ using Managing.Core;
|
|||||||
using Managing.Domain.MoneyManagements;
|
using Managing.Domain.MoneyManagements;
|
||||||
using Managing.Domain.Statistics;
|
using Managing.Domain.Statistics;
|
||||||
using Managing.Domain.Trades;
|
using Managing.Domain.Trades;
|
||||||
|
using Managing.Domain.Users;
|
||||||
using Microsoft.Extensions.Hosting;
|
using Microsoft.Extensions.Hosting;
|
||||||
using Microsoft.Extensions.Logging;
|
using Microsoft.Extensions.Logging;
|
||||||
using Newtonsoft.Json;
|
using Newtonsoft.Json;
|
||||||
@@ -264,7 +265,7 @@ namespace Managing.Infrastructure.Messengers.Discord
|
|||||||
var tradingService = (ITradingService)_services.GetService(typeof(ITradingService));
|
var tradingService = (ITradingService)_services.GetService(typeof(ITradingService));
|
||||||
|
|
||||||
// Create default user for Discord bot operations
|
// 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(
|
var tradeCommand = new OpenPositionRequest(
|
||||||
accountName,
|
accountName,
|
||||||
@@ -274,7 +275,7 @@ namespace Managing.Infrastructure.Messengers.Discord
|
|||||||
initiator,
|
initiator,
|
||||||
DateTime.UtcNow,
|
DateTime.UtcNow,
|
||||||
defaultUser,
|
defaultUser,
|
||||||
ignoreSLTP: ignoreSLTP);
|
100m);
|
||||||
var position = await new OpenPositionCommandHandler(exchangeService, accountService, tradingService)
|
var position = await new OpenPositionCommandHandler(exchangeService, accountService, tradingService)
|
||||||
.Handle(tradeCommand);
|
.Handle(tradeCommand);
|
||||||
|
|
||||||
@@ -416,7 +417,6 @@ namespace Managing.Infrastructure.Messengers.Discord
|
|||||||
new MoneyManagement
|
new MoneyManagement
|
||||||
{
|
{
|
||||||
Name = "MediumRisk",
|
Name = "MediumRisk",
|
||||||
BalanceAtRisk = 0.05m,
|
|
||||||
Leverage = 1,
|
Leverage = 1,
|
||||||
StopLoss = 0.021m,
|
StopLoss = 0.021m,
|
||||||
TakeProfit = 0.042m,
|
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 {Backtest, MoneyManagement} from '../../../generated/ManagingApi'
|
||||||
import type { IModalProps } from '../../../global/type'
|
import type {IModalProps} from '../../../global/type'
|
||||||
|
|
||||||
import Modal from './Modal'
|
import Modal from './Modal'
|
||||||
|
|
||||||
interface IBotNameModalProps extends IModalProps {
|
interface IBotNameModalProps extends IModalProps {
|
||||||
backtest: Backtest
|
backtest: Backtest
|
||||||
isForWatchOnly: boolean
|
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[]
|
moneyManagements: MoneyManagement[]
|
||||||
selectedMoneyManagement: string
|
selectedMoneyManagement: string
|
||||||
setSelectedMoneyManagement: (name: string) => void
|
setSelectedMoneyManagement: (name: string) => void
|
||||||
@@ -25,6 +25,7 @@ const BotNameModal: React.FC<IBotNameModalProps> = ({
|
|||||||
setSelectedMoneyManagement,
|
setSelectedMoneyManagement,
|
||||||
}) => {
|
}) => {
|
||||||
const [botName, setBotName] = useState<string>('')
|
const [botName, setBotName] = useState<string>('')
|
||||||
|
const [initialTradingBalance, setInitialTradingBalance] = useState<number>(1000)
|
||||||
|
|
||||||
// Initialize botName when backtest changes
|
// Initialize botName when backtest changes
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
@@ -35,8 +36,8 @@ const BotNameModal: React.FC<IBotNameModalProps> = ({
|
|||||||
|
|
||||||
const handleSubmit = (e: React.FormEvent) => {
|
const handleSubmit = (e: React.FormEvent) => {
|
||||||
e.preventDefault()
|
e.preventDefault()
|
||||||
if (botName.trim()) {
|
if (botName.trim() && initialTradingBalance > 0) {
|
||||||
onSubmitBotName(botName, backtest, isForWatchOnly, selectedMoneyManagement)
|
onSubmitBotName(botName, backtest, isForWatchOnly, selectedMoneyManagement, initialTradingBalance)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -85,6 +86,22 @@ const BotNameModal: React.FC<IBotNameModalProps> = ({
|
|||||||
</select>
|
</select>
|
||||||
</div>
|
</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">
|
<p className="text-xs text-gray-500 mt-2 mb-4">
|
||||||
{isForWatchOnly
|
{isForWatchOnly
|
||||||
? 'The bot will run in watch-only mode and will not execute trades.'
|
? '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 moment from 'moment'
|
||||||
import React from 'react'
|
import React from 'react'
|
||||||
|
|
||||||
import useApiUrlStore from '../../../app/store/apiStore'
|
import useApiUrlStore from '../../../app/store/apiStore'
|
||||||
import type {
|
import type {Backtest, MoneyManagement, StartBotRequest, Ticker,} from '../../../generated/ManagingApi'
|
||||||
Backtest,
|
import {BacktestClient, BotClient, BotType,} from '../../../generated/ManagingApi'
|
||||||
MoneyManagement,
|
import type {IBacktestCards} from '../../../global/type'
|
||||||
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 MoneyManagementModal from '../../../pages/settingsPage/moneymanagement/moneyManagementModal'
|
||||||
import { CardPosition, CardText, Toast } from '../../mollecules'
|
import {CardPosition, CardText, Toast} from '../../mollecules'
|
||||||
import CardPositionItem from '../Trading/CardPositionItem'
|
import CardPositionItem from '../Trading/CardPositionItem'
|
||||||
import TradeChart from '../Trading/TradeChart/TradeChart'
|
import TradeChart from '../Trading/TradeChart/TradeChart'
|
||||||
|
|
||||||
@@ -73,6 +64,7 @@ const BacktestCards: React.FC<IBacktestCards> = ({ list, setBacktests }) => {
|
|||||||
scenario: backtest.scenario,
|
scenario: backtest.scenario,
|
||||||
ticker: backtest.ticker as Ticker,
|
ticker: backtest.ticker as Ticker,
|
||||||
timeframe: backtest.timeframe,
|
timeframe: backtest.timeframe,
|
||||||
|
initialTradingBalance: 1000,
|
||||||
}
|
}
|
||||||
|
|
||||||
await client
|
await client
|
||||||
@@ -89,6 +81,10 @@ const BacktestCards: React.FC<IBacktestCards> = ({ list, setBacktests }) => {
|
|||||||
const t = new Toast('Optimized backtest is running')
|
const t = new Toast('Optimized backtest is running')
|
||||||
const client = new BacktestClient({}, apiUrl)
|
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
|
await client
|
||||||
.backtest_Run(
|
.backtest_Run(
|
||||||
backtest.accountName,
|
backtest.accountName,
|
||||||
@@ -96,12 +92,13 @@ const BacktestCards: React.FC<IBacktestCards> = ({ list, setBacktests }) => {
|
|||||||
backtest.ticker as Ticker,
|
backtest.ticker as Ticker,
|
||||||
backtest.scenario,
|
backtest.scenario,
|
||||||
backtest.timeframe,
|
backtest.timeframe,
|
||||||
false,
|
false, // watchOnly
|
||||||
daysBetween(backtest.candles[0].date),
|
backtest.walletBalances[0].value, // balance
|
||||||
backtest.walletBalances[0].value,
|
'', // moneyManagementName (empty since we're passing the optimized moneyManagement object)
|
||||||
'',
|
startDate, // startDate
|
||||||
false,
|
endDate, // endDate
|
||||||
backtest.optimizedMoneyManagement
|
false, // save
|
||||||
|
backtest.optimizedMoneyManagement // moneyManagement object
|
||||||
)
|
)
|
||||||
.then((backtest: Backtest) => {
|
.then((backtest: Backtest) => {
|
||||||
t.update('success', `${backtest.ticker} Backtest Succeeded`)
|
t.update('success', `${backtest.ticker} Backtest Succeeded`)
|
||||||
|
|||||||
@@ -1,28 +1,23 @@
|
|||||||
import { useQuery } from '@tanstack/react-query'
|
import {useQuery} from '@tanstack/react-query'
|
||||||
import React, { useEffect, useState } from 'react'
|
import React, {useEffect, useState} from 'react'
|
||||||
import { useForm, type SubmitHandler } from 'react-hook-form'
|
import {type SubmitHandler, useForm} from 'react-hook-form'
|
||||||
|
|
||||||
import useApiUrlStore from '../../../app/store/apiStore'
|
import useApiUrlStore from '../../../app/store/apiStore'
|
||||||
import {
|
|
||||||
Backtest,
|
|
||||||
MoneyManagement,
|
|
||||||
Ticker,
|
|
||||||
} from '../../../generated/ManagingApi'
|
|
||||||
import {
|
import {
|
||||||
AccountClient,
|
AccountClient,
|
||||||
|
Backtest,
|
||||||
BacktestClient,
|
BacktestClient,
|
||||||
BotType,
|
BotType,
|
||||||
DataClient,
|
DataClient,
|
||||||
|
MoneyManagement,
|
||||||
MoneyManagementClient,
|
MoneyManagementClient,
|
||||||
ScenarioClient,
|
ScenarioClient,
|
||||||
|
Ticker,
|
||||||
Timeframe,
|
Timeframe,
|
||||||
} from '../../../generated/ManagingApi'
|
} from '../../../generated/ManagingApi'
|
||||||
import type {
|
import type {BacktestModalProps, IBacktestsFormInput,} from '../../../global/type'
|
||||||
BacktestModalProps,
|
import {Loader, Slider} from '../../atoms'
|
||||||
IBacktestsFormInput,
|
import {Modal, Toast} from '../../mollecules'
|
||||||
} from '../../../global/type'
|
|
||||||
import { Loader, Slider } from '../../atoms'
|
|
||||||
import { Modal, Toast } from '../../mollecules'
|
|
||||||
import FormInput from '../../mollecules/FormInput/FormInput'
|
import FormInput from '../../mollecules/FormInput/FormInput'
|
||||||
import CustomMoneyManagement from '../CustomMoneyManagement/CustomMoneyManagement'
|
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
|
// Use the name of the money management strategy if custom is not provided
|
||||||
const moneyManagementName = customMoneyManagement ? undefined : selectedMoneyManagement
|
const moneyManagementName = customMoneyManagement ? undefined : selectedMoneyManagement
|
||||||
|
|
||||||
|
console.log(customMoneyManagement)
|
||||||
|
|
||||||
backtestClient
|
backtestClient
|
||||||
.backtest_Run(
|
.backtest_Run(
|
||||||
form.accountName,
|
form.accountName,
|
||||||
@@ -122,7 +119,6 @@ const BacktestModal: React.FC<BacktestModalProps> = ({
|
|||||||
if (showLoopSlider && selectedLoopQuantity > loopCount) {
|
if (showLoopSlider && selectedLoopQuantity > loopCount) {
|
||||||
const nextCount = loopCount + 1
|
const nextCount = loopCount + 1
|
||||||
const mm: MoneyManagement = {
|
const mm: MoneyManagement = {
|
||||||
balanceAtRisk: backtest.optimizedMoneyManagement.balanceAtRisk,
|
|
||||||
leverage: backtest.optimizedMoneyManagement.leverage,
|
leverage: backtest.optimizedMoneyManagement.leverage,
|
||||||
name: backtest.optimizedMoneyManagement.name + nextCount,
|
name: backtest.optimizedMoneyManagement.name + nextCount,
|
||||||
stopLoss: backtest.optimizedMoneyManagement.stopLoss,
|
stopLoss: backtest.optimizedMoneyManagement.stopLoss,
|
||||||
@@ -148,6 +144,7 @@ const BacktestModal: React.FC<BacktestModalProps> = ({
|
|||||||
function onMoneyManagementChange(e: any) {
|
function onMoneyManagementChange(e: any) {
|
||||||
if (e.target.value === 'custom') {
|
if (e.target.value === 'custom') {
|
||||||
setShowCustomMoneyManagement(true)
|
setShowCustomMoneyManagement(true)
|
||||||
|
setCustomMoneyManagement(e.target.value)
|
||||||
} else {
|
} else {
|
||||||
setShowCustomMoneyManagement(false)
|
setShowCustomMoneyManagement(false)
|
||||||
setCustomMoneyManagement(undefined)
|
setCustomMoneyManagement(undefined)
|
||||||
@@ -280,6 +277,32 @@ const BacktestModal: React.FC<BacktestModalProps> = ({
|
|||||||
</select>
|
</select>
|
||||||
</FormInput>
|
</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 && (
|
{showCustomMoneyManagement && (
|
||||||
<div className="mt-6">
|
<div className="mt-6">
|
||||||
<CustomMoneyManagement
|
<CustomMoneyManagement
|
||||||
|
|||||||
@@ -1,23 +1,12 @@
|
|||||||
import {
|
import {ChevronDownIcon, ChevronRightIcon, EyeIcon, PlayIcon, TrashIcon} from '@heroicons/react/solid'
|
||||||
ChevronDownIcon,
|
import React, {useEffect, useState} from 'react'
|
||||||
ChevronRightIcon,
|
import {useQuery} from '@tanstack/react-query'
|
||||||
PlayIcon,
|
|
||||||
TrashIcon,
|
|
||||||
EyeIcon
|
|
||||||
} from '@heroicons/react/solid'
|
|
||||||
import React, { useEffect, useState } from 'react'
|
|
||||||
import { useQuery } from '@tanstack/react-query'
|
|
||||||
|
|
||||||
import useApiUrlStore from '../../../app/store/apiStore'
|
import useApiUrlStore from '../../../app/store/apiStore'
|
||||||
import type {
|
import type {Backtest, StartBotRequest, Ticker,} from '../../../generated/ManagingApi'
|
||||||
Backtest,
|
import {BacktestClient, BotClient, MoneyManagementClient} from '../../../generated/ManagingApi'
|
||||||
MoneyManagement,
|
import type {IBacktestCards} from '../../../global/type'
|
||||||
StartBotRequest,
|
import {CardText, SelectColumnFilter, Table, Toast} from '../../mollecules'
|
||||||
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 BotNameModal from '../../mollecules/Modal/BotNameModal'
|
import BotNameModal from '../../mollecules/Modal/BotNameModal'
|
||||||
|
|
||||||
import BacktestRowDetails from './backtestRowDetails'
|
import BacktestRowDetails from './backtestRowDetails'
|
||||||
@@ -50,7 +39,7 @@ const BacktestTable: React.FC<IBacktestCards> = ({ list, isFetching, setBacktest
|
|||||||
}
|
}
|
||||||
}, [moneyManagements])
|
}, [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 t = new Toast('Bot is starting')
|
||||||
const client = new BotClient({}, apiUrl)
|
const client = new BotClient({}, apiUrl)
|
||||||
|
|
||||||
@@ -70,6 +59,7 @@ const BacktestTable: React.FC<IBacktestCards> = ({ list, isFetching, setBacktest
|
|||||||
scenario: backtest.scenario,
|
scenario: backtest.scenario,
|
||||||
ticker: backtest.ticker as Ticker,
|
ticker: backtest.ticker as Ticker,
|
||||||
timeframe: backtest.timeframe,
|
timeframe: backtest.timeframe,
|
||||||
|
initialTradingBalance: initialTradingBalance,
|
||||||
}
|
}
|
||||||
|
|
||||||
await client
|
await client
|
||||||
@@ -92,8 +82,8 @@ const BacktestTable: React.FC<IBacktestCards> = ({ list, isFetching, setBacktest
|
|||||||
setShowBotNameModal(false)
|
setShowBotNameModal(false)
|
||||||
}
|
}
|
||||||
|
|
||||||
const handleSubmitBotName = (botName: string, backtest: Backtest, isForWatchOnly: boolean, moneyManagementName: string) => {
|
const handleSubmitBotName = (botName: string, backtest: Backtest, isForWatchOnly: boolean, moneyManagementName: string, initialTradingBalance: number) => {
|
||||||
runBot(botName, backtest, isForWatchOnly, moneyManagementName)
|
runBot(botName, backtest, isForWatchOnly, moneyManagementName, initialTradingBalance)
|
||||||
setShowBotNameModal(false)
|
setShowBotNameModal(false)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -364,8 +354,8 @@ const BacktestTable: React.FC<IBacktestCards> = ({ list, isFetching, setBacktest
|
|||||||
onClose={handleCloseBotNameModal}
|
onClose={handleCloseBotNameModal}
|
||||||
backtest={currentBacktest}
|
backtest={currentBacktest}
|
||||||
isForWatchOnly={isForWatchOnly}
|
isForWatchOnly={isForWatchOnly}
|
||||||
onSubmitBotName={(botName, backtest, isForWatchOnly, moneyManagementName) =>
|
onSubmitBotName={(botName, backtest, isForWatchOnly, moneyManagementName, initialTradingBalance) =>
|
||||||
handleSubmitBotName(botName, backtest, isForWatchOnly, moneyManagementName)
|
handleSubmitBotName(botName, backtest, isForWatchOnly, moneyManagementName, initialTradingBalance)
|
||||||
}
|
}
|
||||||
moneyManagements={moneyManagements}
|
moneyManagements={moneyManagements}
|
||||||
selectedMoneyManagement={selectedMoneyManagement}
|
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 type {MoneyManagement, Timeframe} from '../../../generated/ManagingApi'
|
||||||
import { Slider } from '../../atoms'
|
|
||||||
import FormInput from '../../mollecules/FormInput/FormInput'
|
import FormInput from '../../mollecules/FormInput/FormInput'
|
||||||
import { useCustomMoneyManagement } from '../../../app/store/customMoneyManagement'
|
import {useCustomMoneyManagement} from '../../../app/store/customMoneyManagement'
|
||||||
|
|
||||||
type ICustomMoneyManagement = {
|
type ICustomMoneyManagement = {
|
||||||
onCreateMoneyManagement: (moneyManagement: MoneyManagement) => void
|
onCreateMoneyManagement: (moneyManagement: MoneyManagement) => void
|
||||||
@@ -18,14 +17,12 @@ const CustomMoneyManagement: React.FC<ICustomMoneyManagement> = ({
|
|||||||
}) => {
|
}) => {
|
||||||
const { moneyManagement, setCustomMoneyManagement } = useCustomMoneyManagement()
|
const { moneyManagement, setCustomMoneyManagement } = useCustomMoneyManagement()
|
||||||
|
|
||||||
const [balanceAtRisk, setBalanceAtRisk] = useState<number>(moneyManagement?.balanceAtRisk || 5)
|
|
||||||
const [leverage, setLeverage] = useState<number>(moneyManagement?.leverage || 1)
|
const [leverage, setLeverage] = useState<number>(moneyManagement?.leverage || 1)
|
||||||
const [takeProfit, setTakeProfit] = useState<number>(moneyManagement?.takeProfit || 2)
|
const [takeProfit, setTakeProfit] = useState<number>(moneyManagement?.takeProfit || 2)
|
||||||
const [stopLoss, setStopLoss] = useState<number>(moneyManagement?.stopLoss || 1)
|
const [stopLoss, setStopLoss] = useState<number>(moneyManagement?.stopLoss || 1)
|
||||||
|
|
||||||
const handleCreateMoneyManagement = () => {
|
const handleCreateMoneyManagement = () => {
|
||||||
const moneyManagement: MoneyManagement = {
|
const moneyManagement: MoneyManagement = {
|
||||||
balanceAtRisk,
|
|
||||||
leverage,
|
leverage,
|
||||||
name: 'custom',
|
name: 'custom',
|
||||||
stopLoss,
|
stopLoss,
|
||||||
@@ -38,7 +35,7 @@ const CustomMoneyManagement: React.FC<ICustomMoneyManagement> = ({
|
|||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
handleCreateMoneyManagement()
|
handleCreateMoneyManagement()
|
||||||
}, [balanceAtRisk, leverage, takeProfit, stopLoss])
|
}, [leverage, takeProfit, stopLoss])
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
@@ -49,23 +46,6 @@ const CustomMoneyManagement: React.FC<ICustomMoneyManagement> = ({
|
|||||||
Custom MoneyManagement
|
Custom MoneyManagement
|
||||||
</div>
|
</div>
|
||||||
<div className="collapse-content">
|
<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}>
|
<FormInput label="Leverage" htmlFor="leverage" inline={false}>
|
||||||
<input
|
<input
|
||||||
id="leverage"
|
id="leverage"
|
||||||
|
|||||||
@@ -2572,7 +2572,6 @@ export enum TradeDirection {
|
|||||||
export interface MoneyManagement {
|
export interface MoneyManagement {
|
||||||
name: string;
|
name: string;
|
||||||
timeframe: Timeframe;
|
timeframe: Timeframe;
|
||||||
balanceAtRisk: number;
|
|
||||||
stopLoss: number;
|
stopLoss: number;
|
||||||
takeProfit: number;
|
takeProfit: number;
|
||||||
leverage: number;
|
leverage: number;
|
||||||
@@ -2842,6 +2841,7 @@ export interface StartBotRequest {
|
|||||||
scenario: string;
|
scenario: string;
|
||||||
accountName: string;
|
accountName: string;
|
||||||
moneyManagementName: string;
|
moneyManagementName: string;
|
||||||
|
initialTradingBalance: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface TradingBot {
|
export interface TradingBot {
|
||||||
|
|||||||
@@ -1,27 +1,20 @@
|
|||||||
import { useEffect, useState } from 'react'
|
import {useEffect, useState} from 'react'
|
||||||
import type { SubmitHandler } from 'react-hook-form'
|
import type {SubmitHandler} from 'react-hook-form'
|
||||||
import { useForm } from 'react-hook-form'
|
import {useForm} from 'react-hook-form'
|
||||||
|
|
||||||
import useApiUrlStore from '../../../app/store/apiStore'
|
import useApiUrlStore from '../../../app/store/apiStore'
|
||||||
import { Slider } from '../../../components/atoms'
|
import {Slider} from '../../../components/atoms'
|
||||||
import { FormInput, Modal, Toast } from '../../../components/mollecules'
|
import {FormInput, Modal, Toast} from '../../../components/mollecules'
|
||||||
import type { MoneyManagement } from '../../../generated/ManagingApi'
|
import type {MoneyManagement} from '../../../generated/ManagingApi'
|
||||||
import {
|
import {MoneyManagementClient, Timeframe,} from '../../../generated/ManagingApi'
|
||||||
MoneyManagementClient,
|
import type {IMoneyManagementFormInput, IMoneyManagementModalProps,} from '../../../global/type'
|
||||||
Timeframe,
|
|
||||||
} from '../../../generated/ManagingApi'
|
|
||||||
import type {
|
|
||||||
IMoneyManagementModalProps,
|
|
||||||
IMoneyManagementFormInput,
|
|
||||||
} from '../../../global/type'
|
|
||||||
|
|
||||||
const MoneyManagementModal: React.FC<IMoneyManagementModalProps> = ({
|
const MoneyManagementModal: React.FC<IMoneyManagementModalProps> = ({
|
||||||
showModal,
|
showModal,
|
||||||
onClose,
|
onClose,
|
||||||
moneyManagement,
|
moneyManagement,
|
||||||
disableInputs = false,
|
disableInputs = false,
|
||||||
}) => {
|
}) => {
|
||||||
const [balanceAtRisk, setBalanceAtRisk] = useState<number>(5)
|
|
||||||
const [takeProfit, setTakeProfit] = useState<number>(20)
|
const [takeProfit, setTakeProfit] = useState<number>(20)
|
||||||
const [name, setName] = useState<string>('')
|
const [name, setName] = useState<string>('')
|
||||||
const [stopLoss, setStopLoss] = useState<number>(10)
|
const [stopLoss, setStopLoss] = useState<number>(10)
|
||||||
@@ -29,14 +22,13 @@ const MoneyManagementModal: React.FC<IMoneyManagementModalProps> = ({
|
|||||||
const [timeframe, setTimeframe] = useState<Timeframe>(
|
const [timeframe, setTimeframe] = useState<Timeframe>(
|
||||||
Timeframe.FifteenMinutes
|
Timeframe.FifteenMinutes
|
||||||
)
|
)
|
||||||
const { reset, register, handleSubmit } = useForm<IMoneyManagementFormInput>()
|
const {reset, register, handleSubmit} = useForm<IMoneyManagementFormInput>()
|
||||||
const { apiUrl } = useApiUrlStore()
|
const {apiUrl} = useApiUrlStore()
|
||||||
|
|
||||||
async function createMoneyManagement(form: IMoneyManagementFormInput) {
|
async function createMoneyManagement(form: IMoneyManagementFormInput) {
|
||||||
const t = new Toast('Creating settings')
|
const t = new Toast('Creating settings')
|
||||||
const client = new MoneyManagementClient({}, apiUrl)
|
const client = new MoneyManagementClient({}, apiUrl)
|
||||||
const mm: MoneyManagement = {
|
const mm: MoneyManagement = {
|
||||||
balanceAtRisk: balanceAtRisk / 100,
|
|
||||||
leverage: leverage,
|
leverage: leverage,
|
||||||
name: name,
|
name: name,
|
||||||
stopLoss: stopLoss / 100,
|
stopLoss: stopLoss / 100,
|
||||||
@@ -53,6 +45,7 @@ const MoneyManagementModal: React.FC<IMoneyManagementModalProps> = ({
|
|||||||
t.update('error', 'Error :' + err)
|
t.update('error', 'Error :' + err)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
const onSubmit: SubmitHandler<IMoneyManagementFormInput> = async (form) => {
|
const onSubmit: SubmitHandler<IMoneyManagementFormInput> = async (form) => {
|
||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
await createMoneyManagement(form)
|
await createMoneyManagement(form)
|
||||||
@@ -75,14 +68,10 @@ const MoneyManagementModal: React.FC<IMoneyManagementModalProps> = ({
|
|||||||
setLeverage(e.target.value)
|
setLeverage(e.target.value)
|
||||||
}
|
}
|
||||||
|
|
||||||
function onBalanceAtRiskChange(e: any) {
|
|
||||||
setBalanceAtRisk(e.target.value)
|
|
||||||
}
|
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (moneyManagement) {
|
if (moneyManagement) {
|
||||||
console.log(moneyManagement)
|
console.log(moneyManagement)
|
||||||
setBalanceAtRisk(moneyManagement.balanceAtRisk * 100)
|
|
||||||
setTakeProfit(moneyManagement.takeProfit * 100)
|
setTakeProfit(moneyManagement.takeProfit * 100)
|
||||||
setStopLoss(moneyManagement.stopLoss * 100)
|
setStopLoss(moneyManagement.stopLoss * 100)
|
||||||
setLeverage(moneyManagement.leverage)
|
setLeverage(moneyManagement.leverage)
|
||||||
@@ -90,14 +79,13 @@ const MoneyManagementModal: React.FC<IMoneyManagementModalProps> = ({
|
|||||||
setName(moneyManagement.name)
|
setName(moneyManagement.name)
|
||||||
|
|
||||||
const defaultValues: MoneyManagement = {
|
const defaultValues: MoneyManagement = {
|
||||||
balanceAtRisk: moneyManagement.balanceAtRisk,
|
|
||||||
leverage: moneyManagement.leverage,
|
leverage: moneyManagement.leverage,
|
||||||
name: moneyManagement.name || '',
|
name: moneyManagement.name || '',
|
||||||
stopLoss: moneyManagement.stopLoss,
|
stopLoss: moneyManagement.stopLoss,
|
||||||
takeProfit: moneyManagement.takeProfit,
|
takeProfit: moneyManagement.takeProfit,
|
||||||
timeframe: moneyManagement.timeframe,
|
timeframe: moneyManagement.timeframe,
|
||||||
}
|
}
|
||||||
reset({ ...defaultValues })
|
reset({...defaultValues})
|
||||||
}
|
}
|
||||||
}, [showModal, moneyManagement])
|
}, [showModal, moneyManagement])
|
||||||
|
|
||||||
@@ -136,18 +124,6 @@ const MoneyManagementModal: React.FC<IMoneyManagementModalProps> = ({
|
|||||||
</select>
|
</select>
|
||||||
</FormInput>
|
</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="TP%" htmlFor="takeProfit">
|
<FormInput label="TP%" htmlFor="takeProfit">
|
||||||
<Slider
|
<Slider
|
||||||
id="takeProfit"
|
id="takeProfit"
|
||||||
|
|||||||
@@ -1,15 +1,11 @@
|
|||||||
import { PencilAltIcon, TrashIcon } from '@heroicons/react/solid'
|
import {PencilAltIcon, TrashIcon} from '@heroicons/react/solid'
|
||||||
import React, { useEffect, useState } from 'react'
|
import React, {useEffect, useState} from 'react'
|
||||||
|
|
||||||
import useApiUrlStore from '../../../app/store/apiStore'
|
import useApiUrlStore from '../../../app/store/apiStore'
|
||||||
import {
|
import {SelectColumnFilter, Table, Toast,} from '../../../components/mollecules'
|
||||||
Toast,
|
import type {MoneyManagement} from '../../../generated/ManagingApi'
|
||||||
SelectColumnFilter,
|
import {MoneyManagementClient} from '../../../generated/ManagingApi'
|
||||||
Table,
|
import type {IMoneyManagementList} from '../../../global/type'
|
||||||
} from '../../../components/mollecules'
|
|
||||||
import type { MoneyManagement } from '../../../generated/ManagingApi'
|
|
||||||
import { MoneyManagementClient } from '../../../generated/ManagingApi'
|
|
||||||
import type { IMoneyManagementList } from '../../../global/type'
|
|
||||||
|
|
||||||
import MoneyManagementModal from './moneyManagementModal'
|
import MoneyManagementModal from './moneyManagementModal'
|
||||||
|
|
||||||
@@ -63,13 +59,7 @@ const MoneyManagementTable: React.FC<IMoneyManagementList> = ({ list }) => {
|
|||||||
disableSortBy: true,
|
disableSortBy: true,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
Cell: ({ cell }: any) => <>{cell.row.values.balanceAtRisk * 100} %</>,
|
Cell: ({ cell }: any) => <>{cell.row.values.stopLoss * 100} %</>,
|
||||||
Header: 'Balance used',
|
|
||||||
accessor: 'balanceAtRisk',
|
|
||||||
disableFilters: true,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
Cell: ({ cell }) => <>{cell.row.values.stopLoss * 100} %</>,
|
|
||||||
Header: 'SL',
|
Header: 'SL',
|
||||||
accessor: 'stopLoss',
|
accessor: 'stopLoss',
|
||||||
disableFilters: true,
|
disableFilters: true,
|
||||||
|
|||||||
Reference in New Issue
Block a user