Fix a bit the spot trading
This commit is contained in:
@@ -189,12 +189,6 @@ public class BacktestFuturesBot : TradingBotBase, ITradingBot
|
||||
await AddSignal(backtestSignal);
|
||||
}
|
||||
|
||||
protected override async Task<decimal> GetLastPriceForPositionOpeningAsync()
|
||||
{
|
||||
// For backtest, use LastCandle close price
|
||||
return LastCandle?.Close ?? 0;
|
||||
}
|
||||
|
||||
protected override async Task<bool> CanOpenPosition(LightSignal signal)
|
||||
{
|
||||
// Backtest-specific logic: only check cooldown and loss streak
|
||||
|
||||
@@ -193,12 +193,6 @@ public class BacktestSpotBot : TradingBotBase, ITradingBot
|
||||
await AddSignal(backtestSignal);
|
||||
}
|
||||
|
||||
protected override async Task<decimal> GetLastPriceForPositionOpeningAsync()
|
||||
{
|
||||
// For backtest, use LastCandle close price
|
||||
return LastCandle?.Close ?? 0;
|
||||
}
|
||||
|
||||
protected override async Task<bool> CanOpenPosition(LightSignal signal)
|
||||
{
|
||||
// For spot trading, only LONG signals can open positions
|
||||
|
||||
@@ -1133,13 +1133,6 @@ public class FuturesBot : TradingBotBase, ITradingBot
|
||||
}
|
||||
}
|
||||
|
||||
protected override async Task<decimal> GetLastPriceForPositionOpeningAsync()
|
||||
{
|
||||
// For live trading, get current price from exchange
|
||||
return await ServiceScopeHelpers.WithScopedService<IExchangeService, decimal>(_scopeFactory,
|
||||
async exchangeService => { return await exchangeService.GetCurrentPrice(Account, Config.Ticker); });
|
||||
}
|
||||
|
||||
protected override async Task<Position> HandleFlipPosition(LightSignal signal, Position openedPosition,
|
||||
LightSignal previousSignal, decimal lastPrice)
|
||||
{
|
||||
|
||||
@@ -114,7 +114,8 @@ public class SpotBot : TradingBotBase, ITradingBot
|
||||
// Calculate PnL based on current token balance and current price
|
||||
// For LONG spot position: PnL = (currentPrice - openPrice) * tokenBalance
|
||||
var openPrice = position.Open.Price;
|
||||
var pnlBeforeFees = TradingBox.CalculatePnL(openPrice, currentPrice, tokenBalance.Amount, 1, TradeDirection.Long);
|
||||
var pnlBeforeFees =
|
||||
TradingBox.CalculatePnL(openPrice, currentPrice, tokenBalance.Amount, 1, TradeDirection.Long);
|
||||
|
||||
// Update position PnL
|
||||
UpdatePositionPnl(position.Identifier, pnlBeforeFees);
|
||||
@@ -162,13 +163,9 @@ public class SpotBot : TradingBotBase, ITradingBot
|
||||
// For spot trading, check token balances to verify position status
|
||||
try
|
||||
{
|
||||
var balances = await ServiceScopeHelpers.WithScopedService<IExchangeService, List<Balance>>(
|
||||
var tokenBalance = await ServiceScopeHelpers.WithScopedService<IExchangeService, Balance?>(
|
||||
_scopeFactory,
|
||||
async exchangeService => { return await exchangeService.GetBalances(Account); });
|
||||
|
||||
var tickerString = Config.Ticker.ToString();
|
||||
var tokenBalance = balances.FirstOrDefault(b =>
|
||||
b.TokenName?.Equals(tickerString, StringComparison.OrdinalIgnoreCase) == true);
|
||||
async exchangeService => await exchangeService.GetBalance(Account, Config.Ticker));
|
||||
|
||||
var hasOpenPosition = Positions.Values.Any(p => p.IsOpen());
|
||||
|
||||
@@ -195,7 +192,7 @@ public class SpotBot : TradingBotBase, ITradingBot
|
||||
return false; // Don't allow opening new position until resolved
|
||||
}
|
||||
}
|
||||
else if (tokenBalance != null && tokenBalance.Amount > 0)
|
||||
else if (tokenBalance != null && tokenBalance.Value > 1m)
|
||||
{
|
||||
// We have a token balance but no internal position - orphaned position
|
||||
await LogWarningAsync(
|
||||
@@ -235,26 +232,21 @@ public class SpotBot : TradingBotBase, ITradingBot
|
||||
// For spot trading, fetch token balance directly and verify/match with internal position
|
||||
try
|
||||
{
|
||||
var balances = await ServiceScopeHelpers.WithScopedService<IExchangeService, List<Balance>>(
|
||||
var tokenBalance = await ServiceScopeHelpers.WithScopedService<IExchangeService, Balance?>(
|
||||
_scopeFactory,
|
||||
async exchangeService => { return await exchangeService.GetBalances(Account); });
|
||||
|
||||
// Find the token balance for the ticker
|
||||
var tickerString = Config.Ticker.ToString();
|
||||
var tokenBalance = balances.FirstOrDefault(b =>
|
||||
b.TokenName?.Equals(tickerString, StringComparison.OrdinalIgnoreCase) == true);
|
||||
async exchangeService => await exchangeService.GetBalance(Account, Config.Ticker));
|
||||
|
||||
if (tokenBalance != null && tokenBalance.Amount > 0)
|
||||
{
|
||||
// Verify that the token balance matches the position amount with 0.1% tolerance
|
||||
var positionQuantity = internalPosition.Open.Quantity;
|
||||
var tokenBalanceAmount = tokenBalance.Amount;
|
||||
|
||||
|
||||
if (positionQuantity > 0)
|
||||
{
|
||||
var tolerance = positionQuantity * 0.001m; // 0.1% tolerance
|
||||
var tolerance = positionQuantity * 0.003m; // 0.3% tolerance
|
||||
var difference = Math.Abs(tokenBalanceAmount - positionQuantity);
|
||||
|
||||
|
||||
if (difference > tolerance)
|
||||
{
|
||||
await LogWarningAsync(
|
||||
@@ -301,13 +293,17 @@ public class SpotBot : TradingBotBase, ITradingBot
|
||||
{
|
||||
currentPrice = await ServiceScopeHelpers.WithScopedService<IExchangeService, decimal>(
|
||||
_scopeFactory,
|
||||
async exchangeService => { return await exchangeService.GetCurrentPrice(Account, Config.Ticker); });
|
||||
async exchangeService =>
|
||||
{
|
||||
return await exchangeService.GetCurrentPrice(Account, Config.Ticker);
|
||||
});
|
||||
}
|
||||
|
||||
if (currentPrice > 0)
|
||||
{
|
||||
var openPrice = internalPosition.Open.Price;
|
||||
var pnlBeforeFees = TradingBox.CalculatePnL(openPrice, currentPrice, actualTokenBalance, 1, TradeDirection.Long);
|
||||
var pnlBeforeFees = TradingBox.CalculatePnL(openPrice, currentPrice, actualTokenBalance, 1,
|
||||
TradeDirection.Long);
|
||||
UpdatePositionPnl(positionForSignal.Identifier, pnlBeforeFees);
|
||||
|
||||
var totalFees = internalPosition.GasFees + internalPosition.UiFees;
|
||||
@@ -584,13 +580,6 @@ public class SpotBot : TradingBotBase, ITradingBot
|
||||
return (closingPrice, pnlCalculated);
|
||||
}
|
||||
|
||||
protected override async Task<decimal> GetLastPriceForPositionOpeningAsync()
|
||||
{
|
||||
// For live trading, get current price from exchange
|
||||
return await ServiceScopeHelpers.WithScopedService<IExchangeService, decimal>(_scopeFactory,
|
||||
async exchangeService => { return await exchangeService.GetCurrentPrice(Account, Config.Ticker); });
|
||||
}
|
||||
|
||||
protected override async Task UpdateSignalsCore(IReadOnlyList<Candle> candles,
|
||||
Dictionary<IndicatorType, IndicatorsResultBase> preCalculatedIndicatorValues = null)
|
||||
{
|
||||
@@ -690,7 +679,8 @@ public class SpotBot : TradingBotBase, ITradingBot
|
||||
// Spot-specific position opening: includes balance verification and live exchange calls
|
||||
if (signal.Direction != TradeDirection.Long)
|
||||
{
|
||||
throw new InvalidOperationException($"Only LONG signals can open positions in spot trading. Received: {signal.Direction}");
|
||||
throw new InvalidOperationException(
|
||||
$"Only LONG signals can open positions in spot trading. Received: {signal.Direction}");
|
||||
}
|
||||
|
||||
if (Account == null || Account.User == null)
|
||||
@@ -776,5 +766,4 @@ public class SpotBot : TradingBotBase, ITradingBot
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
using System.Diagnostics;
|
||||
using System.Diagnostics;
|
||||
using Managing.Application.Abstractions;
|
||||
using Managing.Application.Abstractions.Grains;
|
||||
using Managing.Application.Abstractions.Services;
|
||||
@@ -852,10 +852,15 @@ public abstract class TradingBotBase : ITradingBot
|
||||
async tradingService => { await tradingService.UpdatePositionAsync(position); });
|
||||
}
|
||||
|
||||
protected virtual async Task<decimal> GetLastPriceForPositionOpeningAsync()
|
||||
protected async Task<decimal> GetLastPriceForPositionOpeningAsync()
|
||||
{
|
||||
// Default implementation - subclasses should override
|
||||
return 0;
|
||||
if (TradingBox.IsLiveTrading(Config.TradingType))
|
||||
{
|
||||
return await ServiceScopeHelpers.WithScopedService<IExchangeService, decimal>(_scopeFactory,
|
||||
async exchangeService => await exchangeService.GetCurrentPrice(Account, Config.Ticker));
|
||||
}
|
||||
|
||||
return LastCandle?.Close ?? 0;
|
||||
}
|
||||
|
||||
protected async Task<Position> OpenPosition(LightSignal signal)
|
||||
|
||||
@@ -29,32 +29,11 @@ public class CloseSpotPositionCommandHandler(
|
||||
? TradeDirection.Short
|
||||
: TradeDirection.Long;
|
||||
|
||||
// For spot trading, determine swap direction for closing
|
||||
// Long position: Swap Token -> USDC (sell token for USDC)
|
||||
// Short position: Swap USDC -> Token (buy token with USDC)
|
||||
Ticker fromTicker;
|
||||
Ticker toTicker;
|
||||
double swapAmount;
|
||||
|
||||
if (request.Position.OriginDirection == TradeDirection.Long)
|
||||
{
|
||||
fromTicker = request.Position.Ticker;
|
||||
toTicker = Ticker.USDC;
|
||||
swapAmount = (double)request.Position.Open.Quantity;
|
||||
}
|
||||
else
|
||||
{
|
||||
fromTicker = Ticker.USDC;
|
||||
toTicker = request.Position.Ticker;
|
||||
// For short, we need to calculate how much USDC to swap back
|
||||
// This should be the original amount + profit/loss
|
||||
var originalAmount = request.Position.Open.Price * request.Position.Open.Quantity;
|
||||
swapAmount = (double)originalAmount;
|
||||
}
|
||||
|
||||
// For backtest/paper trading, simulate the swap without calling the exchange
|
||||
SwapInfos swapResult;
|
||||
if (request.Position.TradingType == TradingType.BacktestSpot)
|
||||
var isForBacktest = request.Position.TradingType == TradingType.BacktestSpot;
|
||||
|
||||
if (isForBacktest)
|
||||
{
|
||||
// Simulate successful swap for backtest
|
||||
swapResult = new SwapInfos
|
||||
@@ -71,9 +50,9 @@ public class CloseSpotPositionCommandHandler(
|
||||
swapResult = await tradingService.SwapGmxTokensAsync(
|
||||
request.Position.User,
|
||||
account.Name,
|
||||
fromTicker,
|
||||
toTicker,
|
||||
swapAmount,
|
||||
request.Position.Ticker,
|
||||
Ticker.USDC,
|
||||
(double)request.Position.Open.Quantity,
|
||||
"market",
|
||||
null,
|
||||
0.5);
|
||||
@@ -81,7 +60,8 @@ public class CloseSpotPositionCommandHandler(
|
||||
|
||||
if (!swapResult.Success)
|
||||
{
|
||||
throw new InvalidOperationException($"Failed to close spot position: {swapResult.Error ?? swapResult.Message}");
|
||||
throw new InvalidOperationException(
|
||||
$"Failed to close spot position: {swapResult.Error ?? swapResult.Message}");
|
||||
}
|
||||
|
||||
// Build the closing trade directly for backtest (no exchange call needed)
|
||||
@@ -107,7 +87,10 @@ public class CloseSpotPositionCommandHandler(
|
||||
request.Position.AddUiFees(closingUiFees);
|
||||
request.Position.AddGasFees(Constants.GMX.Config.GasFeePerTransaction);
|
||||
|
||||
// For backtest, skip database update
|
||||
if (!isForBacktest)
|
||||
{
|
||||
await tradingService.UpdatePositionAsync(request.Position);
|
||||
}
|
||||
|
||||
return request.Position;
|
||||
}
|
||||
@@ -120,5 +103,4 @@ public class CloseSpotPositionCommandHandler(
|
||||
throw;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
Reference in New Issue
Block a user