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