Fixes for bots running (#22)

* Fixes for bots running

* Up botmanager

* Add cooldown

* Refact can open position

* Add cooldown Period and MaxLossStreak

* Add agentName

* Add env variable for botManager

* Always enable Botmanager

* Fix bot handle

* Fix get positions

* Add Ticker url

* Dont start stopped bot

* fix
This commit is contained in:
Oda
2025-05-09 17:40:31 +02:00
committed by GitHub
parent a8eb0aaf02
commit 7c38c27b4a
54 changed files with 5164 additions and 641 deletions

View File

@@ -1,16 +1,15 @@
using Managing.Domain.Bots;
using Managing.Domain.MoneyManagements;
using Managing.Application.Bots;
using Managing.Domain.Bots;
using Managing.Domain.Workflows;
using static Managing.Common.Enums;
namespace Managing.Application.Abstractions
{
public interface IBotFactory
{
ITradingBot CreateScalpingBot(string accountName, MoneyManagement moneyManagement, string name, Ticker ticker, string scenario, Timeframe interval, bool isForWatchingOnly, decimal initialTradingBalance);
ITradingBot CreateBacktestScalpingBot(string accountName, MoneyManagement moneyManagement, Ticker ticker, string scenario, Timeframe interval, bool isForWatchingOnly, decimal initialTradingBalance);
ITradingBot CreateFlippingBot(string accountName, MoneyManagement moneyManagement, string name, Ticker ticker, string scenario, Timeframe interval, bool isForWatchingOnly, decimal initialTradingBalance);
ITradingBot CreateBacktestFlippingBot(string accountName, MoneyManagement moneyManagement, Ticker ticker, string scenario, Timeframe interval, bool isForWatchingOnly, decimal initialTradingBalance);
IBot CreateSimpleBot(string botName, Workflow workflow);
ITradingBot CreateScalpingBot(TradingBotConfig config);
ITradingBot CreateBacktestScalpingBot(TradingBotConfig config);
ITradingBot CreateFlippingBot(TradingBotConfig config);
ITradingBot CreateBacktestFlippingBot(TradingBotConfig config);
}
}

View File

@@ -1,38 +1,30 @@
using Managing.Common;
using Managing.Application.Bots;
using Managing.Domain.Bots;
using Managing.Domain.MoneyManagements;
using Managing.Domain.Users;
using Managing.Domain.Workflows;
using static Managing.Common.Enums;
namespace Managing.Application.Abstractions;
public interface IBotService
{
void SaveOrUpdateBotBackup(BotBackup botBackup);
void SaveOrUpdateBotBackup(User user, string identifier, Enums.BotType botType, string data);
void SaveOrUpdateBotBackup(User user, string identifier, BotType botType, BotStatus status, string data);
void AddSimpleBotToCache(IBot bot);
void AddTradingBotToCache(ITradingBot bot);
List<ITradingBot> GetActiveBots();
IEnumerable<BotBackup> GetSavedBots();
void StartBotFromBackup(BotBackup backupBot);
BotBackup GetBotBackup(string name);
BotBackup GetBotBackup(string identifier);
ITradingBot CreateScalpingBot(string accountName, MoneyManagement moneyManagement, string name, Enums.Ticker ticker,
string scenario, Enums.Timeframe interval, bool isForWatchingOnly, decimal initialTradingAmount);
ITradingBot CreateBacktestScalpingBot(string accountName, MoneyManagement moneyManagement, Enums.Ticker ticker,
string scenario, Enums.Timeframe interval, bool isForWatchingOnly, decimal initialTradingAmount);
ITradingBot CreateFlippingBot(string accountName, MoneyManagement moneyManagement, string name, Enums.Ticker ticker,
string scenario, Enums.Timeframe interval, bool isForWatchingOnly, decimal initialTradingAmount);
ITradingBot CreateBacktestFlippingBot(string accountName, MoneyManagement moneyManagement, Enums.Ticker ticker,
string scenario, Enums.Timeframe interval, bool isForWatchingOnly, decimal initialTradingAmount);
ITradingBot CreateScalpingBot(TradingBotConfig config);
ITradingBot CreateBacktestScalpingBot(TradingBotConfig config);
ITradingBot CreateFlippingBot(TradingBotConfig config);
ITradingBot CreateBacktestFlippingBot(TradingBotConfig config);
IBot CreateSimpleBot(string botName, Workflow workflow);
Task<string> StopBot(string requestName);
Task<bool> DeleteBot(string requestName);
Task<string> RestartBot(string requestName);
Task<string> StopBot(string botName);
Task<bool> DeleteBot(string botName);
Task<string> RestartBot(string botName);
void DeleteBotBackup(string backupBotName);
void ToggleIsForWatchingOnly(string botName);
}

View File

@@ -1,33 +1,32 @@
using Managing.Core.FixedSizedQueue;
using Managing.Application.Bots;
using Managing.Core.FixedSizedQueue;
using Managing.Domain.Accounts;
using Managing.Domain.Bots;
using Managing.Domain.Candles;
using Managing.Domain.MoneyManagements;
using Managing.Domain.Scenarios;
using Managing.Domain.Strategies;
using Managing.Domain.Strategies.Base;
using Managing.Domain.Trades;
using Managing.Domain.Users;
using static Managing.Common.Enums;
namespace Managing.Application.Abstractions
{
public interface ITradingBot : IBot
{
HashSet<Signal> Signals { get; set; }
List<Position> Positions { get; set; }
TradingBotConfig Config { get; set; }
Account Account { get; set; }
HashSet<IStrategy> Strategies { get; set; }
FixedSizeQueue<Candle> OptimizedCandles { get; set; }
HashSet<Candle> Candles { get; set; }
Timeframe Timeframe { get; set; }
HashSet<IStrategy> Strategies { get; set; }
Ticker Ticker { get; }
string ScenarioName { get; }
string AccountName { get; }
bool IsForWatchingOnly { get; set; }
MoneyManagement MoneyManagement { get; set; }
BotType BotType { get; set; }
HashSet<Signal> Signals { get; set; }
List<Position> Positions { get; set; }
Dictionary<DateTime, decimal> WalletBalances { get; set; }
Dictionary<StrategyType, StrategiesResultBase> StrategiesValues { get; set; }
User User { get; set; }
string Identifier { get; set; }
DateTime StartupTime { get; set; }
DateTime PreloadSince { get; set; }
int PreloadedCandlesCount { get; set; }
decimal Fee { get; set; }
Scenario Scenario { get; set; }
Task Run();
Task ToggleIsForWatchOnly();
@@ -38,5 +37,6 @@ namespace Managing.Application.Abstractions
void LoadScenario(string scenarioName);
void UpdateStrategiesValues();
Task LoadAccount();
Task<Position> OpenPositionManually(TradeDirection direction);
}
}

View File

@@ -1,6 +1,7 @@
using Managing.Application.Abstractions;
using Managing.Application.Abstractions.Repositories;
using Managing.Application.Abstractions.Services;
using Managing.Application.Bots;
using Managing.Core;
using Managing.Core.FixedSizedQueue;
using Managing.Domain.Accounts;
@@ -64,10 +65,26 @@ namespace Managing.Application.Backtesting
User user = null,
bool isForWatchingOnly = false,
bool save = false,
List<Candle> initialCandles = null)
List<Candle>? initialCandles = null,
decimal cooldownPeriod = 1,
int maxLossStreak = 0)
{
var scalpingBot = _botFactory.CreateBacktestScalpingBot(account.Name, moneyManagement, ticker, "scenario",
timeframe, isForWatchingOnly, balance);
var config = new TradingBotConfig
{
AccountName = account.Name,
MoneyManagement = moneyManagement,
Ticker = ticker,
ScenarioName = scenario.Name,
Timeframe = timeframe,
IsForWatchingOnly = isForWatchingOnly,
BotTradingBalance = balance,
BotType = BotType.ScalpingBot,
IsForBacktest = true,
CooldownPeriod = cooldownPeriod,
MaxLossStreak = maxLossStreak
};
var scalpingBot = _botFactory.CreateBacktestScalpingBot(config);
scalpingBot.LoadScenario(scenario.Name);
await scalpingBot.LoadAccount();
var candles = initialCandles ?? GetCandles(account, ticker, timeframe, startDate, endDate);
@@ -91,21 +108,6 @@ namespace Managing.Application.Backtesting
return result;
}
private List<Candle> GetCandles(Account account, Ticker ticker, Timeframe timeframe,
DateTime startDate, DateTime endDate)
{
List<Candle> candles;
// Use specific date range
candles = _exchangeService.GetCandlesInflux(account.Exchange, ticker,
startDate, timeframe, endDate).Result;
if (candles == null || candles.Count == 0)
throw new Exception($"No candles for {ticker} on {account.Exchange}");
return candles;
}
public async Task<Backtest> RunFlippingBotBacktest(
Account account,
MoneyManagement moneyManagement,
@@ -118,10 +120,26 @@ namespace Managing.Application.Backtesting
User user = null,
bool isForWatchingOnly = false,
bool save = false,
List<Candle> initialCandles = null)
List<Candle>? initialCandles = null,
decimal cooldownPeriod = 1,
int maxLossStreak = 0)
{
var flippingBot = _botFactory.CreateBacktestFlippingBot(account.Name, moneyManagement, ticker, "scenario",
timeframe, false, balance);
var config = new TradingBotConfig
{
AccountName = account.Name,
MoneyManagement = moneyManagement,
Ticker = ticker,
ScenarioName = scenario.Name,
Timeframe = timeframe,
IsForWatchingOnly = isForWatchingOnly,
BotTradingBalance = balance,
BotType = BotType.FlippingBot,
IsForBacktest = true,
CooldownPeriod = cooldownPeriod,
MaxLossStreak = maxLossStreak
};
var flippingBot = _botFactory.CreateBacktestFlippingBot(config);
flippingBot.LoadScenario(scenario.Name);
await flippingBot.LoadAccount();
@@ -146,13 +164,34 @@ namespace Managing.Application.Backtesting
return result;
}
public async Task<Backtest> RunScalpingBotBacktest(Account account, MoneyManagement moneyManagement,
public async Task<Backtest> RunScalpingBotBacktest(
Account account,
MoneyManagement moneyManagement,
Scenario scenario,
Timeframe timeframe, List<Candle> candles, decimal balance, User user = null)
Timeframe timeframe,
List<Candle> candles,
decimal balance,
User user = null,
decimal cooldownPeriod = 1,
int maxLossStreak = 0)
{
var ticker = MiscExtensions.ParseEnum<Ticker>(candles.FirstOrDefault().Ticker);
var bot = _botFactory.CreateBacktestScalpingBot(account.Name, moneyManagement, ticker, "scenario",
timeframe, false, balance);
var config = new TradingBotConfig
{
AccountName = account.Name,
MoneyManagement = moneyManagement,
Ticker = ticker,
ScenarioName = scenario.Name,
Timeframe = timeframe,
IsForWatchingOnly = false,
BotTradingBalance = balance,
BotType = BotType.ScalpingBot,
IsForBacktest = true,
CooldownPeriod = cooldownPeriod,
MaxLossStreak = maxLossStreak
};
var bot = _botFactory.CreateBacktestScalpingBot(config);
bot.LoadScenario(scenario.Name);
await bot.LoadAccount();
@@ -167,13 +206,34 @@ namespace Managing.Application.Backtesting
return result;
}
public async Task<Backtest> RunFlippingBotBacktest(Account account, MoneyManagement moneyManagement,
public async Task<Backtest> RunFlippingBotBacktest(
Account account,
MoneyManagement moneyManagement,
Scenario scenario,
Timeframe timeframe, List<Candle> candles, decimal balance, User user = null)
Timeframe timeframe,
List<Candle> candles,
decimal balance,
User user = null,
decimal cooldownPeriod = 1,
int maxLossStreak = 0)
{
var ticker = MiscExtensions.ParseEnum<Ticker>(candles.FirstOrDefault().Ticker);
var bot = _botFactory.CreateBacktestFlippingBot(account.Name, moneyManagement, ticker, "scenario",
timeframe, false, balance);
var config = new TradingBotConfig
{
AccountName = account.Name,
MoneyManagement = moneyManagement,
Ticker = ticker,
ScenarioName = scenario.Name,
Timeframe = timeframe,
IsForWatchingOnly = false,
BotTradingBalance = balance,
BotType = BotType.FlippingBot,
IsForBacktest = true,
CooldownPeriod = cooldownPeriod,
MaxLossStreak = maxLossStreak
};
var bot = _botFactory.CreateBacktestFlippingBot(config);
bot.LoadScenario(scenario.Name);
await bot.LoadAccount();
@@ -188,6 +248,21 @@ namespace Managing.Application.Backtesting
return result;
}
private List<Candle> GetCandles(Account account, Ticker ticker, Timeframe timeframe,
DateTime startDate, DateTime endDate)
{
List<Candle> candles;
// Use specific date range
candles = _exchangeService.GetCandlesInflux(account.Exchange, ticker,
startDate, timeframe, endDate).Result;
if (candles == null || candles.Count == 0)
throw new Exception($"No candles for {ticker} on {account.Exchange}");
return candles;
}
private Backtest GetBacktestingResult(
Ticker ticker,
Scenario scenario,
@@ -239,7 +314,7 @@ namespace Managing.Application.Backtesting
var score = BacktestScorer.CalculateTotalScore(scoringParams);
var result = new Backtest(ticker, scenario.Name, bot.Positions, bot.Signals.ToList(), timeframe, candles,
bot.BotType, account.Name)
bot.Config.BotType, account.Name)
{
FinalPnl = finalPnl,
WinRate = winRate,

View File

@@ -1,7 +1,6 @@
using Managing.Application.Abstractions;
using Managing.Application.Abstractions.Services;
using Managing.Domain.Bots;
using Managing.Domain.MoneyManagements;
using Managing.Domain.Workflows;
using Microsoft.Extensions.Logging;
using static Managing.Common.Enums;
@@ -38,82 +37,58 @@ namespace Managing.Application.Bots.Base
return new SimpleBot(botName, _tradingBotLogger, workflow, _botService);
}
ITradingBot IBotFactory.CreateScalpingBot(string accountName, MoneyManagement moneyManagement, string name, Ticker ticker, string scenario, Timeframe interval, bool isForWatchingOnly, decimal initialTradingBalance)
ITradingBot IBotFactory.CreateScalpingBot(TradingBotConfig config)
{
config.BotType = BotType.ScalpingBot;
return new ScalpingBot(
accountName,
moneyManagement,
name,
scenario,
_exchangeService,
ticker,
_tradingService,
_tradingBotLogger,
interval,
_tradingService,
_accountService,
_messengerService,
_botService,
initialTradingBalance,
isForWatchingOnly: isForWatchingOnly);
config);
}
ITradingBot IBotFactory.CreateBacktestScalpingBot(string accountName, MoneyManagement moneyManagement, Ticker ticker, string scenario, Timeframe interval, bool isForWatchingOnly, decimal initialTradingBalance)
ITradingBot IBotFactory.CreateBacktestScalpingBot(TradingBotConfig config)
{
config.BotType = BotType.ScalpingBot;
config.IsForBacktest = true;
return new ScalpingBot(
accountName,
moneyManagement,
"BacktestBot",
scenario,
_exchangeService,
ticker,
_tradingService,
_tradingBotLogger,
interval,
_tradingService,
_accountService,
_messengerService,
_botService,
initialTradingBalance,
isForBacktest: true,
isForWatchingOnly: isForWatchingOnly);
config);
}
public ITradingBot CreateFlippingBot(string accountName, MoneyManagement moneyManagement, string name, Ticker ticker, string scenario, Timeframe interval, bool isForWatchingOnly, decimal initialTradingBalance)
public ITradingBot CreateFlippingBot(TradingBotConfig config)
{
config.BotType = BotType.FlippingBot;
return new FlippingBot(
accountName,
moneyManagement,
name,
scenario,
_exchangeService,
ticker,
_tradingService,
_tradingBotLogger,
interval,
_tradingService,
_accountService,
_messengerService,
_botService,
initialTradingBalance,
isForWatchingOnly: isForWatchingOnly);
config);
}
public ITradingBot CreateBacktestFlippingBot(string accountName, MoneyManagement moneyManagement, Ticker ticker, string scenario, Timeframe interval, bool isForWatchingOnly, decimal initialTradingBalance)
public ITradingBot CreateBacktestFlippingBot(TradingBotConfig config)
{
config.BotType = BotType.FlippingBot;
config.IsForBacktest = true;
return new FlippingBot(
accountName,
moneyManagement,
"BacktestBot",
scenario,
_exchangeService,
ticker,
_tradingService,
_tradingBotLogger,
interval,
_tradingService,
_accountService,
_messengerService,
_botService,
initialTradingBalance,
isForBacktest: true,
isForWatchingOnly: isForWatchingOnly);
config);
}
}
}

View File

@@ -1,6 +1,5 @@
using Managing.Application.Abstractions;
using Managing.Application.Abstractions.Services;
using Managing.Domain.MoneyManagements;
using Microsoft.Extensions.Logging;
using static Managing.Common.Enums;
@@ -8,39 +7,18 @@ namespace Managing.Application.Bots
{
public class FlippingBot : TradingBot
{
public FlippingBot(string accountName,
MoneyManagement moneyManagement,
string name,
string scenarioName,
public FlippingBot(
IExchangeService exchangeService,
Ticker ticker,
ITradingService tradingService,
ILogger<TradingBot> logger,
Timeframe timeframe,
ITradingService tradingService,
IAccountService accountService,
IMessengerService messengerService,
IBotService botService,
decimal initialTradingBalance,
bool isForBacktest = false,
bool isForWatchingOnly = false)
: base(accountName,
moneyManagement,
name,
ticker,
scenarioName,
exchangeService,
logger,
tradingService,
timeframe,
accountService,
messengerService,
botService,
initialTradingBalance,
isForBacktest,
isForWatchingOnly,
flipPosition: true)
TradingBotConfig config)
: base(exchangeService, logger, tradingService, accountService, messengerService, botService, config)
{
BotType = BotType.FlippingBot;
Config.BotType = BotType.FlippingBot;
Config.FlipPosition = true;
}
public sealed override void Start()

View File

@@ -1,6 +1,5 @@
using Managing.Application.Abstractions;
using Managing.Application.Abstractions.Services;
using Managing.Domain.MoneyManagements;
using Microsoft.Extensions.Logging;
using static Managing.Common.Enums;
@@ -8,38 +7,17 @@ namespace Managing.Application.Bots
{
public class ScalpingBot : TradingBot
{
public ScalpingBot(string accountName,
MoneyManagement moneyManagement,
string name,
string scenarioName,
public ScalpingBot(
IExchangeService exchangeService,
Ticker ticker,
ITradingService tradingService,
ILogger<TradingBot> logger,
Timeframe timeframe,
ITradingService tradingService,
IAccountService accountService,
IMessengerService messengerService,
IBotService botService,
decimal initialTradingBalance,
bool isForBacktest = false,
bool isForWatchingOnly = false)
: base(accountName,
moneyManagement,
name,
ticker,
scenarioName,
exchangeService,
logger,
tradingService,
timeframe,
accountService,
messengerService,
botService,
initialTradingBalance,
isForBacktest,
isForWatchingOnly)
TradingBotConfig config)
: base(exchangeService, logger, tradingService, accountService, messengerService, botService, config)
{
BotType = BotType.ScalpingBot;
Config.BotType = BotType.ScalpingBot;
}
public sealed override void Start()

View File

@@ -45,7 +45,7 @@ namespace Managing.Application.Bots
public override void SaveBackup()
{
var data = JsonConvert.SerializeObject(_workflow);
_botService.SaveOrUpdateBotBackup(User, Identifier, BotType.SimpleBot, data);
_botService.SaveOrUpdateBotBackup(User, Identifier, BotType.SimpleBot, Status, data);
}
public override void LoadBackup(BotBackup backup)

View File

@@ -28,76 +28,47 @@ public class TradingBot : Bot, ITradingBot
private readonly ITradingService TradingService;
private readonly IBotService BotService;
public TradingBotConfig Config { get; set; }
public Account Account { get; set; }
public HashSet<IStrategy> Strategies { get; set; }
public FixedSizeQueue<Candle> OptimizedCandles { get; set; }
public HashSet<Candle> Candles { get; set; }
public HashSet<Signal> Signals { get; set; }
public List<Position> Positions { get; set; }
public Ticker Ticker { get; set; }
public string ScenarioName { get; set; }
public string AccountName { get; set; }
public MoneyManagement MoneyManagement { get; set; }
public Timeframe Timeframe { get; set; }
public bool IsForBacktest { get; set; }
public DateTime PreloadSince { get; set; }
public bool IsForWatchingOnly { get; set; }
public bool FlipPosition { get; set; }
public int PreloadedCandlesCount { get; set; }
public BotType BotType { get; set; }
public decimal Fee { get; set; }
public Scenario Scenario { get; set; }
public Dictionary<DateTime, decimal> WalletBalances { get; set; }
public Dictionary<StrategyType, StrategiesResultBase> StrategiesValues { get; set; }
public DateTime StartupTime { get; set; }
/// <summary>
/// The dedicated trading balance for this bot in USD
/// </summary>
public decimal BotTradingBalance { get; set; }
public DateTime PreloadSince { get; set; }
public int PreloadedCandlesCount { get; set; }
public decimal Fee { get; set; }
public Scenario Scenario { get; set; }
public TradingBot(
string accountName,
MoneyManagement moneyManagement,
string name,
Ticker ticker,
string scenarioName,
IExchangeService exchangeService,
ILogger<TradingBot> logger,
ITradingService tradingService,
Timeframe timeframe,
IAccountService accountService,
IMessengerService messengerService,
IBotService botService,
decimal initialTradingBalance,
bool isForBacktest = false,
bool isForWatchingOnly = false,
bool flipPosition = false)
: base(name)
TradingBotConfig config
)
: base(config.AccountName)
{
ExchangeService = exchangeService;
AccountService = accountService;
MessengerService = messengerService;
TradingService = tradingService;
BotService = botService;
Logger = logger;
if (initialTradingBalance <= Constants.GMX.Config.MinimumPositionAmount)
if (config.BotTradingBalance <= Constants.GMX.Config.MinimumPositionAmount)
{
throw new ArgumentException(
$"Initial trading balance must be greater than {Constants.GMX.Config.MinimumPositionAmount}",
nameof(initialTradingBalance));
nameof(config.BotTradingBalance));
}
IsForWatchingOnly = isForWatchingOnly;
FlipPosition = flipPosition;
AccountName = accountName;
MoneyManagement = moneyManagement;
Ticker = ticker;
ScenarioName = scenarioName;
Timeframe = timeframe;
IsForBacktest = isForBacktest;
Logger = logger;
BotTradingBalance = initialTradingBalance;
Config = config;
Strategies = new HashSet<IStrategy>();
Signals = new HashSet<Signal>();
@@ -107,10 +78,10 @@ public class TradingBot : Bot, ITradingBot
WalletBalances = new Dictionary<DateTime, decimal>();
StrategiesValues = new Dictionary<StrategyType, StrategiesResultBase>();
if (!isForBacktest)
if (!Config.IsForBacktest)
{
Interval = CandleExtensions.GetIntervalFromTimeframe(timeframe);
PreloadSince = CandleExtensions.GetBotPreloadSinceFromTimeframe(timeframe);
Interval = CandleExtensions.GetIntervalFromTimeframe(Config.Timeframe);
PreloadSince = CandleExtensions.GetBotPreloadSinceFromTimeframe(Config.Timeframe);
}
}
@@ -120,9 +91,9 @@ public class TradingBot : Bot, ITradingBot
// Load account synchronously
await LoadAccount();
if (!IsForBacktest)
if (!Config.IsForBacktest)
{
LoadScenario(ScenarioName);
LoadScenario(Config.ScenarioName);
await PreloadCandles();
await CancelAllOrders();
@@ -146,10 +117,10 @@ public class TradingBot : Bot, ITradingBot
public async Task LoadAccount()
{
var account = await AccountService.GetAccount(AccountName, false, false);
var account = await AccountService.GetAccount(Config.AccountName, false, false);
if (account == null)
{
Logger.LogWarning($"No account found for this {AccountName}");
Logger.LogWarning($"No account found for this {Config.AccountName}");
Stop();
}
else
@@ -185,7 +156,7 @@ public class TradingBot : Bot, ITradingBot
public async Task Run()
{
if (!IsForBacktest)
if (!Config.IsForBacktest)
{
// Check broker balance before running
var balance = await ExchangeService.GetBalance(Account, false);
@@ -200,25 +171,25 @@ public class TradingBot : Bot, ITradingBot
Logger.LogInformation($"____________________{Name}____________________");
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 {Config.BotType} - Ticker : {Config.Ticker}");
}
var previousLastCandle = OptimizedCandles.LastOrDefault();
if (!IsForBacktest)
if (!Config.IsForBacktest)
await UpdateCandles();
var currentLastCandle = OptimizedCandles.LastOrDefault();
if (currentLastCandle != previousLastCandle || IsForBacktest)
if (currentLastCandle != previousLastCandle || Config.IsForBacktest)
await UpdateSignals(OptimizedCandles);
else
Logger.LogInformation($"No need to update signals for {Ticker}");
Logger.LogInformation($"No need to update signals for {Config.Ticker}");
if (!IsForWatchingOnly)
if (!Config.IsForWatchingOnly)
await ManagePositions();
if (!IsForBacktest)
if (!Config.IsForBacktest)
{
SaveBackup();
UpdateStrategiesValues();
@@ -248,7 +219,8 @@ public class TradingBot : Bot, ITradingBot
if (OptimizedCandles.Any())
return;
var candles = await ExchangeService.GetCandlesInflux(Account.Exchange, Ticker, PreloadSince, Timeframe);
var candles =
await ExchangeService.GetCandlesInflux(Account.Exchange, Config.Ticker, PreloadSince, Config.Timeframe);
foreach (var candle in candles.Where(c => c.Date < DateTime.Now.ToUniversalTime()))
{
@@ -272,25 +244,22 @@ public class TradingBot : Bot, ITradingBot
await AddSignal(signal);
}
private async Task AddSignal(Signal signal)
{
// if (!IsForBacktest)
// TradingService.InsertSignal(signal);
if (IsForWatchingOnly || (ExecutionCount < 1 && !IsForBacktest))
if (Config.IsForWatchingOnly || (ExecutionCount < 1 && !Config.IsForBacktest))
signal.Status = SignalStatus.Expired;
Signals.Add(signal);
var signalText = $"{ScenarioName} trigger a signal. Signal told you " +
$"to {signal.Direction} {Ticker} on {Timeframe}. The confidence in this signal is {signal.Confidence}. Identifier : {signal.Identifier}";
var signalText = $"{Config.ScenarioName} trigger a signal. Signal told you " +
$"to {signal.Direction} {Config.Ticker} on {Config.Timeframe}. The confidence in this signal is {signal.Confidence}. Identifier : {signal.Identifier}";
Logger.LogInformation(signalText);
if (IsForWatchingOnly && !IsForBacktest && ExecutionCount > 0)
if (Config.IsForWatchingOnly && !Config.IsForBacktest && ExecutionCount > 0)
{
await MessengerService.SendSignal(signalText, Account.Exchange, Ticker, signal.Direction, Timeframe);
await MessengerService.SendSignal(signalText, Account.Exchange, Config.Ticker, signal.Direction,
Config.Timeframe);
}
}
@@ -300,7 +269,8 @@ public class TradingBot : Bot, ITradingBot
return;
var lastCandle = OptimizedCandles.Last();
var newCandle = await ExchangeService.GetCandlesInflux(Account.Exchange, Ticker, lastCandle.Date, Timeframe);
var newCandle =
await ExchangeService.GetCandlesInflux(Account.Exchange, Config.Ticker, lastCandle.Date, Config.Timeframe);
foreach (var candle in newCandle.Where(c => c.Date < DateTime.Now.ToUniversalTime()))
{
@@ -342,7 +312,7 @@ public class TradingBot : Bot, ITradingBot
if (WalletBalances.Count == 0)
{
// WalletBalances[date] = await ExchangeService.GetBalance(Account, IsForBacktest);
WalletBalances[date] = BotTradingBalance;
WalletBalances[date] = Config.BotTradingBalance;
return;
}
@@ -353,24 +323,23 @@ public class TradingBot : Bot, ITradingBot
}
}
private async Task UpdatePosition(Signal signal, Position positionForSignal)
{
try
{
Logger.LogInformation($"Updating position {positionForSignal.SignalIdentifier}");
var position = IsForBacktest
var position = Config.IsForBacktest
? positionForSignal
: TradingService.GetPositionByIdentifier(positionForSignal.Identifier);
var positionsExchange = IsForBacktest
var positionsExchange = Config.IsForBacktest
? new List<Position> { position }
: await TradingService.GetBrokerPositions(Account);
if (!IsForBacktest)
if (!Config.IsForBacktest)
{
var brokerPosition = positionsExchange.FirstOrDefault(p => p.Ticker == Ticker);
var brokerPosition = positionsExchange.FirstOrDefault(p => p.Ticker == Config.Ticker);
if (brokerPosition != null)
{
UpdatePositionPnl(positionForSignal.Identifier, brokerPosition.ProfitAndLoss.Realized);
@@ -395,7 +364,7 @@ public class TradingBot : Bot, ITradingBot
if (position.Status == PositionStatus.New)
{
var orders = await ExchangeService.GetOpenOrders(Account, Ticker);
var orders = await ExchangeService.GetOpenOrders(Account, Config.Ticker);
if (orders.Any())
{
await LogInformation(
@@ -420,9 +389,9 @@ public class TradingBot : Bot, ITradingBot
// Position might be partially filled, meaning that TPSL havent been sended yet
// But the position might already been closed by the exchange so we have to check should be closed
var lastCandle = IsForBacktest
var lastCandle = Config.IsForBacktest
? OptimizedCandles.Last()
: ExchangeService.GetCandle(Account, Ticker, DateTime.UtcNow);
: ExchangeService.GetCandle(Account, Config.Ticker, DateTime.UtcNow);
if (positionForSignal.OriginDirection == TradeDirection.Long)
{
@@ -512,7 +481,6 @@ public class TradingBot : Bot, ITradingBot
}
}
private async Task OpenPosition(Signal signal)
{
// Check if a position is already open
@@ -521,9 +489,9 @@ public class TradingBot : Bot, ITradingBot
var openedPosition = Positions.FirstOrDefault(p => p.Status == PositionStatus.Filled
&& p.SignalIdentifier != signal.Identifier);
var lastPrice = IsForBacktest
var lastPrice = Config.IsForBacktest
? OptimizedCandles.Last().Close
: ExchangeService.GetPrice(Account, Ticker, DateTime.UtcNow);
: ExchangeService.GetPrice(Account, Config.Ticker, DateTime.UtcNow);
// If position open
if (openedPosition != null)
@@ -542,7 +510,7 @@ public class TradingBot : Bot, ITradingBot
{
// An operation is already open for the opposite direction
// ==> Flip the position
if (FlipPosition)
if (Config.FlipPosition)
{
await LogInformation("Try to flip the position because of an opposite direction signal");
await CloseTrade(previousSignal, openedPosition, openedPosition.Open, lastPrice, true);
@@ -561,10 +529,8 @@ public class TradingBot : Bot, ITradingBot
}
else
{
if (!CanOpenPosition(signal))
if (!(await CanOpenPosition(signal)))
{
await LogInformation(
"Tried to open position but last position was a loss. Wait for an opposition direction side or wait x candles to open a new position");
SetSignalStatus(signal.Identifier, SignalStatus.Expired);
return;
}
@@ -575,15 +541,15 @@ public class TradingBot : Bot, ITradingBot
try
{
var command = new OpenPositionRequest(
AccountName,
MoneyManagement,
Config.AccountName,
Config.MoneyManagement,
signal.Direction,
Ticker,
Config.Ticker,
PositionInitiator.Bot,
signal.Date,
User,
BotTradingBalance,
IsForBacktest,
Config.BotTradingBalance,
Config.IsForBacktest,
lastPrice,
signalIdentifier: signal.Identifier);
@@ -598,7 +564,7 @@ public class TradingBot : Bot, ITradingBot
{
SetSignalStatus(signal.Identifier, SignalStatus.PositionOpen);
if (!IsForBacktest)
if (!Config.IsForBacktest)
{
await MessengerService.SendPosition(position);
}
@@ -622,25 +588,142 @@ public class TradingBot : Bot, ITradingBot
}
}
private bool CanOpenPosition(Signal signal)
private async Task<bool> CanOpenPosition(Signal signal)
{
if (!IsForBacktest && ExecutionCount < 1)
// Early return if we're in backtest mode and haven't executed yet
if (!Config.IsForBacktest && ExecutionCount < 1)
{
await LogInformation("Cannot open position: Bot hasn't executed yet");
return false;
}
if (Positions.Count == 0)
// Check if we're in backtest mode
if (Config.IsForBacktest)
{
return await CheckCooldownPeriod(signal) && await CheckLossStreak(signal);
}
// Check broker positions for live trading
var canOpenPosition = await CheckBrokerPositions();
if (!canOpenPosition)
{
return false;
}
// Check cooldown period and loss streak
return await CheckCooldownPeriod(signal) && await CheckLossStreak(signal);
}
private async Task<bool> CheckLossStreak(Signal signal)
{
// If MaxLossStreak is 0, there's no limit
if (Config.MaxLossStreak <= 0)
{
return true;
}
// Get the last N finished positions regardless of direction
var recentPositions = Positions
.Where(p => p.IsFinished())
.OrderByDescending(p => p.Open.Date)
.Take(Config.MaxLossStreak)
.ToList();
// If we don't have enough positions to form a streak, we can open
if (recentPositions.Count < Config.MaxLossStreak)
{
return true;
}
// Check if all recent positions were losses
var allLosses = recentPositions.All(p => p.ProfitAndLoss?.Realized < 0);
if (!allLosses)
{
return true;
}
// If we have a loss streak, check if the last position was in the same direction as the signal
var lastPosition = recentPositions.First();
if (lastPosition.OriginDirection == signal.Direction)
{
await LogWarning($"Cannot open position: Max loss streak ({Config.MaxLossStreak}) reached. " +
$"Last {recentPositions.Count} trades were losses. " +
$"Last position was {lastPosition.OriginDirection}, waiting for a signal in the opposite direction.");
return false;
}
return true;
}
private async Task<bool> CheckBrokerPositions()
{
try
{
var positions = await ExchangeService.GetBrokerPositions(Account);
if (!positions.Any(p => p.Ticker == Config.Ticker))
{
return true;
}
// Handle existing position on broker
var previousPosition = Positions.LastOrDefault();
var orders = await ExchangeService.GetOpenOrders(Account, Config.Ticker);
var reason = $"Cannot open position. There is already a position open for {Config.Ticker} on the broker.";
if (previousPosition != null && orders.Count >= 2)
{
await SetPositionStatus(previousPosition.SignalIdentifier, PositionStatus.Filled);
}
else
{
reason +=
" Position open on broker but not enough orders or no previous position internally saved by the bot";
}
await LogWarning(reason);
return false;
}
catch (Exception ex)
{
await LogWarning($"Error checking broker positions: {ex.Message}");
return false;
}
}
private async Task<bool> CheckCooldownPeriod(Signal signal)
{
var lastPosition = Positions.LastOrDefault(p => p.IsFinished()
&& p.SignalIdentifier != signal.Identifier
&& p.OriginDirection == signal.Direction);
if (lastPosition == null)
{
return true;
}
var cooldownCandle = OptimizedCandles.TakeLast((int)Config.CooldownPeriod).FirstOrDefault();
if (cooldownCandle == null)
{
await LogWarning("Cannot check cooldown period: Not enough candles available");
return false;
}
var tenCandleAgo = OptimizedCandles.TakeLast(10).First();
var positionSignal = Signals.FirstOrDefault(s => s.Identifier == lastPosition.SignalIdentifier);
if (positionSignal == null)
{
await LogWarning($"Cannot find signal for last position {lastPosition.Identifier}");
return false;
}
return positionSignal.Date < tenCandleAgo.Date;
var canOpenPosition = positionSignal.Date < cooldownCandle.Date;
if (!canOpenPosition)
{
await LogInformation(
$"Position cannot be opened: Cooldown period not elapsed. Last position date: {positionSignal.Date}, Cooldown candle date: {cooldownCandle.Date}");
}
return canOpenPosition;
}
public async Task CloseTrade(Signal signal, Position position, Trade tradeToClose, decimal lastPrice,
@@ -654,18 +737,18 @@ public class TradingBot : Bot, ITradingBot
}
await LogInformation(
$"Trying to close trade {Ticker} at {lastPrice} - Type : {tradeToClose.TradeType} - Quantity : {tradeToClose.Quantity} " +
$"Trying to close trade {Config.Ticker} at {lastPrice} - Type : {tradeToClose.TradeType} - Quantity : {tradeToClose.Quantity} " +
$"- Closing Position : {tradeClosingPosition}");
// Get status of position before closing it. The position might be already close by the exchange
if (!IsForBacktest && await ExchangeService.GetQuantityInPosition(Account, Ticker) == 0)
if (!Config.IsForBacktest && await ExchangeService.GetQuantityInPosition(Account, Config.Ticker) == 0)
{
Logger.LogInformation($"Trade already close on exchange");
await HandleClosedPosition(position);
}
else
{
var command = new ClosePositionCommand(position, lastPrice, isForBacktest: IsForBacktest);
var command = new ClosePositionCommand(position, lastPrice, isForBacktest: Config.IsForBacktest);
try
{
var closedPosition = (new ClosePositionCommandHandler(ExchangeService, AccountService, TradingService)
@@ -710,12 +793,12 @@ public class TradingBot : Bot, ITradingBot
if (position.ProfitAndLoss != null)
{
// Add PnL (could be positive or negative)
BotTradingBalance += position.ProfitAndLoss.Realized;
Config.BotTradingBalance += position.ProfitAndLoss.Realized;
// Subtract fees
BotTradingBalance -= GetPositionFees(position);
Config.BotTradingBalance -= GetPositionFees(position);
Logger.LogInformation($"Updated bot trading balance to: {BotTradingBalance}");
Logger.LogInformation($"Updated bot trading balance to: {Config.BotTradingBalance}");
}
}
else
@@ -723,7 +806,7 @@ public class TradingBot : Bot, ITradingBot
await LogWarning("Weird things happen - Trying to update position status, but no position found");
}
if (!IsForBacktest)
if (!Config.IsForBacktest)
{
await MessengerService.SendClosingPosition(position);
}
@@ -733,15 +816,15 @@ public class TradingBot : Bot, ITradingBot
private async Task CancelAllOrders()
{
if (!IsForBacktest && !IsForWatchingOnly)
if (!Config.IsForBacktest && !Config.IsForWatchingOnly)
{
try
{
var openOrders = await ExchangeService.GetOpenOrders(Account, Ticker);
var openOrders = await ExchangeService.GetOpenOrders(Account, Config.Ticker);
if (openOrders.Any())
{
var openPositions = (await ExchangeService.GetBrokerPositions(Account))
.Where(p => p.Ticker == Ticker);
.Where(p => p.Ticker == Config.Ticker);
var cancelClose = openPositions.Any();
if (cancelClose)
@@ -750,15 +833,15 @@ public class TradingBot : Bot, ITradingBot
}
else
{
Logger.LogInformation($"Canceling all orders for {Ticker}");
await ExchangeService.CancelOrder(Account, Ticker);
var closePendingOrderStatus = await ExchangeService.CancelOrder(Account, Ticker);
Logger.LogInformation($"Closing all {Ticker} orders status : {closePendingOrderStatus}");
Logger.LogInformation($"Canceling all orders for {Config.Ticker}");
await ExchangeService.CancelOrder(Account, Config.Ticker);
var closePendingOrderStatus = await ExchangeService.CancelOrder(Account, Config.Ticker);
Logger.LogInformation($"Closing all {Config.Ticker} orders status : {closePendingOrderStatus}");
}
}
else
{
Logger.LogInformation($"No need to cancel orders for {Ticker}");
Logger.LogInformation($"No need to cancel orders for {Config.Ticker}");
}
}
catch (Exception ex)
@@ -771,10 +854,11 @@ public class TradingBot : Bot, ITradingBot
private async Task SetPositionStatus(string signalIdentifier, PositionStatus positionStatus)
{
if (!Positions.First(p => p.SignalIdentifier == signalIdentifier).Status.Equals(positionStatus))
var position = Positions.First(p => p.SignalIdentifier == signalIdentifier);
if (!position.Status.Equals(positionStatus))
{
await LogInformation($"Position {signalIdentifier} is now {positionStatus}");
Positions.First(p => p.SignalIdentifier == signalIdentifier).Status = positionStatus;
await LogInformation($"Position {signalIdentifier} new status {position.Status} => {positionStatus}");
}
SetSignalStatus(signalIdentifier,
@@ -864,8 +948,8 @@ public class TradingBot : Bot, ITradingBot
public async Task ToggleIsForWatchOnly()
{
IsForWatchingOnly = (!IsForWatchingOnly);
await LogInformation($"Watch only toggle for bot : {Name} - Watch only : {IsForWatchingOnly}");
Config.IsForWatchingOnly = !Config.IsForWatchingOnly;
await LogInformation($"Watch only toggle for bot : {Name} - Watch only : {Config.IsForWatchingOnly}");
}
private async Task LogInformation(string message)
@@ -883,7 +967,7 @@ public class TradingBot : Bot, ITradingBot
private async Task SendTradeMessage(string message, bool isBadBehavior = false)
{
if (!IsForBacktest)
if (!Config.IsForBacktest)
{
await MessengerService.SendTradeMessage(message, isBadBehavior);
}
@@ -894,43 +978,47 @@ public class TradingBot : Bot, ITradingBot
var data = new TradingBotBackup
{
Name = Name,
BotType = BotType,
BotType = Config.BotType,
Signals = Signals,
Positions = Positions,
Timeframe = Timeframe,
Ticker = Ticker,
ScenarioName = ScenarioName,
AccountName = AccountName,
IsForWatchingOnly = IsForWatchingOnly,
Timeframe = Config.Timeframe,
Ticker = Config.Ticker,
ScenarioName = Config.ScenarioName,
AccountName = Config.AccountName,
IsForWatchingOnly = Config.IsForWatchingOnly,
WalletBalances = WalletBalances,
MoneyManagement = MoneyManagement,
BotTradingBalance = BotTradingBalance,
MoneyManagement = Config.MoneyManagement,
BotTradingBalance = Config.BotTradingBalance,
StartupTime = StartupTime,
CooldownPeriod = Config.CooldownPeriod,
};
BotService.SaveOrUpdateBotBackup(User, Identifier, BotType, JsonConvert.SerializeObject(data));
BotService.SaveOrUpdateBotBackup(User, Identifier, Config.BotType, Status, JsonConvert.SerializeObject(data));
}
public override void LoadBackup(BotBackup backup)
{
var data = JsonConvert.DeserializeObject<TradingBotBackup>(backup.Data);
Config = new TradingBotConfig
{
AccountName = data.AccountName,
MoneyManagement = data.MoneyManagement,
Ticker = data.Ticker,
ScenarioName = data.ScenarioName,
Timeframe = data.Timeframe,
IsForBacktest = false, // Always false when loading from backup
IsForWatchingOnly = data.IsForWatchingOnly,
BotTradingBalance = data.BotTradingBalance,
BotType = data.BotType,
CooldownPeriod = data.CooldownPeriod,
};
Signals = data.Signals;
Positions = data.Positions;
WalletBalances = data.WalletBalances;
// MoneyManagement = data.MoneyManagement; => loaded from database
Timeframe = data.Timeframe;
Ticker = data.Ticker;
ScenarioName = data.ScenarioName;
AccountName = data.AccountName;
IsForWatchingOnly = data.IsForWatchingOnly;
BotTradingBalance = data.BotTradingBalance;
PreloadSince = data.StartupTime;
Identifier = backup.Identifier;
User = backup.User;
// Restore the startup time if it was previously saved
if (data.StartupTime != DateTime.MinValue)
{
StartupTime = data.StartupTime;
}
Status = backup.LastStatus;
}
/// <summary>
@@ -949,7 +1037,8 @@ public class TradingBot : Bot, ITradingBot
}
// Create a fake signal for manual position opening
var signal = new Signal(Ticker, direction, Confidence.Low, lastCandle, lastCandle.Date, TradingExchanges.GmxV2,
var signal = new Signal(Config.Ticker, direction, Confidence.Low, lastCandle, lastCandle.Date,
TradingExchanges.GmxV2,
StrategyType.Stc, SignalType.Signal);
signal.Status = SignalStatus.WaitingForPosition; // Ensure status is correct
signal.User = Account.User; // Assign user
@@ -989,4 +1078,5 @@ public class TradingBotBackup
public MoneyManagement MoneyManagement { get; set; }
public DateTime StartupTime { get; set; }
public decimal BotTradingBalance { get; set; }
public decimal CooldownPeriod { get; set; }
}

View File

@@ -0,0 +1,20 @@
using Managing.Domain.MoneyManagements;
using static Managing.Common.Enums;
namespace Managing.Application.Bots;
public class TradingBotConfig
{
public string AccountName { get; set; }
public MoneyManagement MoneyManagement { get; set; }
public Ticker Ticker { get; set; }
public string ScenarioName { get; set; }
public Timeframe Timeframe { get; set; }
public bool IsForBacktest { get; set; }
public bool IsForWatchingOnly { get; set; }
public bool FlipPosition { get; set; }
public BotType BotType { get; set; }
public decimal BotTradingBalance { get; set; }
public decimal CooldownPeriod { get; set; } = 1;
public int MaxLossStreak { get; set; } = 0; // 0 means no limit
}

View File

@@ -3,13 +3,13 @@ using Managing.Application.Abstractions;
using Managing.Application.Abstractions.Repositories;
using Managing.Application.Abstractions.Services;
using Managing.Application.Bots;
using Managing.Common;
using Managing.Domain.Bots;
using Managing.Domain.MoneyManagements;
using Managing.Domain.Users;
using Managing.Domain.Workflows;
using Microsoft.Extensions.Logging;
using Newtonsoft.Json;
using static Managing.Common.Enums;
namespace Managing.Application.ManageBot
{
@@ -22,13 +22,14 @@ namespace Managing.Application.ManageBot
private readonly ILogger<TradingBot> _tradingBotLogger;
private readonly ITradingService _tradingService;
private readonly IMoneyManagementService _moneyManagementService;
private readonly IUserService _userService;
private ConcurrentDictionary<string, BotTaskWrapper> _botTasks =
new ConcurrentDictionary<string, BotTaskWrapper>();
public BotService(IBotRepository botRepository, IExchangeService exchangeService,
IMessengerService messengerService, IAccountService accountService, ILogger<TradingBot> tradingBotLogger,
ITradingService tradingService, IMoneyManagementService moneyManagementService)
ITradingService tradingService, IMoneyManagementService moneyManagementService, IUserService userService)
{
_botRepository = botRepository;
_exchangeService = exchangeService;
@@ -37,11 +38,7 @@ namespace Managing.Application.ManageBot
_tradingBotLogger = tradingBotLogger;
_tradingService = tradingService;
_moneyManagementService = moneyManagementService;
}
public async void SaveOrUpdateBotBackup(BotBackup botBackup)
{
await _botRepository.InsertBotAsync(botBackup);
_userService = userService;
}
public BotBackup GetBotBackup(string identifier)
@@ -49,12 +46,13 @@ namespace Managing.Application.ManageBot
return _botRepository.GetBots().FirstOrDefault(b => b.Identifier == identifier);
}
public void SaveOrUpdateBotBackup(User user, string identifier, Enums.BotType botType, string data)
public void SaveOrUpdateBotBackup(User user, string identifier, BotType botType, BotStatus status, string data)
{
var backup = GetBotBackup(identifier);
if (backup != null)
{
backup.LastStatus = status;
backup.Data = data;
_botRepository.UpdateBackupBot(backup);
}
@@ -62,6 +60,7 @@ namespace Managing.Application.ManageBot
{
var botBackup = new BotBackup
{
LastStatus = status,
User = user,
Identifier = identifier,
BotType = botType,
@@ -127,7 +126,7 @@ namespace Managing.Application.ManageBot
// null); // Assuming null is an acceptable parameter for workflow
// botTask = Task.Run(() => ((IBot)bot).Start());
// break;
case Enums.BotType.ScalpingBot:
case BotType.ScalpingBot:
var scalpingBotData = JsonConvert.DeserializeObject<TradingBotBackup>(backupBot.Data);
var scalpingMoneyManagement =
_moneyManagementService.GetMoneyMangement(scalpingBotData.MoneyManagement.Name).Result;
@@ -142,7 +141,7 @@ namespace Managing.Application.ManageBot
scalpingBotData.BotTradingBalance);
botTask = Task.Run(() => InitBot((ITradingBot)bot, backupBot));
break;
case Enums.BotType.FlippingBot:
case BotType.FlippingBot:
var flippingBotData = JsonConvert.DeserializeObject<TradingBotBackup>(backupBot.Data);
var flippingMoneyManagement =
_moneyManagementService.GetMoneyMangement(flippingBotData.MoneyManagement.Name).Result;
@@ -166,9 +165,12 @@ namespace Managing.Application.ManageBot
}
}
private static Action InitBot(ITradingBot bot, BotBackup backupBot)
private Action InitBot(ITradingBot bot, BotBackup backupBot)
{
bot.Start();
var user = _userService.GetUser(backupBot.User.Name);
backupBot.User = user;
bot.LoadBackup(backupBot);
return () => { };
}
@@ -178,9 +180,9 @@ namespace Managing.Application.ManageBot
return new SimpleBot(botName, _tradingBotLogger, workflow, this);
}
public async Task<string> StopBot(string botName)
public async Task<string> StopBot(string identifier)
{
if (_botTasks.TryGetValue(botName, out var botWrapper))
if (_botTasks.TryGetValue(identifier, out var botWrapper))
{
if (botWrapper.BotInstance is IBot bot)
{
@@ -190,12 +192,12 @@ namespace Managing.Application.ManageBot
}
}
return Enums.BotStatus.Down.ToString();
return BotStatus.Down.ToString();
}
public async Task<bool> DeleteBot(string botName)
public async Task<bool> DeleteBot(string identifier)
{
if (_botTasks.TryRemove(botName, out var botWrapper))
if (_botTasks.TryRemove(identifier, out var botWrapper))
{
try
{
@@ -205,7 +207,7 @@ namespace Managing.Application.ManageBot
bot.Stop()); // Assuming Stop is an asynchronous process wrapped in Task.Run for synchronous methods
}
await _botRepository.DeleteBotBackup(botName);
await _botRepository.DeleteBotBackup(identifier);
return true;
}
catch (Exception e)
@@ -218,9 +220,9 @@ namespace Managing.Application.ManageBot
return false;
}
public Task<string> RestartBot(string botName)
public Task<string> RestartBot(string identifier)
{
if (_botTasks.TryGetValue(botName, out var botWrapper))
if (_botTasks.TryGetValue(identifier, out var botWrapper))
{
if (botWrapper.BotInstance is IBot bot)
{
@@ -229,17 +231,17 @@ namespace Managing.Application.ManageBot
}
}
return Task.FromResult(Enums.BotStatus.Down.ToString());
return Task.FromResult(BotStatus.Down.ToString());
}
public void DeleteBotBackup(string backupBotName)
public void DeleteBotBackup(string identifier)
{
_botRepository.DeleteBotBackup(backupBotName);
_botRepository.DeleteBotBackup(identifier);
}
public void ToggleIsForWatchingOnly(string botName)
public void ToggleIsForWatchingOnly(string identifier)
{
if (_botTasks.TryGetValue(botName, out var botTaskWrapper) &&
if (_botTasks.TryGetValue(identifier, out var botTaskWrapper) &&
botTaskWrapper.BotInstance is ITradingBot tradingBot)
{
tradingBot.ToggleIsForWatchOnly().Wait();
@@ -247,89 +249,159 @@ namespace Managing.Application.ManageBot
}
public ITradingBot CreateScalpingBot(string accountName, MoneyManagement moneyManagement, string name,
Enums.Ticker ticker, string scenario, Enums.Timeframe interval, bool isForWatchingOnly,
Ticker ticker, string scenario, Timeframe interval, bool isForWatchingOnly,
decimal initialTradingBalance)
{
var config = new TradingBotConfig
{
AccountName = accountName,
MoneyManagement = moneyManagement,
Ticker = ticker,
ScenarioName = scenario,
Timeframe = interval,
IsForWatchingOnly = isForWatchingOnly,
BotTradingBalance = initialTradingBalance,
BotType = BotType.ScalpingBot
};
return new ScalpingBot(
accountName,
moneyManagement,
name,
scenario,
_exchangeService,
ticker,
_tradingService,
_tradingBotLogger,
interval,
_tradingService,
_accountService,
_messengerService,
this,
initialTradingBalance,
isForWatchingOnly: isForWatchingOnly);
config);
}
public ITradingBot CreateBacktestScalpingBot(string accountName, MoneyManagement moneyManagement,
Enums.Ticker ticker, string scenario, Enums.Timeframe interval, bool isForWatchingOnly,
Ticker ticker, string scenario, Timeframe interval, bool isForWatchingOnly,
decimal initialTradingBalance)
{
var config = new TradingBotConfig
{
AccountName = accountName,
MoneyManagement = moneyManagement,
Ticker = ticker,
ScenarioName = scenario,
Timeframe = interval,
IsForWatchingOnly = isForWatchingOnly,
BotTradingBalance = initialTradingBalance,
BotType = BotType.ScalpingBot,
IsForBacktest = true
};
return new ScalpingBot(
accountName,
moneyManagement,
"BacktestBot",
scenario,
_exchangeService,
ticker,
_tradingService,
_tradingBotLogger,
interval,
_tradingService,
_accountService,
_messengerService,
this,
initialTradingBalance,
isForBacktest: true,
isForWatchingOnly: isForWatchingOnly);
config);
}
public ITradingBot CreateFlippingBot(string accountName, MoneyManagement moneyManagement, string name,
Enums.Ticker ticker, string scenario, Enums.Timeframe interval, bool isForWatchingOnly,
Ticker ticker, string scenario, Timeframe interval, bool isForWatchingOnly,
decimal initialTradingBalance)
{
var config = new TradingBotConfig
{
AccountName = accountName,
MoneyManagement = moneyManagement,
Ticker = ticker,
ScenarioName = scenario,
Timeframe = interval,
IsForWatchingOnly = isForWatchingOnly,
BotTradingBalance = initialTradingBalance,
BotType = BotType.FlippingBot
};
return new FlippingBot(
accountName,
moneyManagement,
name,
scenario,
_exchangeService,
ticker,
_tradingService,
_tradingBotLogger,
interval,
_tradingService,
_accountService,
_messengerService,
this,
initialTradingBalance,
isForWatchingOnly: isForWatchingOnly);
config);
}
public ITradingBot CreateBacktestFlippingBot(string accountName, MoneyManagement moneyManagement,
Enums.Ticker ticker, string scenario, Enums.Timeframe interval, bool isForWatchingOnly,
Ticker ticker, string scenario, Timeframe interval, bool isForWatchingOnly,
decimal initialTradingBalance)
{
var config = new TradingBotConfig
{
AccountName = accountName,
MoneyManagement = moneyManagement,
Ticker = ticker,
ScenarioName = scenario,
Timeframe = interval,
IsForWatchingOnly = isForWatchingOnly,
BotTradingBalance = initialTradingBalance,
BotType = BotType.FlippingBot,
IsForBacktest = true
};
return new FlippingBot(
accountName,
moneyManagement,
"BacktestBot",
scenario,
_exchangeService,
ticker,
_tradingService,
_tradingBotLogger,
interval,
_tradingService,
_accountService,
_messengerService,
this,
initialTradingBalance,
isForBacktest: true,
isForWatchingOnly: isForWatchingOnly);
config);
}
public ITradingBot CreateScalpingBot(TradingBotConfig config)
{
return new ScalpingBot(
_exchangeService,
_tradingBotLogger,
_tradingService,
_accountService,
_messengerService,
this,
config);
}
public ITradingBot CreateBacktestScalpingBot(TradingBotConfig config)
{
config.IsForBacktest = true;
return new ScalpingBot(
_exchangeService,
_tradingBotLogger,
_tradingService,
_accountService,
_messengerService,
this,
config);
}
public ITradingBot CreateFlippingBot(TradingBotConfig config)
{
return new FlippingBot(
_exchangeService,
_tradingBotLogger,
_tradingService,
_accountService,
_messengerService,
this,
config);
}
public ITradingBot CreateBacktestFlippingBot(TradingBotConfig config)
{
config.IsForBacktest = true;
return new FlippingBot(
_exchangeService,
_tradingBotLogger,
_tradingService,
_accountService,
_messengerService,
this,
config);
}
}
}

View File

@@ -1,44 +1,20 @@
using Managing.Domain.Users;
using Managing.Application.Bots;
using Managing.Domain.Users;
using MediatR;
using static Managing.Common.Enums;
namespace Managing.Application.ManageBot.Commands
{
public class StartBotCommand : IRequest<string>
{
public string Name { get; }
public BotType BotType { get; }
public Ticker Ticker { get; internal set; }
public Timeframe Timeframe { get; internal set; }
public bool IsForWatchingOnly { get; internal set; }
public string Scenario { get; internal set; }
public string AccountName { get; internal set; }
public string MoneyManagementName { get; internal set; }
public User User { get; internal set; }
public decimal InitialTradingBalance { get; internal set; }
public TradingBotConfig Config { get; }
public User User { get; }
public StartBotCommand(BotType botType,
string name,
Ticker ticker,
string scenario,
Timeframe timeframe,
string accountName,
string moneyManagementName,
User user,
bool isForWatchingOnly = false,
decimal initialTradingBalance = 0)
public StartBotCommand(TradingBotConfig config, string name, User user)
{
BotType = botType;
Config = config;
Name = name;
Scenario = scenario;
Ticker = ticker;
Timeframe = timeframe;
IsForWatchingOnly = isForWatchingOnly;
AccountName = accountName;
MoneyManagementName = moneyManagementName;
User = user;
InitialTradingBalance = initialTradingBalance > 0 ? initialTradingBalance :
throw new ArgumentException("Initial trading balance must be greater than zero", nameof(initialTradingBalance));
}
}
}

View File

@@ -17,7 +17,7 @@ namespace Managing.Application.ManageBot
{
var allActiveBots = _botService.GetActiveBots();
var userBots = allActiveBots
.Where(bot => bot.User != null && bot.User.Name == request.UserName)
.Where(bot => bot.User != null && bot.User.AgentName == request.UserName)
.ToList();
return Task.FromResult(userBots);

View File

@@ -19,15 +19,14 @@ namespace Managing.Application.ManageBot
public Task<ITradingBot> Handle(GetUserStrategyCommand request, CancellationToken cancellationToken)
{
var allActiveBots = _botService.GetActiveBots();
// Find the specific strategy that matches both user and strategy name
var strategy = allActiveBots
.FirstOrDefault(bot =>
bot.User != null &&
bot.User.Name == request.AgentName &&
bot.Name == request.StrategyName);
.FirstOrDefault(bot =>
bot.User.AgentName == request.AgentName &&
bot.Identifier == request.StrategyName);
return Task.FromResult(strategy);
}
}
}
}

View File

@@ -31,6 +31,13 @@ public class LoadBackupBotCommandHandler : IRequestHandler<LoadBackupBotCommand,
{
try
{
if (backupBot.LastStatus == BotStatus.Down)
{
_logger.LogInformation("Skipping backup bot {Identifier} as it is marked as Down.",
backupBot.Identifier);
continue;
}
var activeBot = _botService.GetActiveBots().FirstOrDefault(b => b.Identifier == backupBot.Identifier);
if (activeBot == null)

View File

@@ -29,38 +29,38 @@ namespace Managing.Application.ManageBot
{
BotStatus botStatus = BotStatus.Down;
var account = await _accountService.GetAccount(request.AccountName, true, true);
var account = await _accountService.GetAccount(request.Config.AccountName, true, true);
if (account == null)
{
throw new Exception($"Account {request.AccountName} not found");
throw new Exception($"Account {request.Config.AccountName} not found");
}
var usdcBalance = account.Balances.FirstOrDefault(b => b.TokenName == Ticker.USDC.ToString());
if (usdcBalance == null || usdcBalance.Value < request.InitialTradingBalance)
if (usdcBalance == null || usdcBalance.Value < request.Config.BotTradingBalance)
{
throw new Exception($"Account {request.AccountName} has no USDC balance or not enough balance");
throw new Exception($"Account {request.Config.AccountName} has no USDC balance or not enough balance");
}
var moneyManagement =
await _moneyManagementService.GetMoneyMangement(request.User, request.MoneyManagementName);
switch (request.BotType)
// Ensure cooldown period is set
if (request.Config.CooldownPeriod <= 0)
{
request.Config.CooldownPeriod = 1; // Default to 1 minute if not set
}
switch (request.Config.BotType)
{
case BotType.SimpleBot:
var bot = _botFactory.CreateSimpleBot(request.Name, null);
_botService.AddSimpleBotToCache(bot);
return bot.GetStatus();
case BotType.ScalpingBot:
var sBot = _botFactory.CreateScalpingBot(request.AccountName, moneyManagement, request.Name,
request.Ticker, request.Scenario, request.Timeframe, request.IsForWatchingOnly,
request.InitialTradingBalance);
var sBot = _botFactory.CreateScalpingBot(request.Config);
_botService.AddTradingBotToCache(sBot);
return sBot.GetStatus();
case BotType.FlippingBot:
var fBot = _botFactory.CreateFlippingBot(request.AccountName, moneyManagement, request.Name,
request.Ticker, request.Scenario, request.Timeframe, request.IsForWatchingOnly,
request.InitialTradingBalance);
var fBot = _botFactory.CreateFlippingBot(request.Config);
_botService.AddTradingBotToCache(fBot);
return fBot.GetStatus();
}

View File

@@ -17,7 +17,7 @@ namespace Managing.Application.ManageBot
{
_botService.ToggleIsForWatchingOnly(request.Name);
var bot = _botService.GetActiveBots().FirstOrDefault(b => b.Name == request.Name);
return Task.FromResult(bot?.IsForWatchingOnly.ToString());
return Task.FromResult(bot?.Config.IsForWatchingOnly.ToString());
}
}
}

View File

@@ -24,7 +24,7 @@ public class UserService : IUserService
"0x23AA99254cfaA2c374bE2bA5B55C68018cCdFCb3", // Local optiflex
"0x932167388dD9aad41149b3cA23eBD489E2E2DD78", // Embedded wallet
"0x66CB57Fe3f53cE57376421106dFDa2D39186cBd0", // Embedded wallet optiflex
"0x7baBf95621f22bEf2DB67E500D022Ca110722FaD", // DevCowchain
"0x7baBf95621f22bEf2DB67E500D022Ca110722FaD", // DevCowchain
"0xc8bC497534d0A43bAb2BBA9BA94d46D9Ddfaea6B" // DevCowchain2
];
@@ -47,7 +47,8 @@ public class UserService : IUserService
if (!message.Equals("KaigenTeamXCowchain"))
{
_logger.LogWarning($"Message {message} not starting with KaigenTeamXCowchain");
throw new Exception($"Message not good : {message} - Address : {address} - User : {name} - Signature : {signature}");
throw new Exception(
$"Message not good : {message} - Address : {address} - User : {name} - Signature : {signature}");
}
// if (!authorizedAddresses.Contains(recoveredAddress))
@@ -106,11 +107,19 @@ public class UserService : IUserService
{
account
};
// Update user with the new account
await _userRepository.UpdateUser(user);
}
return user;
}
public User GetUser(string name)
{
return _userRepository.GetUserByNameAsync(name).Result;
}
public async Task<User> GetUserByAddressAsync(string address)
{
var account = await _accountService.GetAccountByKey(address, true, false);
@@ -118,4 +127,18 @@ public class UserService : IUserService
user.Accounts = _accountService.GetAccountsByUser(user).ToList();
return user;
}
}
public async Task<User> UpdateAgentName(User user, string agentName)
{
// Check if agent name is already used
var existingUser = await _userRepository.GetUserByAgentNameAsync(agentName);
if (existingUser != null)
{
throw new Exception("Agent name already used");
}
user.AgentName = agentName;
await _userRepository.UpdateUser(user);
return user;
}
}