Add isLiveTrading helper to fix bug
This commit is contained in:
@@ -14,6 +14,7 @@ using Managing.Domain.Bots;
|
|||||||
using Managing.Domain.Indicators;
|
using Managing.Domain.Indicators;
|
||||||
using Managing.Domain.MoneyManagements;
|
using Managing.Domain.MoneyManagements;
|
||||||
using Managing.Domain.Scenarios;
|
using Managing.Domain.Scenarios;
|
||||||
|
using Managing.Domain.Shared.Helpers;
|
||||||
using Managing.Domain.Strategies;
|
using Managing.Domain.Strategies;
|
||||||
using Managing.Domain.Trades;
|
using Managing.Domain.Trades;
|
||||||
using Managing.Domain.Users;
|
using Managing.Domain.Users;
|
||||||
@@ -955,13 +956,6 @@ public class BotController : BaseController
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
var tradingType = request.Config.TradingType switch
|
|
||||||
{
|
|
||||||
TradingType.BacktestFutures => TradingType.Futures,
|
|
||||||
TradingType.BacktestSpot => TradingType.Spot,
|
|
||||||
_ => TradingType.Futures
|
|
||||||
};
|
|
||||||
|
|
||||||
// Map the request to the full TradingBotConfig
|
// Map the request to the full TradingBotConfig
|
||||||
var config = new TradingBotConfig
|
var config = new TradingBotConfig
|
||||||
{
|
{
|
||||||
@@ -985,7 +979,7 @@ public class BotController : BaseController
|
|||||||
// Set computed/default properties
|
// Set computed/default properties
|
||||||
FlipPosition = request.Config.FlipPosition,
|
FlipPosition = request.Config.FlipPosition,
|
||||||
Name = request.Config.Name,
|
Name = request.Config.Name,
|
||||||
TradingType = tradingType
|
TradingType = TradingBox.GetLiveTradingType(request.Config.TradingType)
|
||||||
};
|
};
|
||||||
|
|
||||||
return (config, user);
|
return (config, user);
|
||||||
|
|||||||
@@ -13,8 +13,8 @@ public interface ICandleStoreGrain : IGrainWithStringKey
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets the current list of historical candles (up to 500 most recent)
|
/// Gets the current list of historical candles (up to 500 most recent)
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <returns>List of candles ordered by date</returns>
|
/// <returns>Read-only list of candles ordered by date</returns>
|
||||||
Task<HashSet<Candle>> GetCandlesAsync();
|
Task<IReadOnlyList<Candle>> GetCandlesAsync();
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets the X latest candles from the store
|
/// Gets the X latest candles from the store
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
|||||||
@@ -68,7 +68,7 @@ public abstract class TradingBotBase : ITradingBot
|
|||||||
|
|
||||||
public virtual async Task Start(BotStatus previousStatus)
|
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
|
// Start async initialization in the background without blocking
|
||||||
try
|
try
|
||||||
@@ -235,7 +235,7 @@ public abstract class TradingBotBase : ITradingBot
|
|||||||
await ManagePositions();
|
await ManagePositions();
|
||||||
|
|
||||||
UpdateWalletBalances();
|
UpdateWalletBalances();
|
||||||
if (Config.TradingType == TradingType.Futures)
|
if (TradingBox.IsLiveTrading(Config.TradingType))
|
||||||
{
|
{
|
||||||
ExecutionCount++;
|
ExecutionCount++;
|
||||||
|
|
||||||
@@ -436,7 +436,9 @@ public abstract class TradingBotBase : ITradingBot
|
|||||||
|
|
||||||
protected void UpdateWalletBalances()
|
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)
|
if (WalletBalances.Count == 0)
|
||||||
{
|
{
|
||||||
@@ -636,7 +638,7 @@ public abstract class TradingBotBase : ITradingBot
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Synth risk monitoring (only for live trading)
|
// 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)
|
positionForSignal.Status == PositionStatus.Filled)
|
||||||
{
|
{
|
||||||
await MonitorSynthRisk(signal, positionForSignal);
|
await MonitorSynthRisk(signal, positionForSignal);
|
||||||
@@ -644,19 +646,22 @@ public abstract class TradingBotBase : ITradingBot
|
|||||||
}
|
}
|
||||||
catch (Exception ex)
|
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);
|
SentrySdk.CaptureException(ex);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Virtual methods for trading mode-specific behavior
|
// 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)
|
// 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)
|
// Default implementation: do nothing (for backtest)
|
||||||
}
|
}
|
||||||
@@ -1217,7 +1222,7 @@ public abstract class TradingBotBase : ITradingBot
|
|||||||
$"Total Fees: `${position.GasFees + position.UiFees:F2}`\n" +
|
$"Total Fees: `${position.GasFees + position.UiFees:F2}`\n" +
|
||||||
$"Net P&L (after fees): `${position.ProfitAndLoss.Net:F2}`";
|
$"Net P&L (after fees): `${position.ProfitAndLoss.Net:F2}`";
|
||||||
|
|
||||||
if (Config.TradingType == TradingType.Futures)
|
if (TradingBox.IsLiveTrading(Config.TradingType))
|
||||||
{
|
{
|
||||||
await LogDebugAsync(logMessage);
|
await LogDebugAsync(logMessage);
|
||||||
}
|
}
|
||||||
@@ -1227,7 +1232,7 @@ public abstract class TradingBotBase : ITradingBot
|
|||||||
await SetPositionStatus(position.SignalIdentifier, PositionStatus.Finished);
|
await SetPositionStatus(position.SignalIdentifier, PositionStatus.Finished);
|
||||||
|
|
||||||
// Update position in database with all trade changes
|
// Update position in database with all trade changes
|
||||||
if (Config.TradingType == TradingType.Futures)
|
if (TradingBox.IsLiveTrading(Config.TradingType))
|
||||||
{
|
{
|
||||||
position.Status = PositionStatus.Finished;
|
position.Status = PositionStatus.Finished;
|
||||||
await UpdatePositionDatabase(position);
|
await UpdatePositionDatabase(position);
|
||||||
@@ -1240,7 +1245,9 @@ public abstract class TradingBotBase : ITradingBot
|
|||||||
|
|
||||||
// Update the last position closing time for cooldown period tracking
|
// Update the last position closing time for cooldown period tracking
|
||||||
// Only update if position was actually filled
|
// 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
|
else
|
||||||
{
|
{
|
||||||
@@ -1283,7 +1290,7 @@ public abstract class TradingBotBase : ITradingBot
|
|||||||
|
|
||||||
private async Task CancelAllOrders()
|
private async Task CancelAllOrders()
|
||||||
{
|
{
|
||||||
if (Config.TradingType == TradingType.Futures && !Config.IsForWatchingOnly)
|
if (TradingBox.IsLiveTrading(Config.TradingType) && !Config.IsForWatchingOnly)
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
@@ -1465,7 +1472,7 @@ public abstract class TradingBotBase : ITradingBot
|
|||||||
try
|
try
|
||||||
{
|
{
|
||||||
// Set signal status based on configuration
|
// 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;
|
signal.Status = SignalStatus.Expired;
|
||||||
}
|
}
|
||||||
@@ -1480,7 +1487,7 @@ public abstract class TradingBotBase : ITradingBot
|
|||||||
$"🆔 Signal ID: `{signal.Identifier}`";
|
$"🆔 Signal ID: `{signal.Identifier}`";
|
||||||
|
|
||||||
// Apply Synth-based signal filtering if enabled
|
// 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,
|
await ServiceScopeHelpers.WithScopedServices<ITradingService, IExchangeService>(_scopeFactory,
|
||||||
async (tradingService, exchangeService) =>
|
async (tradingService, exchangeService) =>
|
||||||
@@ -1513,7 +1520,7 @@ public abstract class TradingBotBase : ITradingBot
|
|||||||
|
|
||||||
await LogInformation(signalText);
|
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 =>
|
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
|
// Calculate cooldown end time based on last position closing time
|
||||||
var cooldownEndTime =
|
var cooldownEndTime =
|
||||||
TradingBox.CalculateCooldownEndTime(LastPositionClosingTime.Value, Config.Timeframe, Config.CooldownPeriod);
|
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)
|
if (isInCooldown)
|
||||||
{
|
{
|
||||||
@@ -1937,7 +1945,8 @@ public abstract class TradingBotBase : ITradingBot
|
|||||||
await agentGrain.OnPositionOpenedAsync(positionOpenEvent);
|
await agentGrain.OnPositionOpenedAsync(positionOpenEvent);
|
||||||
await platformGrain.OnPositionOpenAsync(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;
|
break;
|
||||||
|
|
||||||
case NotificationEventType.PositionClosed:
|
case NotificationEventType.PositionClosed:
|
||||||
@@ -1952,7 +1961,8 @@ public abstract class TradingBotBase : ITradingBot
|
|||||||
await agentGrain.OnPositionClosedAsync(positionClosedEvent);
|
await agentGrain.OnPositionClosedAsync(positionClosedEvent);
|
||||||
await platformGrain.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;
|
break;
|
||||||
|
|
||||||
case NotificationEventType.PositionUpdated:
|
case NotificationEventType.PositionUpdated:
|
||||||
@@ -2109,7 +2119,7 @@ public abstract class TradingBotBase : ITradingBot
|
|||||||
|
|
||||||
protected virtual async Task SendTradeMessageAsync(string message, bool isBadBehavior = false)
|
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 user = Account.User;
|
||||||
var messageWithBotName = $"🤖 {user.AgentName} - {Config.Name}\n{message}";
|
var messageWithBotName = $"🤖 {user.AgentName} - {Config.Name}\n{message}";
|
||||||
|
|||||||
@@ -121,7 +121,7 @@ public class CandleStoreGrain : Grain, ICandleStoreGrain, IAsyncObserver<Candle>
|
|||||||
await base.OnDeactivateAsync(reason, cancellationToken);
|
await base.OnDeactivateAsync(reason, cancellationToken);
|
||||||
}
|
}
|
||||||
|
|
||||||
public Task<HashSet<Candle>> GetCandlesAsync()
|
public Task<IReadOnlyList<Candle>> GetCandlesAsync()
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
@@ -130,15 +130,16 @@ public class CandleStoreGrain : Grain, ICandleStoreGrain, IAsyncObserver<Candle>
|
|||||||
{
|
{
|
||||||
_logger.LogWarning("State not initialized for grain {GrainKey}, returning empty list",
|
_logger.LogWarning("State not initialized for grain {GrainKey}, returning empty list",
|
||||||
this.GetPrimaryKeyString());
|
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)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
_logger.LogError(ex, "Error retrieving candles for grain {GrainKey}", this.GetPrimaryKeyString());
|
_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;
|
_scopeFactory = scopeFactory;
|
||||||
}
|
}
|
||||||
|
|
||||||
private async Task<HashSet<Candle>> GetCandlesAsync(TradingExchanges tradingExchange, TradingBotConfig config)
|
private async Task<IReadOnlyList<Candle>> GetCandlesAsync(TradingExchanges tradingExchange, TradingBotConfig config)
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
var newCandles = await ServiceScopeHelpers.WithScopedService<IGrainFactory, HashSet<Candle>>(
|
var newCandles = await ServiceScopeHelpers.WithScopedService<IGrainFactory, IReadOnlyList<Candle>>(
|
||||||
_scopeFactory, async grainFactory =>
|
_scopeFactory, async grainFactory =>
|
||||||
{
|
{
|
||||||
var priceGrainKey =
|
var priceGrainKey =
|
||||||
@@ -66,9 +66,8 @@ public class ScenarioRunnerGrain : Grain, IScenarioRunnerGrain
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
var candlesHashSet = await GetCandlesAsync(tradingExchanges, config);
|
// Get candles as ordered List (already ordered by date from CandleStoreGrain)
|
||||||
// Convert to ordered List to preserve chronological order for indicators
|
var candlesList = await GetCandlesAsync(tradingExchanges, config);
|
||||||
var candlesList = candlesHashSet.OrderBy(c => c.Date).ToList();
|
|
||||||
|
|
||||||
if (candlesList.Count == 0)
|
if (candlesList.Count == 0)
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -66,7 +66,7 @@ public class Backtest
|
|||||||
Timeframe = Config.Timeframe,
|
Timeframe = Config.Timeframe,
|
||||||
IsForWatchingOnly = false, // Always start as active bot
|
IsForWatchingOnly = false, // Always start as active bot
|
||||||
BotTradingBalance = initialTradingBalance,
|
BotTradingBalance = initialTradingBalance,
|
||||||
TradingType = TradingType.Futures, // Always Futures for live bots
|
TradingType = Config.TradingType,
|
||||||
CooldownPeriod = Config.CooldownPeriod,
|
CooldownPeriod = Config.CooldownPeriod,
|
||||||
MaxLossStreak = Config.MaxLossStreak,
|
MaxLossStreak = Config.MaxLossStreak,
|
||||||
MaxPositionTimeHours = Config.MaxPositionTimeHours, // Properly copy nullable value
|
MaxPositionTimeHours = Config.MaxPositionTimeHours, // Properly copy nullable value
|
||||||
|
|||||||
@@ -69,12 +69,6 @@ public static class TradingBox
|
|||||||
preCalculatedIndicatorValues);
|
preCalculatedIndicatorValues);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static LightSignal GetSignal(IReadOnlyList<Candle> newCandles, LightScenario lightScenario,
|
|
||||||
Dictionary<string, LightSignal> previousSignal, IndicatorComboConfig config, int? loopbackPeriod = 1)
|
|
||||||
{
|
|
||||||
return GetSignal(newCandles, lightScenario, previousSignal, config, loopbackPeriod, null);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static LightSignal GetSignal(IReadOnlyList<Candle> newCandles, LightScenario lightScenario,
|
public static LightSignal GetSignal(IReadOnlyList<Candle> newCandles, LightScenario lightScenario,
|
||||||
Dictionary<string, LightSignal> previousSignal, IndicatorComboConfig config, int? loopbackPeriod,
|
Dictionary<string, LightSignal> previousSignal, IndicatorComboConfig config, int? loopbackPeriod,
|
||||||
Dictionary<IndicatorType, IndicatorsResultBase> preCalculatedIndicatorValues)
|
Dictionary<IndicatorType, IndicatorsResultBase> preCalculatedIndicatorValues)
|
||||||
@@ -1343,4 +1337,23 @@ public static class TradingBox
|
|||||||
}
|
}
|
||||||
|
|
||||||
#endregion
|
#endregion
|
||||||
|
|
||||||
|
public static bool IsLiveTrading(TradingType tradingType)
|
||||||
|
{
|
||||||
|
return tradingType switch
|
||||||
|
{
|
||||||
|
TradingType.Futures => true,
|
||||||
|
TradingType.Spot => true,
|
||||||
|
_ => false
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
public static TradingType GetLiveTradingType(TradingType tradingType)
|
||||||
|
{
|
||||||
|
return tradingType switch {
|
||||||
|
TradingType.BacktestFutures => TradingType.Futures,
|
||||||
|
TradingType.BacktestSpot => TradingType.Spot,
|
||||||
|
_ => throw new InvalidOperationException($"Unsupported TradingType for live trading: {tradingType}")
|
||||||
|
};
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@@ -13,9 +13,9 @@ describe('swap tokens implementation', () => {
|
|||||||
console.log('Account', sdk.account)
|
console.log('Account', sdk.account)
|
||||||
const result = await swapGmxTokensImpl(
|
const result = await swapGmxTokensImpl(
|
||||||
sdk,
|
sdk,
|
||||||
Ticker.ETH,
|
Ticker.BTC,
|
||||||
Ticker.USDC,
|
Ticker.USDC,
|
||||||
0.0042
|
0.00006733
|
||||||
)
|
)
|
||||||
|
|
||||||
assert.strictEqual(typeof result, 'string')
|
assert.strictEqual(typeof result, 'string')
|
||||||
|
|||||||
Reference in New Issue
Block a user