Add isLiveTrading helper to fix bug
This commit is contained in:
@@ -68,7 +68,7 @@ public abstract class TradingBotBase : ITradingBot
|
||||
|
||||
public virtual async Task Start(BotStatus previousStatus)
|
||||
{
|
||||
if (Config.TradingType == TradingType.Futures)
|
||||
if (TradingBox.IsLiveTrading(Config.TradingType))
|
||||
{
|
||||
// Start async initialization in the background without blocking
|
||||
try
|
||||
@@ -111,8 +111,8 @@ public abstract class TradingBotBase : ITradingBot
|
||||
case BotStatus.Stopped:
|
||||
// If status was Stopped we log a message to inform the user that the bot is restarting
|
||||
await LogInformationAsync($"🔄 Bot Restarted\n" +
|
||||
$"📊 Resuming operations with {Signals.Count} signals and {Positions.Count} positions\n" +
|
||||
$"✅ Ready to continue trading");
|
||||
$"📊 Resuming operations with {Signals.Count} signals and {Positions.Count} positions\n" +
|
||||
$"✅ Ready to continue trading");
|
||||
break;
|
||||
|
||||
default:
|
||||
@@ -235,7 +235,7 @@ public abstract class TradingBotBase : ITradingBot
|
||||
await ManagePositions();
|
||||
|
||||
UpdateWalletBalances();
|
||||
if (Config.TradingType == TradingType.Futures)
|
||||
if (TradingBox.IsLiveTrading(Config.TradingType))
|
||||
{
|
||||
ExecutionCount++;
|
||||
|
||||
@@ -436,7 +436,9 @@ public abstract class TradingBotBase : ITradingBot
|
||||
|
||||
protected void UpdateWalletBalances()
|
||||
{
|
||||
var date = Config.TradingType == TradingType.BacktestFutures ? LastCandle?.Date ?? DateTime.UtcNow : DateTime.UtcNow;
|
||||
var date = Config.TradingType == TradingType.BacktestFutures
|
||||
? LastCandle?.Date ?? DateTime.UtcNow
|
||||
: DateTime.UtcNow;
|
||||
|
||||
if (WalletBalances.Count == 0)
|
||||
{
|
||||
@@ -474,7 +476,7 @@ public abstract class TradingBotBase : ITradingBot
|
||||
|
||||
// Common position status handling
|
||||
if (internalPosition.Status == PositionStatus.Finished ||
|
||||
internalPosition.Status == PositionStatus.Flipped)
|
||||
internalPosition.Status == PositionStatus.Flipped)
|
||||
{
|
||||
await HandleClosedPosition(positionForSignal);
|
||||
}
|
||||
@@ -636,7 +638,7 @@ public abstract class TradingBotBase : ITradingBot
|
||||
}
|
||||
|
||||
// Synth risk monitoring (only for live trading)
|
||||
if (Config.UseSynthApi && Config.TradingType == TradingType.Futures &&
|
||||
if (Config.UseSynthApi && TradingBox.IsLiveTrading(Config.TradingType) &&
|
||||
positionForSignal.Status == PositionStatus.Filled)
|
||||
{
|
||||
await MonitorSynthRisk(signal, positionForSignal);
|
||||
@@ -644,19 +646,22 @@ public abstract class TradingBotBase : ITradingBot
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
await LogWarningAsync($"Cannot update position {positionForSignal.Identifier}: {ex.Message}, {ex.StackTrace}");
|
||||
await LogWarningAsync(
|
||||
$"Cannot update position {positionForSignal.Identifier}: {ex.Message}, {ex.StackTrace}");
|
||||
SentrySdk.CaptureException(ex);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// Virtual methods for trading mode-specific behavior
|
||||
protected virtual async Task SynchronizeWithBrokerPositions(Position internalPosition, Position positionForSignal, List<Position> brokerPositions)
|
||||
protected virtual async Task SynchronizeWithBrokerPositions(Position internalPosition, Position positionForSignal,
|
||||
List<Position> brokerPositions)
|
||||
{
|
||||
// Default implementation: do nothing (for backtest)
|
||||
}
|
||||
|
||||
protected virtual async Task HandleOrderManagementAndPositionStatus(LightSignal signal, Position internalPosition, Position positionForSignal)
|
||||
protected virtual async Task HandleOrderManagementAndPositionStatus(LightSignal signal, Position internalPosition,
|
||||
Position positionForSignal)
|
||||
{
|
||||
// Default implementation: do nothing (for backtest)
|
||||
}
|
||||
@@ -957,44 +962,44 @@ public abstract class TradingBotBase : ITradingBot
|
||||
LightSignal previousSignal, decimal lastPrice)
|
||||
{
|
||||
// Default implementation - subclasses should override
|
||||
if (Config.FlipPosition)
|
||||
{
|
||||
var isPositionInProfit = (openedPosition.ProfitAndLoss?.Realized ?? 0) > 0;
|
||||
var shouldFlip = !Config.FlipOnlyWhenInProfit || isPositionInProfit;
|
||||
if (Config.FlipPosition)
|
||||
{
|
||||
var isPositionInProfit = (openedPosition.ProfitAndLoss?.Realized ?? 0) > 0;
|
||||
var shouldFlip = !Config.FlipOnlyWhenInProfit || isPositionInProfit;
|
||||
|
||||
if (shouldFlip)
|
||||
{
|
||||
var flipReason = Config.FlipOnlyWhenInProfit
|
||||
? "current position is in profit"
|
||||
: "FlipOnlyWhenInProfit is disabled";
|
||||
if (shouldFlip)
|
||||
{
|
||||
var flipReason = Config.FlipOnlyWhenInProfit
|
||||
? "current position is in profit"
|
||||
: "FlipOnlyWhenInProfit is disabled";
|
||||
|
||||
await LogInformation(
|
||||
$"🔄 Position Flip Initiated\nFlipping position due to opposite signal\nReason: {flipReason}");
|
||||
await CloseTrade(previousSignal, openedPosition, openedPosition.Open, lastPrice, true);
|
||||
await SetPositionStatus(previousSignal.Identifier, PositionStatus.Flipped);
|
||||
var newPosition = await OpenPosition(signal);
|
||||
await LogInformation(
|
||||
$"✅ Position Flipped\nPosition: `{previousSignal.Identifier}` → `{signal.Identifier}`\nPrice: `${lastPrice}`");
|
||||
return newPosition;
|
||||
}
|
||||
else
|
||||
{
|
||||
var currentPnl = openedPosition.ProfitAndLoss?.Realized ?? 0;
|
||||
await LogInformation(
|
||||
$"💸 Flip Blocked - Not Profitable\nPosition `{previousSignal.Identifier}` PnL: `${currentPnl:F2}`\nSignal `{signal.Identifier}` will wait for profitability");
|
||||
|
||||
SetSignalStatus(signal.Identifier, SignalStatus.Expired);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
await LogInformation(
|
||||
$"🚫 Flip Disabled\nPosition already open for: `{previousSignal.Identifier}`\nFlipping disabled, new signal expired");
|
||||
SetSignalStatus(signal.Identifier, SignalStatus.Expired);
|
||||
return null;
|
||||
}
|
||||
await LogInformation(
|
||||
$"🔄 Position Flip Initiated\nFlipping position due to opposite signal\nReason: {flipReason}");
|
||||
await CloseTrade(previousSignal, openedPosition, openedPosition.Open, lastPrice, true);
|
||||
await SetPositionStatus(previousSignal.Identifier, PositionStatus.Flipped);
|
||||
var newPosition = await OpenPosition(signal);
|
||||
await LogInformation(
|
||||
$"✅ Position Flipped\nPosition: `{previousSignal.Identifier}` → `{signal.Identifier}`\nPrice: `${lastPrice}`");
|
||||
return newPosition;
|
||||
}
|
||||
else
|
||||
{
|
||||
var currentPnl = openedPosition.ProfitAndLoss?.Realized ?? 0;
|
||||
await LogInformation(
|
||||
$"💸 Flip Blocked - Not Profitable\nPosition `{previousSignal.Identifier}` PnL: `${currentPnl:F2}`\nSignal `{signal.Identifier}` will wait for profitability");
|
||||
|
||||
SetSignalStatus(signal.Identifier, SignalStatus.Expired);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
await LogInformation(
|
||||
$"🚫 Flip Disabled\nPosition already open for: `{previousSignal.Identifier}`\nFlipping disabled, new signal expired");
|
||||
SetSignalStatus(signal.Identifier, SignalStatus.Expired);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Executes the actual position opening logic.
|
||||
@@ -1003,34 +1008,34 @@ public abstract class TradingBotBase : ITradingBot
|
||||
protected virtual async Task<Position> ExecuteOpenPosition(LightSignal signal, decimal lastPrice)
|
||||
{
|
||||
// Default implementation - subclasses should override
|
||||
// Verify actual balance before opening position
|
||||
// Verify actual balance before opening position
|
||||
await VerifyAndUpdateBalanceAsync();
|
||||
|
||||
var command = new OpenPositionRequest(
|
||||
Config.AccountName,
|
||||
Config.MoneyManagement,
|
||||
signal.Direction,
|
||||
Config.Ticker,
|
||||
PositionInitiator.Bot,
|
||||
signal.Date,
|
||||
Account.User,
|
||||
Config.BotTradingBalance,
|
||||
var command = new OpenPositionRequest(
|
||||
Config.AccountName,
|
||||
Config.MoneyManagement,
|
||||
signal.Direction,
|
||||
Config.Ticker,
|
||||
PositionInitiator.Bot,
|
||||
signal.Date,
|
||||
Account.User,
|
||||
Config.BotTradingBalance,
|
||||
Config.TradingType == TradingType.BacktestFutures,
|
||||
lastPrice,
|
||||
signalIdentifier: signal.Identifier,
|
||||
lastPrice,
|
||||
signalIdentifier: signal.Identifier,
|
||||
initiatorIdentifier: Identifier,
|
||||
tradingType: Config.TradingType);
|
||||
|
||||
var position = await ServiceScopeHelpers
|
||||
.WithScopedServices<IExchangeService, IAccountService, ITradingService, Position>(
|
||||
_scopeFactory,
|
||||
async (exchangeService, accountService, tradingService) =>
|
||||
{
|
||||
return await new OpenPositionCommandHandler(exchangeService, accountService, tradingService)
|
||||
.Handle(command);
|
||||
});
|
||||
var position = await ServiceScopeHelpers
|
||||
.WithScopedServices<IExchangeService, IAccountService, ITradingService, Position>(
|
||||
_scopeFactory,
|
||||
async (exchangeService, accountService, tradingService) =>
|
||||
{
|
||||
return await new OpenPositionCommandHandler(exchangeService, accountService, tradingService)
|
||||
.Handle(command);
|
||||
});
|
||||
|
||||
return position;
|
||||
return position;
|
||||
}
|
||||
|
||||
private async Task SendPositionToCopyTrading(Position position)
|
||||
@@ -1175,7 +1180,7 @@ public abstract class TradingBotBase : ITradingBot
|
||||
var brokerHistoryReconciled = await ReconcileWithBrokerHistory(position, currentCandle);
|
||||
if (brokerHistoryReconciled && !forceMarketClose)
|
||||
{
|
||||
goto SkipCandleBasedCalculation;
|
||||
goto SkipCandleBasedCalculation;
|
||||
}
|
||||
|
||||
// Calculate position closing details using subclass-specific logic
|
||||
@@ -1217,7 +1222,7 @@ public abstract class TradingBotBase : ITradingBot
|
||||
$"Total Fees: `${position.GasFees + position.UiFees:F2}`\n" +
|
||||
$"Net P&L (after fees): `${position.ProfitAndLoss.Net:F2}`";
|
||||
|
||||
if (Config.TradingType == TradingType.Futures)
|
||||
if (TradingBox.IsLiveTrading(Config.TradingType))
|
||||
{
|
||||
await LogDebugAsync(logMessage);
|
||||
}
|
||||
@@ -1227,7 +1232,7 @@ public abstract class TradingBotBase : ITradingBot
|
||||
await SetPositionStatus(position.SignalIdentifier, PositionStatus.Finished);
|
||||
|
||||
// Update position in database with all trade changes
|
||||
if (Config.TradingType == TradingType.Futures)
|
||||
if (TradingBox.IsLiveTrading(Config.TradingType))
|
||||
{
|
||||
position.Status = PositionStatus.Finished;
|
||||
await UpdatePositionDatabase(position);
|
||||
@@ -1240,7 +1245,9 @@ public abstract class TradingBotBase : ITradingBot
|
||||
|
||||
// Update the last position closing time for cooldown period tracking
|
||||
// Only update if position was actually filled
|
||||
LastPositionClosingTime = Config.TradingType == TradingType.BacktestFutures ? currentCandle.Date : DateTime.UtcNow;
|
||||
LastPositionClosingTime = Config.TradingType == TradingType.BacktestFutures
|
||||
? currentCandle.Date
|
||||
: DateTime.UtcNow;
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -1283,7 +1290,7 @@ public abstract class TradingBotBase : ITradingBot
|
||||
|
||||
private async Task CancelAllOrders()
|
||||
{
|
||||
if (Config.TradingType == TradingType.Futures && !Config.IsForWatchingOnly)
|
||||
if (TradingBox.IsLiveTrading(Config.TradingType) && !Config.IsForWatchingOnly)
|
||||
{
|
||||
try
|
||||
{
|
||||
@@ -1465,7 +1472,7 @@ public abstract class TradingBotBase : ITradingBot
|
||||
try
|
||||
{
|
||||
// Set signal status based on configuration
|
||||
if (Config.IsForWatchingOnly || (ExecutionCount < 1 && Config.TradingType == TradingType.Futures))
|
||||
if (Config.IsForWatchingOnly || (ExecutionCount < 1 && TradingBox.IsLiveTrading(Config.TradingType)))
|
||||
{
|
||||
signal.Status = SignalStatus.Expired;
|
||||
}
|
||||
@@ -1480,7 +1487,7 @@ public abstract class TradingBotBase : ITradingBot
|
||||
$"🆔 Signal ID: `{signal.Identifier}`";
|
||||
|
||||
// Apply Synth-based signal filtering if enabled
|
||||
if (Config.UseSynthApi && Config.TradingType == TradingType.Futures && ExecutionCount > 0)
|
||||
if (Config.UseSynthApi && TradingBox.IsLiveTrading(Config.TradingType) && ExecutionCount > 0)
|
||||
{
|
||||
await ServiceScopeHelpers.WithScopedServices<ITradingService, IExchangeService>(_scopeFactory,
|
||||
async (tradingService, exchangeService) =>
|
||||
@@ -1513,7 +1520,7 @@ public abstract class TradingBotBase : ITradingBot
|
||||
|
||||
await LogInformation(signalText);
|
||||
|
||||
if (Config.IsForWatchingOnly && Config.TradingType == TradingType.Futures && ExecutionCount > 0)
|
||||
if (Config.IsForWatchingOnly && TradingBox.IsLiveTrading(Config.TradingType) && ExecutionCount > 0)
|
||||
{
|
||||
await ServiceScopeHelpers.WithScopedService<IMessengerService>(_scopeFactory, async messengerService =>
|
||||
{
|
||||
@@ -1852,7 +1859,8 @@ public abstract class TradingBotBase : ITradingBot
|
||||
// Calculate cooldown end time based on last position closing time
|
||||
var cooldownEndTime =
|
||||
TradingBox.CalculateCooldownEndTime(LastPositionClosingTime.Value, Config.Timeframe, Config.CooldownPeriod);
|
||||
var isInCooldown = (Config.TradingType == TradingType.BacktestFutures ? LastCandle.Date : DateTime.UtcNow) < cooldownEndTime;
|
||||
var isInCooldown = (Config.TradingType == TradingType.BacktestFutures ? LastCandle.Date : DateTime.UtcNow) <
|
||||
cooldownEndTime;
|
||||
|
||||
if (isInCooldown)
|
||||
{
|
||||
@@ -1937,7 +1945,8 @@ public abstract class TradingBotBase : ITradingBot
|
||||
await agentGrain.OnPositionOpenedAsync(positionOpenEvent);
|
||||
await platformGrain.OnPositionOpenAsync(positionOpenEvent);
|
||||
|
||||
await LogDebugAsync($"Sent position opened event to both grains for position {position.Identifier}");
|
||||
await LogDebugAsync(
|
||||
$"Sent position opened event to both grains for position {position.Identifier}");
|
||||
break;
|
||||
|
||||
case NotificationEventType.PositionClosed:
|
||||
@@ -1952,7 +1961,8 @@ public abstract class TradingBotBase : ITradingBot
|
||||
await agentGrain.OnPositionClosedAsync(positionClosedEvent);
|
||||
await platformGrain.OnPositionClosedAsync(positionClosedEvent);
|
||||
|
||||
await LogDebugAsync($"Sent position closed event to both grains for position {position.Identifier}");
|
||||
await LogDebugAsync(
|
||||
$"Sent position closed event to both grains for position {position.Identifier}");
|
||||
break;
|
||||
|
||||
case NotificationEventType.PositionUpdated:
|
||||
@@ -2109,7 +2119,7 @@ public abstract class TradingBotBase : ITradingBot
|
||||
|
||||
protected virtual async Task SendTradeMessageAsync(string message, bool isBadBehavior = false)
|
||||
{
|
||||
if (Config.TradingType == TradingType.Futures)
|
||||
if (TradingBox.IsLiveTrading(Config.TradingType))
|
||||
{
|
||||
var user = Account.User;
|
||||
var messageWithBotName = $"🤖 {user.AgentName} - {Config.Name}\n{message}";
|
||||
|
||||
@@ -121,7 +121,7 @@ public class CandleStoreGrain : Grain, ICandleStoreGrain, IAsyncObserver<Candle>
|
||||
await base.OnDeactivateAsync(reason, cancellationToken);
|
||||
}
|
||||
|
||||
public Task<HashSet<Candle>> GetCandlesAsync()
|
||||
public Task<IReadOnlyList<Candle>> GetCandlesAsync()
|
||||
{
|
||||
try
|
||||
{
|
||||
@@ -130,15 +130,16 @@ public class CandleStoreGrain : Grain, ICandleStoreGrain, IAsyncObserver<Candle>
|
||||
{
|
||||
_logger.LogWarning("State not initialized for grain {GrainKey}, returning empty list",
|
||||
this.GetPrimaryKeyString());
|
||||
return Task.FromResult(new HashSet<Candle>());
|
||||
return Task.FromResult<IReadOnlyList<Candle>>(new List<Candle>());
|
||||
}
|
||||
|
||||
return Task.FromResult(_state.State.Candles.ToHashSet());
|
||||
// Return a readonly wrapper to preserve order and prevent external modifications
|
||||
return Task.FromResult<IReadOnlyList<Candle>>(_state.State.Candles.AsReadOnly());
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex, "Error retrieving candles for grain {GrainKey}", this.GetPrimaryKeyString());
|
||||
return Task.FromResult(new HashSet<Candle>());
|
||||
return Task.FromResult<IReadOnlyList<Candle>>(new List<Candle>());
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -32,11 +32,11 @@ public class ScenarioRunnerGrain : Grain, IScenarioRunnerGrain
|
||||
_scopeFactory = scopeFactory;
|
||||
}
|
||||
|
||||
private async Task<HashSet<Candle>> GetCandlesAsync(TradingExchanges tradingExchange, TradingBotConfig config)
|
||||
private async Task<IReadOnlyList<Candle>> GetCandlesAsync(TradingExchanges tradingExchange, TradingBotConfig config)
|
||||
{
|
||||
try
|
||||
{
|
||||
var newCandles = await ServiceScopeHelpers.WithScopedService<IGrainFactory, HashSet<Candle>>(
|
||||
var newCandles = await ServiceScopeHelpers.WithScopedService<IGrainFactory, IReadOnlyList<Candle>>(
|
||||
_scopeFactory, async grainFactory =>
|
||||
{
|
||||
var priceGrainKey =
|
||||
@@ -66,9 +66,8 @@ public class ScenarioRunnerGrain : Grain, IScenarioRunnerGrain
|
||||
return null;
|
||||
}
|
||||
|
||||
var candlesHashSet = await GetCandlesAsync(tradingExchanges, config);
|
||||
// Convert to ordered List to preserve chronological order for indicators
|
||||
var candlesList = candlesHashSet.OrderBy(c => c.Date).ToList();
|
||||
// Get candles as ordered List (already ordered by date from CandleStoreGrain)
|
||||
var candlesList = await GetCandlesAsync(tradingExchanges, config);
|
||||
|
||||
if (candlesList.Count == 0)
|
||||
{
|
||||
|
||||
Reference in New Issue
Block a user