Add user to position + fix few things
This commit is contained in:
BIN
src/.DS_Store
vendored
BIN
src/.DS_Store
vendored
Binary file not shown.
@@ -151,17 +151,17 @@ builder.Services.AddSwaggerGen(options =>
|
|||||||
|
|
||||||
builder.WebHost.SetupDiscordBot();
|
builder.WebHost.SetupDiscordBot();
|
||||||
// builder.Services.AddHostedService<FeeWorker>();
|
// builder.Services.AddHostedService<FeeWorker>();
|
||||||
// builder.Services.AddHostedService<PositionManagerWorker>();
|
builder.Services.AddHostedService<PositionManagerWorker>();
|
||||||
// builder.Services.AddHostedService<PositionFetcher>();
|
// builder.Services.AddHostedService<PositionFetcher>();
|
||||||
// builder.Services.AddHostedService<PricesFiveMinutesWorker>();
|
// builder.Services.AddHostedService<PricesFiveMinutesWorker>();
|
||||||
builder.Services.AddHostedService<PricesFifteenMinutesWorker>();
|
// builder.Services.AddHostedService<PricesFifteenMinutesWorker>();
|
||||||
builder.Services.AddHostedService<PricesOneHourWorker>();
|
// builder.Services.AddHostedService<PricesOneHourWorker>();
|
||||||
builder.Services.AddHostedService<PricesFourHoursWorker>();
|
// builder.Services.AddHostedService<PricesFourHoursWorker>();
|
||||||
builder.Services.AddHostedService<PricesOneDayWorker>();
|
// builder.Services.AddHostedService<PricesOneDayWorker>();
|
||||||
// builder.Services.AddHostedService<SpotlightWorker>();
|
// // builder.Services.AddHostedService<SpotlightWorker>();
|
||||||
// builder.Services.AddHostedService<TraderWatcher>();
|
// // builder.Services.AddHostedService<TraderWatcher>();
|
||||||
builder.Services.AddHostedService<LeaderboardWorker>();
|
// builder.Services.AddHostedService<LeaderboardWorker>();
|
||||||
builder.Services.AddHostedService<FundingRatesWatcher>();
|
// builder.Services.AddHostedService<FundingRatesWatcher>();
|
||||||
|
|
||||||
// App
|
// App
|
||||||
var app = builder.Build();
|
var app = builder.Build();
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ using Managing.Application.Abstractions.Services;
|
|||||||
using Managing.Application.Workers;
|
using Managing.Application.Workers;
|
||||||
using Managing.Application.Workers.Abstractions;
|
using Managing.Application.Workers.Abstractions;
|
||||||
using Managing.Domain.Accounts;
|
using Managing.Domain.Accounts;
|
||||||
|
using Managing.Domain.Shared.Helpers;
|
||||||
using Managing.Domain.Trades;
|
using Managing.Domain.Trades;
|
||||||
using Newtonsoft.Json;
|
using Newtonsoft.Json;
|
||||||
using static Managing.Common.Enums;
|
using static Managing.Common.Enums;
|
||||||
@@ -58,51 +59,84 @@ public class PositionManagerWorker : BaseWorker<PositionManagerWorker>
|
|||||||
position.Status = PositionStatus.Updating;
|
position.Status = PositionStatus.Updating;
|
||||||
_tradingService.UpdatePosition(position);
|
_tradingService.UpdatePosition(position);
|
||||||
|
|
||||||
_logger.LogDebug("Processing risk orders for {Direction} position opened at {OpenDate}",
|
_logger.LogDebug("Verifying position on exchange for {Direction} position opened at {OpenDate}",
|
||||||
position.OriginDirection, position.Date.ToString("o"));
|
position.OriginDirection, position.Date.ToString("o"));
|
||||||
|
|
||||||
var account = await _accountService.GetAccount(position.AccountName, false, false);
|
var account = await _accountService.GetAccount(position.AccountName, false, false);
|
||||||
var success = true;
|
|
||||||
|
|
||||||
// Process and update trades
|
// Get positions directly from broker
|
||||||
var updatedSl = await ProcessTrade(account, position.StopLoss, "SL", async () =>
|
var brokerPositions = await _exchangeService.GetBrokerPositions(account);
|
||||||
await _exchangeService.OpenStopLoss(account, position.Ticker, position.OriginDirection,
|
var exchangePosition = brokerPositions.FirstOrDefault(p =>
|
||||||
position.StopLoss.Price, position.StopLoss.Quantity, false, DateTime.UtcNow));
|
p.Ticker == position.Ticker &&
|
||||||
|
p.OriginDirection == position.OriginDirection);
|
||||||
|
|
||||||
|
if (exchangePosition == null)
|
||||||
|
{
|
||||||
|
_logger.LogWarning("Position not found on exchange - marking as canceled");
|
||||||
|
position.Status = PositionStatus.Canceled;
|
||||||
|
_tradingService.UpdatePosition(position);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update with exchange data if available
|
||||||
|
if (exchangePosition.StopLoss != null)
|
||||||
|
{
|
||||||
|
_logger.LogInformation("Stop Loss found on exchange - ID: {OrderId}", exchangePosition.StopLoss.ExchangeOrderId);
|
||||||
|
position.StopLoss = exchangePosition.StopLoss;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
_logger.LogWarning("Stop Loss not found on exchange - creating new SL order");
|
||||||
|
var updatedSl = await _exchangeService.OpenStopLoss(account, position.Ticker, position.OriginDirection,
|
||||||
|
position.StopLoss.Price, position.StopLoss.Quantity, false, DateTime.UtcNow);
|
||||||
|
|
||||||
if (updatedSl != null)
|
if (updatedSl != null)
|
||||||
{
|
{
|
||||||
position.StopLoss = updatedSl;
|
position.StopLoss = updatedSl;
|
||||||
success &= updatedSl.Status.IsActive();
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
var updatedTp1 = await ProcessTrade(account, position.TakeProfit1, "TP1", async () =>
|
if (exchangePosition.TakeProfit1 != null)
|
||||||
await _exchangeService.OpenTakeProfit(account, position.Ticker, position.OriginDirection,
|
{
|
||||||
position.TakeProfit1.Price, position.TakeProfit1.Quantity, false, DateTime.UtcNow));
|
_logger.LogInformation("Take Profit found on exchange - ID: {OrderId}", exchangePosition.TakeProfit1.ExchangeOrderId);
|
||||||
|
position.TakeProfit1 = exchangePosition.TakeProfit1;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
_logger.LogWarning("Take Profit not found on exchange - creating new TP order");
|
||||||
|
var updatedTp1 = await _exchangeService.OpenTakeProfit(account, position.Ticker, position.OriginDirection,
|
||||||
|
position.TakeProfit1.Price, position.TakeProfit1.Quantity, false, DateTime.UtcNow);
|
||||||
|
|
||||||
if (updatedTp1 != null)
|
if (updatedTp1 != null)
|
||||||
{
|
{
|
||||||
position.TakeProfit1 = updatedTp1;
|
position.TakeProfit1 = updatedTp1;
|
||||||
success &= updatedTp1.Status.IsActive();
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Trade? updatedTp2 = null;
|
// Handle TP2 if it exists
|
||||||
if (position.TakeProfit2 != null)
|
if (position.TakeProfit2 != null)
|
||||||
{
|
{
|
||||||
updatedTp2 = await ProcessTrade(account, position.TakeProfit2, "TP2", async () =>
|
if (exchangePosition.TakeProfit2 != null)
|
||||||
await _exchangeService.OpenTakeProfit(account, position.Ticker, position.OriginDirection,
|
{
|
||||||
position.TakeProfit2.Price, position.TakeProfit2.Quantity, false, DateTime.UtcNow));
|
_logger.LogInformation("Take Profit 2 found on exchange - ID: {OrderId}", exchangePosition.TakeProfit2.ExchangeOrderId);
|
||||||
|
position.TakeProfit2 = exchangePosition.TakeProfit2;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
_logger.LogWarning("Take Profit 2 not found on exchange - creating new TP2 order");
|
||||||
|
var updatedTp2 = await _exchangeService.OpenTakeProfit(account, position.Ticker, position.OriginDirection,
|
||||||
|
position.TakeProfit2.Price, position.TakeProfit2.Quantity, false, DateTime.UtcNow);
|
||||||
|
|
||||||
if (updatedTp2 != null)
|
if (updatedTp2 != null)
|
||||||
{
|
{
|
||||||
position.TakeProfit2 = updatedTp2;
|
position.TakeProfit2 = updatedTp2;
|
||||||
success &= updatedTp2.Status.IsActive() || updatedTp2.Status == TradeStatus.Cancelled;
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Update position status based on trade states
|
// Update position status based on verification results
|
||||||
position.Status = success && AllTradesActive(position)
|
var success = AllTradesActive(position);
|
||||||
? PositionStatus.Filled
|
position.Status = success ? PositionStatus.Filled : PositionStatus.PartiallyFilled;
|
||||||
: PositionStatus.PartiallyFilled;
|
|
||||||
|
|
||||||
_logger.LogInformation("Final position status: {Status}", position.Status);
|
_logger.LogInformation("Final position status: {Status}", position.Status);
|
||||||
}
|
}
|
||||||
@@ -119,52 +153,13 @@ public class PositionManagerWorker : BaseWorker<PositionManagerWorker>
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private async Task<Trade?> ProcessTrade(Account account, Trade trade, string tradeType, Func<Task<Trade>> createTrade)
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
// 1. Check existing status on exchange
|
|
||||||
var exchangeTrade = await _exchangeService.GetTrade(account, trade.ExchangeOrderId, trade.Ticker);
|
|
||||||
if (exchangeTrade != null && exchangeTrade.Status.IsActive())
|
|
||||||
{
|
|
||||||
_logger.LogInformation("{TradeType} already exists on exchange - Status: {Status}",
|
|
||||||
tradeType, exchangeTrade.Status);
|
|
||||||
return exchangeTrade;
|
|
||||||
}
|
|
||||||
|
|
||||||
// 2. Only create new order if in pending state
|
|
||||||
if (trade.Status != TradeStatus.PendingOpen)
|
|
||||||
{
|
|
||||||
_logger.LogWarning("{TradeType} creation skipped - Invalid status: {Status}",
|
|
||||||
tradeType, trade.Status);
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
// 3. Create new order
|
|
||||||
var newTrade = await createTrade();
|
|
||||||
if (newTrade?.Status == TradeStatus.Requested)
|
|
||||||
{
|
|
||||||
_logger.LogInformation("{TradeType} successfully created - ExchangeID: {ExchangeOrderId}",
|
|
||||||
tradeType, newTrade.ExchangeOrderId);
|
|
||||||
return newTrade;
|
|
||||||
}
|
|
||||||
|
|
||||||
_logger.LogError("{TradeType} creation failed - Null response or invalid status", tradeType);
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
catch (Exception ex)
|
|
||||||
{
|
|
||||||
_logger.LogError(ex, "{TradeType} processing failed", tradeType);
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private bool AllTradesActive(Position position)
|
private bool AllTradesActive(Position position)
|
||||||
{
|
{
|
||||||
return position.StopLoss.Status.IsActive() &&
|
return position.StopLoss.Status.IsActive() &&
|
||||||
position.TakeProfit1.Status.IsActive() &&
|
position.TakeProfit1.Status.IsActive() &&
|
||||||
(position.TakeProfit2?.Status.IsActive() ?? true);
|
(position.TakeProfit2?.Status.IsActive() ?? true);
|
||||||
}
|
}
|
||||||
|
|
||||||
private async Task ManageFilledPositions()
|
private async Task ManageFilledPositions()
|
||||||
{
|
{
|
||||||
var positions = GetPositions(PositionStatus.Filled);
|
var positions = GetPositions(PositionStatus.Filled);
|
||||||
@@ -181,22 +176,98 @@ public class PositionManagerWorker : BaseWorker<PositionManagerWorker>
|
|||||||
position.Status = PositionStatus.Updating;
|
position.Status = PositionStatus.Updating;
|
||||||
_tradingService.UpdatePosition(position);
|
_tradingService.UpdatePosition(position);
|
||||||
|
|
||||||
_logger.LogInformation("Managing filled position - Direction: {Direction}, Open Since: {OpenDate}",
|
_logger.LogInformation("Checking position state on exchange - Direction: {Direction}, Open Since: {OpenDate}",
|
||||||
position.OriginDirection, position.Date.ToString("yyyy-MM-dd HH:mm:ss"));
|
position.OriginDirection, position.Date.ToString("yyyy-MM-dd HH:mm:ss"));
|
||||||
|
|
||||||
var account = await GetAccount(position.AccountName);
|
var account = await GetAccount(position.AccountName);
|
||||||
|
|
||||||
// Perform position management
|
// Check if position still exists on broker
|
||||||
var updatedPosition = await _tradingService.ManagePosition(account, position);
|
var brokerPositions = await _exchangeService.GetBrokerPositions(account);
|
||||||
|
var exchangePosition = brokerPositions.FirstOrDefault(p =>
|
||||||
|
p.Ticker == position.Ticker &&
|
||||||
|
p.OriginDirection == position.OriginDirection);
|
||||||
|
|
||||||
// Log status changes if they occurred
|
if (exchangePosition == null)
|
||||||
if (updatedPosition.Status != position.Status)
|
|
||||||
{
|
{
|
||||||
_logger.LogInformation("Position status updated: {OldStatus} → {NewStatus}",
|
// Position no longer on exchange - it has been closed
|
||||||
position.Status, updatedPosition.Status);
|
_logger.LogInformation("Position no longer on exchange - marking as finished");
|
||||||
|
position.Status = PositionStatus.Finished;
|
||||||
|
|
||||||
|
// Determine if SL or TP was hit by checking which one is missing
|
||||||
|
if (exchangePosition?.StopLoss == null && position.StopLoss.Status != TradeStatus.Filled)
|
||||||
|
{
|
||||||
|
_logger.LogInformation("Stop loss appears to have been hit");
|
||||||
|
position.StopLoss.SetStatus(TradeStatus.Filled);
|
||||||
|
position.ProfitAndLoss = TradingBox.GetProfitAndLoss(
|
||||||
|
position,
|
||||||
|
position.StopLoss.Quantity,
|
||||||
|
position.StopLoss.Price,
|
||||||
|
position.Open.Leverage);
|
||||||
|
}
|
||||||
|
else if (exchangePosition?.TakeProfit1 == null && position.TakeProfit1.Status != TradeStatus.Filled)
|
||||||
|
{
|
||||||
|
_logger.LogInformation("Take profit 1 appears to have been hit");
|
||||||
|
position.TakeProfit1.SetStatus(TradeStatus.Filled);
|
||||||
|
position.ProfitAndLoss = TradingBox.GetProfitAndLoss(
|
||||||
|
position,
|
||||||
|
position.TakeProfit1.Quantity,
|
||||||
|
position.TakeProfit1.Price,
|
||||||
|
position.Open.Leverage);
|
||||||
|
}
|
||||||
|
else if (exchangePosition?.TakeProfit2 == null && position.TakeProfit2?.Status != TradeStatus.Filled)
|
||||||
|
{
|
||||||
|
_logger.LogInformation("Take profit 2 appears to have been hit");
|
||||||
|
position.TakeProfit2.SetStatus(TradeStatus.Filled);
|
||||||
|
position.ProfitAndLoss = TradingBox.GetProfitAndLoss(
|
||||||
|
position,
|
||||||
|
position.TakeProfit2.Quantity,
|
||||||
|
position.TakeProfit2.Price,
|
||||||
|
position.Open.Leverage);
|
||||||
}
|
}
|
||||||
|
|
||||||
_tradingService.UpdatePosition(updatedPosition);
|
// Cancel any remaining orders
|
||||||
|
await _exchangeService.CancelOrder(account, position.Ticker);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// Position still exists - update with exchange data
|
||||||
|
_logger.LogInformation("Position still active on exchange with quantity {Quantity}",
|
||||||
|
exchangePosition.Open?.Quantity ?? 0);
|
||||||
|
|
||||||
|
// Update our position with broker data
|
||||||
|
if (exchangePosition.Open != null)
|
||||||
|
position.Open = exchangePosition.Open;
|
||||||
|
|
||||||
|
if (exchangePosition.StopLoss != null)
|
||||||
|
position.StopLoss = exchangePosition.StopLoss;
|
||||||
|
|
||||||
|
if (exchangePosition.TakeProfit1 != null)
|
||||||
|
position.TakeProfit1 = exchangePosition.TakeProfit1;
|
||||||
|
|
||||||
|
if (exchangePosition.TakeProfit2 != null)
|
||||||
|
position.TakeProfit2 = exchangePosition.TakeProfit2;
|
||||||
|
|
||||||
|
if (exchangePosition.ProfitAndLoss != null)
|
||||||
|
position.ProfitAndLoss = exchangePosition.ProfitAndLoss;
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// Calculate PNL if not provided
|
||||||
|
var lastPrice = _exchangeService.GetPrice(account, position.Ticker, DateTime.UtcNow);
|
||||||
|
position.ProfitAndLoss = TradingBox.GetProfitAndLoss(
|
||||||
|
position,
|
||||||
|
position.Open.Quantity,
|
||||||
|
lastPrice,
|
||||||
|
position.Open.Leverage);
|
||||||
|
}
|
||||||
|
|
||||||
|
_logger.LogInformation("Updated position from exchange - PNL: {PNL}",
|
||||||
|
position.ProfitAndLoss?.Net ?? 0);
|
||||||
|
|
||||||
|
// Keep status as Filled
|
||||||
|
position.Status = PositionStatus.Filled;
|
||||||
|
}
|
||||||
|
|
||||||
|
_tradingService.UpdatePosition(position);
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
@@ -243,12 +314,26 @@ public class PositionManagerWorker : BaseWorker<PositionManagerWorker>
|
|||||||
_tradingService.UpdatePosition(position);
|
_tradingService.UpdatePosition(position);
|
||||||
|
|
||||||
var account = await GetAccount(position.AccountName);
|
var account = await GetAccount(position.AccountName);
|
||||||
var trade = await _exchangeService.GetTrade(account.Key, position.Open.ExchangeOrderId, position.Ticker);
|
|
||||||
var openTrade = position.Open;
|
|
||||||
|
|
||||||
if (trade.Status == TradeStatus.PendingOpen || trade.Status == TradeStatus.Requested)
|
// Check if position exists on broker
|
||||||
|
var brokerPositions = await _exchangeService.GetBrokerPositions(account);
|
||||||
|
var exchangePosition = brokerPositions.FirstOrDefault(p =>
|
||||||
|
p.Ticker == position.Ticker &&
|
||||||
|
p.OriginDirection == position.OriginDirection);
|
||||||
|
|
||||||
|
if (exchangePosition != null)
|
||||||
{
|
{
|
||||||
// Position staleness check
|
// Position is confirmed on exchange
|
||||||
|
position.Status = PositionStatus.PartiallyFilled;
|
||||||
|
position.Open = exchangePosition.Open; // Use the exchange data
|
||||||
|
_tradingService.UpdatePosition(position);
|
||||||
|
|
||||||
|
_logger.LogInformation("[{Identifier}] Position found on exchange - moving to partially filled status",
|
||||||
|
position.Identifier);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Position not found on exchange - check for staleness
|
||||||
if (position.Date < DateTime.UtcNow.AddDays(-1))
|
if (position.Date < DateTime.UtcNow.AddDays(-1))
|
||||||
{
|
{
|
||||||
position.Status = PositionStatus.Canceled;
|
position.Status = PositionStatus.Canceled;
|
||||||
@@ -259,29 +344,17 @@ public class PositionManagerWorker : BaseWorker<PositionManagerWorker>
|
|||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
// Reset status for retry
|
// Reset status to try again
|
||||||
position.Status = PositionStatus.New;
|
position.Status = PositionStatus.New;
|
||||||
_tradingService.UpdatePosition(position);
|
_tradingService.UpdatePosition(position);
|
||||||
_logger.LogInformation("[{Identifier}] Awaiting order fill - {Ticker} (0/{ExpectedQuantity})",
|
_logger.LogInformation("[{Identifier}] Position not yet found on exchange - awaiting fill",
|
||||||
position.Identifier,
|
|
||||||
position.Ticker, openTrade.Quantity);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
position.Status = PositionStatus.PartiallyFilled;
|
|
||||||
position.Open = openTrade;
|
|
||||||
// Position is now open, now waiting to open SLTP
|
|
||||||
_tradingService.UpdatePosition(position);
|
|
||||||
|
|
||||||
_logger.LogInformation("[{Identifier}] Position now open ",
|
|
||||||
position.Identifier);
|
position.Identifier);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
_logger.LogError(ex, "Error processing position {Identifier}", position.Identifier);
|
_logger.LogError(ex, "Error processing position {Identifier}", position.Identifier);
|
||||||
// Consider resetting to New status for retry if needed
|
// Reset to New status for retry
|
||||||
position.Status = PositionStatus.New;
|
position.Status = PositionStatus.New;
|
||||||
_tradingService.UpdatePosition(position);
|
_tradingService.UpdatePosition(position);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -136,9 +136,9 @@ public class TradingController : BaseController
|
|||||||
nameof(moneyManagementName));
|
nameof(moneyManagementName));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var user = await GetUser();
|
||||||
if (moneyManagement != null)
|
if (moneyManagement != null)
|
||||||
{
|
{
|
||||||
var user = await GetUser();
|
|
||||||
moneyManagement = await _moneyManagementService.GetMoneyMangement(user, moneyManagementName);
|
moneyManagement = await _moneyManagementService.GetMoneyMangement(user, moneyManagementName);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -149,6 +149,7 @@ public class TradingController : BaseController
|
|||||||
ticker,
|
ticker,
|
||||||
PositionInitiator.User,
|
PositionInitiator.User,
|
||||||
DateTime.UtcNow,
|
DateTime.UtcNow,
|
||||||
|
user,
|
||||||
isForPaperTrading: isForPaperTrading,
|
isForPaperTrading: isForPaperTrading,
|
||||||
price: openPrice);
|
price: openPrice);
|
||||||
var result = await _openTradeCommandHandler.Handle(command);
|
var result = await _openTradeCommandHandler.Handle(command);
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
using Managing.Application.Trading;
|
using Managing.Application.Trading;
|
||||||
using Managing.Application.Trading.Commands;
|
using Managing.Application.Trading.Commands;
|
||||||
using Managing.Domain.Trades;
|
using Managing.Domain.Trades;
|
||||||
|
using Managing.Domain.Users;
|
||||||
using Moq;
|
using Moq;
|
||||||
using Xunit;
|
using Xunit;
|
||||||
using static Managing.Common.Enums;
|
using static Managing.Common.Enums;
|
||||||
@@ -23,7 +24,9 @@ public class PositionTests : BaseTests
|
|||||||
Ticker.BTC,
|
Ticker.BTC,
|
||||||
PositionInitiator.User,
|
PositionInitiator.User,
|
||||||
DateTime.UtcNow,
|
DateTime.UtcNow,
|
||||||
isForPaperTrading: false);
|
_account.User,
|
||||||
|
isForPaperTrading: false,
|
||||||
|
signalIdentifier: new Guid().ToString());
|
||||||
var handler = new OpenPositionCommandHandler(
|
var handler = new OpenPositionCommandHandler(
|
||||||
_exchangeService,
|
_exchangeService,
|
||||||
_accountService.Object,
|
_accountService.Object,
|
||||||
@@ -41,8 +44,8 @@ public class PositionTests : BaseTests
|
|||||||
// _ = new GetAccountPositioqwnInfoListOutputDTO().DecodeOutput(hexPositions).d
|
// _ = new GetAccountPositioqwnInfoListOutputDTO().DecodeOutput(hexPositions).d
|
||||||
//
|
//
|
||||||
var openTrade = await _exchangeService.GetTrade(_account, "", Ticker.BTC);
|
var openTrade = await _exchangeService.GetTrade(_account, "", Ticker.BTC);
|
||||||
var position = new Position("", TradeDirection.Long, Ticker.BTC, MoneyManagement, PositionInitiator.User,
|
var position = new Position("", "", TradeDirection.Long, Ticker.BTC, MoneyManagement, PositionInitiator.User,
|
||||||
DateTime.UtcNow)
|
DateTime.UtcNow, new User())
|
||||||
{
|
{
|
||||||
Open = openTrade
|
Open = openTrade
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
using Managing.Domain.Trades;
|
using Managing.Domain.Trades;
|
||||||
|
using Managing.Domain.Users;
|
||||||
using Xunit;
|
using Xunit;
|
||||||
using static Managing.Common.Enums;
|
using static Managing.Common.Enums;
|
||||||
|
|
||||||
@@ -9,7 +10,8 @@ namespace Managing.Application.Tests
|
|||||||
[Theory]
|
[Theory]
|
||||||
[InlineData(1, 100, 110, 10)]
|
[InlineData(1, 100, 110, 10)]
|
||||||
[InlineData(2, 100, 110, 20)]
|
[InlineData(2, 100, 110, 20)]
|
||||||
public void Should_Return_Correct_ProfitAndLoss_Amount(decimal quantity, decimal price, decimal exitPrice, decimal expectedResult)
|
public void Should_Return_Correct_ProfitAndLoss_Amount(decimal quantity, decimal price, decimal exitPrice,
|
||||||
|
decimal expectedResult)
|
||||||
{
|
{
|
||||||
// Arrange
|
// Arrange
|
||||||
var init = new List<Tuple<decimal, decimal>>();
|
var init = new List<Tuple<decimal, decimal>>();
|
||||||
@@ -42,7 +44,8 @@ namespace Managing.Application.Tests
|
|||||||
position.ProfitAndLoss = new ProfitAndLoss(orders, position.OriginDirection);
|
position.ProfitAndLoss = new ProfitAndLoss(orders, position.OriginDirection);
|
||||||
|
|
||||||
// Trigger Stop Loss
|
// Trigger Stop Loss
|
||||||
position.ProfitAndLoss.AddFill(-position.TakeProfit2.Quantity, position.StopLoss.Price, position.OriginDirection);
|
position.ProfitAndLoss.AddFill(-position.TakeProfit2.Quantity, position.StopLoss.Price,
|
||||||
|
position.OriginDirection);
|
||||||
|
|
||||||
// Assert
|
// Assert
|
||||||
Assert.Equal(20, position.ProfitAndLoss.Realized);
|
Assert.Equal(20, position.ProfitAndLoss.Realized);
|
||||||
@@ -65,7 +68,8 @@ namespace Managing.Application.Tests
|
|||||||
position.ProfitAndLoss = new ProfitAndLoss(orders, position.OriginDirection);
|
position.ProfitAndLoss = new ProfitAndLoss(orders, position.OriginDirection);
|
||||||
|
|
||||||
// Trigger Stop Loss
|
// Trigger Stop Loss
|
||||||
position.ProfitAndLoss.AddFill(-position.TakeProfit2.Quantity, position.StopLoss.Price, position.OriginDirection);
|
position.ProfitAndLoss.AddFill(-position.TakeProfit2.Quantity, position.StopLoss.Price,
|
||||||
|
position.OriginDirection);
|
||||||
|
|
||||||
// Assert
|
// Assert
|
||||||
Assert.Equal(3.97005582759999752M, position.ProfitAndLoss.Realized);
|
Assert.Equal(3.97005582759999752M, position.ProfitAndLoss.Realized);
|
||||||
@@ -127,8 +131,10 @@ namespace Managing.Application.Tests
|
|||||||
|
|
||||||
// Act
|
// Act
|
||||||
position.ProfitAndLoss = new ProfitAndLoss(orders, position.OriginDirection);
|
position.ProfitAndLoss = new ProfitAndLoss(orders, position.OriginDirection);
|
||||||
position.ProfitAndLoss.AddFill(-position.TakeProfit1.Quantity, position.TakeProfit1.Price, TradeDirection.Short);
|
position.ProfitAndLoss.AddFill(-position.TakeProfit1.Quantity, position.TakeProfit1.Price,
|
||||||
position.ProfitAndLoss.AddFill(-position.TakeProfit2.Quantity, position.StopLoss.Price, TradeDirection.Short);
|
TradeDirection.Short);
|
||||||
|
position.ProfitAndLoss.AddFill(-position.TakeProfit2.Quantity, position.StopLoss.Price,
|
||||||
|
TradeDirection.Short);
|
||||||
|
|
||||||
// Assert
|
// Assert
|
||||||
Assert.Equal(20, position.ProfitAndLoss.Realized);
|
Assert.Equal(20, position.ProfitAndLoss.Realized);
|
||||||
@@ -151,7 +157,8 @@ namespace Managing.Application.Tests
|
|||||||
position.ProfitAndLoss = new ProfitAndLoss(orders, position.OriginDirection);
|
position.ProfitAndLoss = new ProfitAndLoss(orders, position.OriginDirection);
|
||||||
|
|
||||||
// Trigger Stop Loss
|
// Trigger Stop Loss
|
||||||
position.ProfitAndLoss.AddFill(-position.TakeProfit2.Quantity, position.TakeProfit2.Price, TradeDirection.Short);
|
position.ProfitAndLoss.AddFill(-position.TakeProfit2.Quantity, position.TakeProfit2.Price,
|
||||||
|
TradeDirection.Short);
|
||||||
|
|
||||||
// Assert
|
// Assert
|
||||||
Assert.Equal(120, position.ProfitAndLoss.Realized);
|
Assert.Equal(120, position.ProfitAndLoss.Realized);
|
||||||
@@ -174,7 +181,8 @@ namespace Managing.Application.Tests
|
|||||||
position.ProfitAndLoss = new ProfitAndLoss(orders, position.OriginDirection);
|
position.ProfitAndLoss = new ProfitAndLoss(orders, position.OriginDirection);
|
||||||
|
|
||||||
// Trigger Stop Loss
|
// Trigger Stop Loss
|
||||||
position.ProfitAndLoss.AddFill(-position.TakeProfit2.Quantity, position.StopLoss.Price, TradeDirection.Long);
|
position.ProfitAndLoss.AddFill(-position.TakeProfit2.Quantity, position.StopLoss.Price,
|
||||||
|
TradeDirection.Long);
|
||||||
|
|
||||||
// Assert
|
// Assert
|
||||||
Assert.Equal(20, position.ProfitAndLoss.Realized);
|
Assert.Equal(20, position.ProfitAndLoss.Realized);
|
||||||
@@ -197,7 +205,8 @@ namespace Managing.Application.Tests
|
|||||||
position.ProfitAndLoss = new ProfitAndLoss(orders, position.OriginDirection);
|
position.ProfitAndLoss = new ProfitAndLoss(orders, position.OriginDirection);
|
||||||
|
|
||||||
// Trigger Stop Loss
|
// Trigger Stop Loss
|
||||||
position.ProfitAndLoss.AddFill(-position.TakeProfit2.Quantity, position.TakeProfit2.Price, TradeDirection.Long);
|
position.ProfitAndLoss.AddFill(-position.TakeProfit2.Quantity, position.TakeProfit2.Price,
|
||||||
|
TradeDirection.Long);
|
||||||
|
|
||||||
// Assert
|
// Assert
|
||||||
Assert.Equal(120, position.ProfitAndLoss.Realized);
|
Assert.Equal(120, position.ProfitAndLoss.Realized);
|
||||||
@@ -205,7 +214,8 @@ namespace Managing.Application.Tests
|
|||||||
|
|
||||||
private static Position GetFakeShortPosition()
|
private static Position GetFakeShortPosition()
|
||||||
{
|
{
|
||||||
return new Position("FakeAccount", TradeDirection.Short, Ticker.BTC, null, PositionInitiator.PaperTrading, DateTime.UtcNow)
|
return new Position("", "FakeAccount", TradeDirection.Short, Ticker.BTC, null,
|
||||||
|
PositionInitiator.PaperTrading, DateTime.UtcNow, new User())
|
||||||
{
|
{
|
||||||
Open = new Trade(DateTime.Now, TradeDirection.Short, TradeStatus.Filled,
|
Open = new Trade(DateTime.Now, TradeDirection.Short, TradeStatus.Filled,
|
||||||
TradeType.Market, Ticker.ADA, 10, 100, 1, "OpenOrderId", ""),
|
TradeType.Market, Ticker.ADA, 10, 100, 1, "OpenOrderId", ""),
|
||||||
@@ -220,22 +230,28 @@ namespace Managing.Application.Tests
|
|||||||
|
|
||||||
private static Position GetSolanaLongPosition()
|
private static Position GetSolanaLongPosition()
|
||||||
{
|
{
|
||||||
return new Position("FakeAccount", TradeDirection.Long, Ticker.BTC, null, PositionInitiator.PaperTrading, DateTime.UtcNow)
|
return new Position("", "FakeAccount", TradeDirection.Long, Ticker.BTC, null,
|
||||||
|
PositionInitiator.PaperTrading, DateTime.UtcNow, new User())
|
||||||
{
|
{
|
||||||
Open = new Trade(DateTime.Now, TradeDirection.Long, TradeStatus.Filled,
|
Open = new Trade(DateTime.Now, TradeDirection.Long, TradeStatus.Filled,
|
||||||
TradeType.Market, Ticker.ADA, (decimal)6.0800904000245037980887037491, (decimal)81.6200, 1, "OpenOrderId", ""),
|
TradeType.Market, Ticker.ADA, (decimal)6.0800904000245037980887037491, (decimal)81.6200, 1,
|
||||||
|
"OpenOrderId", ""),
|
||||||
StopLoss = new Trade(DateTime.Now, TradeDirection.Short, TradeStatus.PendingOpen,
|
StopLoss = new Trade(DateTime.Now, TradeDirection.Short, TradeStatus.PendingOpen,
|
||||||
TradeType.StopMarket, Ticker.ADA, (decimal)3.6480542400147022788532222495, (decimal)79.987600, 1, "StopLossOrderId", ""),
|
TradeType.StopMarket, Ticker.ADA, (decimal)3.6480542400147022788532222495, (decimal)79.987600, 1,
|
||||||
|
"StopLossOrderId", ""),
|
||||||
TakeProfit1 = new Trade(DateTime.Now, TradeDirection.Short, TradeStatus.PendingOpen,
|
TakeProfit1 = new Trade(DateTime.Now, TradeDirection.Short, TradeStatus.PendingOpen,
|
||||||
TradeType.TakeProfitLimit, Ticker.ADA, (decimal)2.4320361600098015192354814996, (decimal)85.701000, 1, "TakeProfit1OrderId", ""),
|
TradeType.TakeProfitLimit, Ticker.ADA, (decimal)2.4320361600098015192354814996, (decimal)85.701000,
|
||||||
|
1, "TakeProfit1OrderId", ""),
|
||||||
TakeProfit2 = new Trade(DateTime.Now, TradeDirection.Short, TradeStatus.PendingOpen,
|
TakeProfit2 = new Trade(DateTime.Now, TradeDirection.Short, TradeStatus.PendingOpen,
|
||||||
TradeType.TakeProfitLimit, Ticker.ADA, (decimal)3.6480542400147022788532222495, (decimal)89.782000, 1, "TakeProfit1OrderId", "")
|
TradeType.TakeProfitLimit, Ticker.ADA, (decimal)3.6480542400147022788532222495, (decimal)89.782000,
|
||||||
|
1, "TakeProfit1OrderId", "")
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
private static Position GetFakeLongPosition()
|
private static Position GetFakeLongPosition()
|
||||||
{
|
{
|
||||||
return new Position("FakeAccount", TradeDirection.Long, Ticker.BTC, null, PositionInitiator.PaperTrading, DateTime.UtcNow)
|
return new Position("", "FakeAccount", TradeDirection.Long, Ticker.BTC, null,
|
||||||
|
PositionInitiator.PaperTrading, DateTime.UtcNow, new User())
|
||||||
{
|
{
|
||||||
Open = new Trade(DateTime.Now, TradeDirection.Short, TradeStatus.Filled,
|
Open = new Trade(DateTime.Now, TradeDirection.Short, TradeStatus.Filled,
|
||||||
TradeType.Market, Ticker.ADA, 10, 100, 1, "OpenOrderId", ""),
|
TradeType.Market, Ticker.ADA, 10, 100, 1, "OpenOrderId", ""),
|
||||||
|
|||||||
@@ -344,11 +344,13 @@ public class TradingBot : Bot, ITradingBot
|
|||||||
var orders = await ExchangeService.GetOpenOrders(Account, Ticker);
|
var orders = await ExchangeService.GetOpenOrders(Account, Ticker);
|
||||||
if (orders.Any())
|
if (orders.Any())
|
||||||
{
|
{
|
||||||
await LogInformation($"Cannot update Position. Position is still waiting for opening. There is {orders.Count()} open orders.");
|
await LogInformation(
|
||||||
|
$"Cannot update Position. Position is still waiting for opening. There is {orders.Count()} open orders.");
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
await LogWarning($"Cannot update Position. No position on exchange and no orders. Position {signal.Identifier} might be closed already.");
|
await LogWarning(
|
||||||
|
$"Cannot update Position. No position on exchange and no orders. Position {signal.Identifier} might be closed already.");
|
||||||
await HandleClosedPosition(positionForSignal);
|
await HandleClosedPosition(positionForSignal);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -525,15 +527,16 @@ public class TradingBot : Bot, ITradingBot
|
|||||||
Ticker,
|
Ticker,
|
||||||
IsForBacktest ? PositionInitiator.PaperTrading : PositionInitiator.Bot,
|
IsForBacktest ? PositionInitiator.PaperTrading : PositionInitiator.Bot,
|
||||||
signal.Date,
|
signal.Date,
|
||||||
|
User,
|
||||||
IsForBacktest,
|
IsForBacktest,
|
||||||
lastPrice,
|
lastPrice,
|
||||||
balance: WalletBalances.LastOrDefault().Value,
|
balance: WalletBalances.LastOrDefault().Value,
|
||||||
fee: Fee);
|
fee: Fee,
|
||||||
|
signalIdentifier: signal.Identifier);
|
||||||
|
|
||||||
var position = await new OpenPositionCommandHandler(ExchangeService, AccountService, TradingService)
|
var position = await new OpenPositionCommandHandler(ExchangeService, AccountService, TradingService)
|
||||||
.Handle(command);
|
.Handle(command);
|
||||||
|
|
||||||
|
|
||||||
if (position != null)
|
if (position != null)
|
||||||
{
|
{
|
||||||
position.SignalIdentifier = signal.Identifier;
|
position.SignalIdentifier = signal.Identifier;
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
using Managing.Domain.MoneyManagements;
|
using Managing.Domain.MoneyManagements;
|
||||||
using Managing.Domain.Trades;
|
using Managing.Domain.Trades;
|
||||||
|
using Managing.Domain.Users;
|
||||||
using MediatR;
|
using MediatR;
|
||||||
using static Managing.Common.Enums;
|
using static Managing.Common.Enums;
|
||||||
|
|
||||||
@@ -14,11 +15,13 @@ namespace Managing.Application.Trading.Commands
|
|||||||
Ticker ticker,
|
Ticker ticker,
|
||||||
PositionInitiator initiator,
|
PositionInitiator initiator,
|
||||||
DateTime date,
|
DateTime date,
|
||||||
|
User user,
|
||||||
bool isForPaperTrading = false,
|
bool isForPaperTrading = false,
|
||||||
decimal? price = null,
|
decimal? price = null,
|
||||||
decimal? balance = 1000,
|
decimal? balance = 1000,
|
||||||
decimal? fee = null,
|
decimal? fee = null,
|
||||||
bool? ignoreSLTP = false)
|
bool? ignoreSLTP = false,
|
||||||
|
string signalIdentifier = null)
|
||||||
{
|
{
|
||||||
AccountName = accountName;
|
AccountName = accountName;
|
||||||
MoneyManagement = moneyManagement;
|
MoneyManagement = moneyManagement;
|
||||||
@@ -31,8 +34,11 @@ namespace Managing.Application.Trading.Commands
|
|||||||
Initiator = initiator;
|
Initiator = initiator;
|
||||||
Fee = fee;
|
Fee = fee;
|
||||||
IgnoreSLTP = ignoreSLTP;
|
IgnoreSLTP = ignoreSLTP;
|
||||||
|
User = user;
|
||||||
|
SignalIdentifier = signalIdentifier;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public string SignalIdentifier { get; set; }
|
||||||
public string AccountName { get; }
|
public string AccountName { get; }
|
||||||
public MoneyManagement MoneyManagement { get; }
|
public MoneyManagement MoneyManagement { get; }
|
||||||
public TradeDirection Direction { get; }
|
public TradeDirection Direction { get; }
|
||||||
@@ -44,5 +50,6 @@ namespace Managing.Application.Trading.Commands
|
|||||||
public decimal? Balance { get; }
|
public decimal? Balance { get; }
|
||||||
public DateTime Date { get; set; }
|
public DateTime Date { get; set; }
|
||||||
public PositionInitiator Initiator { get; internal set; }
|
public PositionInitiator Initiator { get; internal set; }
|
||||||
|
public User User { get; internal set; }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -26,8 +26,15 @@ namespace Managing.Application.Trading
|
|||||||
}
|
}
|
||||||
|
|
||||||
var initiator = request.IsForPaperTrading ? PositionInitiator.PaperTrading : request.Initiator;
|
var initiator = request.IsForPaperTrading ? PositionInitiator.PaperTrading : request.Initiator;
|
||||||
var position = new Position(request.AccountName, request.Direction, request.Ticker, request.MoneyManagement,
|
var position = new Position(new Guid().ToString(), request.AccountName, request.Direction, request.Ticker,
|
||||||
initiator, request.Date);
|
request.MoneyManagement,
|
||||||
|
initiator, request.Date, request.User);
|
||||||
|
|
||||||
|
if (!string.IsNullOrEmpty(request.SignalIdentifier))
|
||||||
|
{
|
||||||
|
position.SignalIdentifier = request.SignalIdentifier;
|
||||||
|
}
|
||||||
|
|
||||||
var balance = request.IsForPaperTrading
|
var balance = request.IsForPaperTrading
|
||||||
? request.Balance.GetValueOrDefault()
|
? request.Balance.GetValueOrDefault()
|
||||||
: exchangeService.GetBalance(account, request.IsForPaperTrading).Result;
|
: exchangeService.GetBalance(account, request.IsForPaperTrading).Result;
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
using Managing.Application.Abstractions;
|
using InfluxDB.Client.Api.Domain;
|
||||||
|
using Managing.Application.Abstractions;
|
||||||
using Managing.Application.Abstractions.Services;
|
using Managing.Application.Abstractions.Services;
|
||||||
using Managing.Application.Trading.Commands;
|
using Managing.Application.Trading.Commands;
|
||||||
using Managing.Application.Trading;
|
using Managing.Application.Trading;
|
||||||
@@ -63,10 +64,9 @@ public class OpenPosition : FlowBase
|
|||||||
var Positions = JsonConvert.DeserializeObject<List<Position>>(_cacheService.GetValue(POSITIONS_KEY));
|
var Positions = JsonConvert.DeserializeObject<List<Position>>(_cacheService.GetValue(POSITIONS_KEY));
|
||||||
var Signals = JsonConvert.DeserializeObject<HashSet<Signal>>(_cacheService.GetValue(POSITIONS_KEY));
|
var Signals = JsonConvert.DeserializeObject<HashSet<Signal>>(_cacheService.GetValue(POSITIONS_KEY));
|
||||||
|
|
||||||
Fee = _cacheService.GetOrSave(FEE_KEY, () =>
|
Fee = _cacheService.GetOrSave(FEE_KEY,
|
||||||
{
|
() => { return _tradingService.GetFee(Account, OpenPositionParameters.IsForBacktest); },
|
||||||
return _tradingService.GetFee(Account, OpenPositionParameters.IsForBacktest);
|
TimeSpan.FromDays(1));
|
||||||
}, TimeSpan.FromDays(1));
|
|
||||||
|
|
||||||
await ExecuteOpenPosition(signal, Positions, Signals, Candles, Account);
|
await ExecuteOpenPosition(signal, Positions, Signals, Candles, Account);
|
||||||
|
|
||||||
@@ -82,13 +82,16 @@ public class OpenPosition : FlowBase
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private async Task ExecuteOpenPosition(Signal signal, List<Position> positions, HashSet<Signal> signals, HashSet<Candle> candles, Account account)
|
private async Task ExecuteOpenPosition(Signal signal, List<Position> positions, HashSet<Signal> signals,
|
||||||
|
HashSet<Candle> candles, Account account)
|
||||||
{
|
{
|
||||||
// Check if a position is already open
|
// Check if a position is already open
|
||||||
var openedPosition = positions.FirstOrDefault(p => p.Status == PositionStatus.Filled
|
var openedPosition = positions.FirstOrDefault(p => p.Status == PositionStatus.Filled
|
||||||
&& p.SignalIdentifier != signal.Identifier);
|
&& p.SignalIdentifier != signal.Identifier);
|
||||||
|
|
||||||
var lastPrice = OpenPositionParameters.IsForBacktest ? candles.Last().Close : _exchangeService.GetPrice(account, signal.Ticker, DateTime.UtcNow);
|
var lastPrice = OpenPositionParameters.IsForBacktest
|
||||||
|
? candles.Last().Close
|
||||||
|
: _exchangeService.GetPrice(account, signal.Ticker, DateTime.UtcNow);
|
||||||
|
|
||||||
// If position open
|
// If position open
|
||||||
if (openedPosition != null)
|
if (openedPosition != null)
|
||||||
@@ -110,7 +113,8 @@ public class OpenPosition : FlowBase
|
|||||||
{
|
{
|
||||||
//await LogInformation("Try to flip the position because of an opposite direction signal");
|
//await LogInformation("Try to flip the position because of an opposite direction signal");
|
||||||
//await CloseTrade(previousSignal, openedPosition, openedPosition.Open, lastPrice, true);
|
//await CloseTrade(previousSignal, openedPosition, openedPosition.Open, lastPrice, true);
|
||||||
positions.FirstOrDefault(s => s.Identifier == previousSignal.Identifier).Status = PositionStatus.Flipped;
|
positions.FirstOrDefault(s => s.Identifier == previousSignal.Identifier).Status =
|
||||||
|
PositionStatus.Flipped;
|
||||||
await ExecuteOpenPosition(signal, positions, signals, candles, account);
|
await ExecuteOpenPosition(signal, positions, signals, candles, account);
|
||||||
|
|
||||||
//await LogInformation($"Position {previousSignal.Identifier} flipped by {signal.Identifier} at {lastPrice}$");
|
//await LogInformation($"Position {previousSignal.Identifier} flipped by {signal.Identifier} at {lastPrice}$");
|
||||||
@@ -134,8 +138,11 @@ public class OpenPosition : FlowBase
|
|||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
var moneyManagement = await _settingsRepository.GetMoneyManagement(OpenPositionParameters.MoneyManagementName);
|
var moneyManagement =
|
||||||
var WalletBalances = JsonConvert.DeserializeObject<Dictionary<DateTime, decimal>>(_cacheService.GetValue(WALLET_BALANCES));
|
await _settingsRepository.GetMoneyManagement(OpenPositionParameters.MoneyManagementName);
|
||||||
|
var WalletBalances =
|
||||||
|
JsonConvert.DeserializeObject<Dictionary<DateTime, decimal>>(
|
||||||
|
_cacheService.GetValue(WALLET_BALANCES));
|
||||||
|
|
||||||
var command = new OpenPositionRequest(
|
var command = new OpenPositionRequest(
|
||||||
OpenPositionParameters.AccountName,
|
OpenPositionParameters.AccountName,
|
||||||
@@ -144,6 +151,7 @@ public class OpenPosition : FlowBase
|
|||||||
signal.Ticker,
|
signal.Ticker,
|
||||||
PositionInitiator.Bot,
|
PositionInitiator.Bot,
|
||||||
signal.Date,
|
signal.Date,
|
||||||
|
account.User,
|
||||||
OpenPositionParameters.IsForBacktest,
|
OpenPositionParameters.IsForBacktest,
|
||||||
lastPrice,
|
lastPrice,
|
||||||
balance: WalletBalances.LastOrDefault().Value,
|
balance: WalletBalances.LastOrDefault().Value,
|
||||||
@@ -158,18 +166,21 @@ public class OpenPosition : FlowBase
|
|||||||
{
|
{
|
||||||
position.SignalIdentifier = signal.Identifier;
|
position.SignalIdentifier = signal.Identifier;
|
||||||
positions.Add(position);
|
positions.Add(position);
|
||||||
signals.FirstOrDefault(s => s.Identifier == signal.Identifier).Status = SignalStatus.PositionOpen;
|
signals.FirstOrDefault(s => s.Identifier == signal.Identifier).Status =
|
||||||
|
SignalStatus.PositionOpen;
|
||||||
|
|
||||||
if (!OpenPositionParameters.IsForBacktest)
|
if (!OpenPositionParameters.IsForBacktest)
|
||||||
{
|
{
|
||||||
await _messengerService.SendPosition(position);
|
await _messengerService.SendPosition(position);
|
||||||
}
|
}
|
||||||
|
|
||||||
Output = JsonConvert.SerializeObject(position);
|
Output = JsonConvert.SerializeObject(position);
|
||||||
//Logger.LogInformation($"Position requested");
|
//Logger.LogInformation($"Position requested");
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
positions.FirstOrDefault(s => s.Identifier == signal.Identifier).Status = PositionStatus.Rejected;
|
positions.FirstOrDefault(s => s.Identifier == signal.Identifier).Status =
|
||||||
|
PositionStatus.Rejected;
|
||||||
signals.FirstOrDefault(s => s.Identifier == signal.Identifier).Status = SignalStatus.Expired;
|
signals.FirstOrDefault(s => s.Identifier == signal.Identifier).Status = SignalStatus.Expired;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -180,10 +191,10 @@ public class OpenPosition : FlowBase
|
|||||||
//await LogWarning($"Cannot open trade : {ex.Message}");
|
//await LogWarning($"Cannot open trade : {ex.Message}");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private bool CanOpenPosition(Signal signal, List<Position> positions, HashSet<Signal> signals, HashSet<Candle> candles)
|
private bool CanOpenPosition(Signal signal, List<Position> positions, HashSet<Signal> signals,
|
||||||
|
HashSet<Candle> candles)
|
||||||
{
|
{
|
||||||
if (positions.Count == 0)
|
if (positions.Count == 0)
|
||||||
return true;
|
return true;
|
||||||
|
|||||||
@@ -7,9 +7,10 @@ namespace Managing.Domain.Trades
|
|||||||
{
|
{
|
||||||
public class Position
|
public class Position
|
||||||
{
|
{
|
||||||
public Position(string accountName, TradeDirection originDirection, Ticker ticker, MoneyManagement moneyManagement, PositionInitiator positionInitiator, DateTime date)
|
public Position(string identifier, string accountName, TradeDirection originDirection, Ticker ticker,
|
||||||
|
MoneyManagement moneyManagement, PositionInitiator positionInitiator, DateTime date, User user)
|
||||||
{
|
{
|
||||||
Identifier = Guid.NewGuid().ToString();
|
Identifier = identifier;
|
||||||
AccountName = accountName;
|
AccountName = accountName;
|
||||||
OriginDirection = originDirection;
|
OriginDirection = originDirection;
|
||||||
Ticker = ticker;
|
Ticker = ticker;
|
||||||
@@ -17,34 +18,24 @@ namespace Managing.Domain.Trades
|
|||||||
Initiator = positionInitiator;
|
Initiator = positionInitiator;
|
||||||
Date = date;
|
Date = date;
|
||||||
Status = Initiator == PositionInitiator.PaperTrading ? PositionStatus.Filled : PositionStatus.New;
|
Status = Initiator == PositionInitiator.PaperTrading ? PositionStatus.Filled : PositionStatus.New;
|
||||||
|
User = user;
|
||||||
}
|
}
|
||||||
|
|
||||||
[Required]
|
[Required] public string AccountName { get; }
|
||||||
public string AccountName { get; }
|
[Required] public DateTime Date { get; set; }
|
||||||
[Required]
|
[Required] public TradeDirection OriginDirection { get; }
|
||||||
public DateTime Date { get; set; }
|
[Required] public Ticker Ticker { get; }
|
||||||
[Required]
|
[Required] public MoneyManagement MoneyManagement { get; }
|
||||||
public TradeDirection OriginDirection { get; }
|
[Required] public Trade Open { get; set; }
|
||||||
[Required]
|
[Required] public Trade StopLoss { get; set; }
|
||||||
public Ticker Ticker { get; }
|
[Required] public Trade TakeProfit1 { get; set; }
|
||||||
[Required]
|
|
||||||
public MoneyManagement MoneyManagement { get; }
|
|
||||||
[Required]
|
|
||||||
public Trade Open { get; set; }
|
|
||||||
[Required]
|
|
||||||
public Trade StopLoss { get; set; }
|
|
||||||
[Required]
|
|
||||||
public Trade TakeProfit1 { get; set; }
|
|
||||||
public Trade TakeProfit2 { get; set; }
|
public Trade TakeProfit2 { get; set; }
|
||||||
public ProfitAndLoss ProfitAndLoss { get; set; }
|
public ProfitAndLoss ProfitAndLoss { get; set; }
|
||||||
[Required]
|
[Required] public PositionStatus Status { get; set; }
|
||||||
public PositionStatus Status { get; set; }
|
|
||||||
public string SignalIdentifier { get; set; }
|
public string SignalIdentifier { get; set; }
|
||||||
[Required]
|
[Required] public string Identifier { get; set; }
|
||||||
public string Identifier { get; set; }
|
[Required] public PositionInitiator Initiator { get; }
|
||||||
[Required]
|
[Required] public User User { get; set; }
|
||||||
public PositionInitiator Initiator { get; }
|
|
||||||
public User User { get; set; }
|
|
||||||
|
|
||||||
public bool IsFinished()
|
public bool IsFinished()
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -257,7 +257,7 @@ public static class MongoMappers
|
|||||||
MoneyManagement = Map(position.MoneyManagement),
|
MoneyManagement = Map(position.MoneyManagement),
|
||||||
Initiator = position.Initiator,
|
Initiator = position.Initiator,
|
||||||
Ticker = position.Ticker,
|
Ticker = position.Ticker,
|
||||||
User = position.User != null ? Map(position.User) : null
|
User = Map(position.User)
|
||||||
};
|
};
|
||||||
|
|
||||||
if (position.StopLoss != null)
|
if (position.StopLoss != null)
|
||||||
@@ -294,8 +294,8 @@ public static class MongoMappers
|
|||||||
|
|
||||||
public static Position Map(PositionDto dto)
|
public static Position Map(PositionDto dto)
|
||||||
{
|
{
|
||||||
var position = new Position(dto.AccountName, originDirection: dto.OriginDirection, dto.Ticker,
|
var position = new Position(dto.Identifier, dto.AccountName, originDirection: dto.OriginDirection, dto.Ticker,
|
||||||
Map(dto.MoneyManagement), dto.Initiator, dto.Date)
|
Map(dto.MoneyManagement), dto.Initiator, dto.Date, Map(dto.User))
|
||||||
{
|
{
|
||||||
Open = new Trade(date: dto.Open.Date, direction: dto.Open.Direction, status: dto.Open.Status,
|
Open = new Trade(date: dto.Open.Date, direction: dto.Open.Direction, status: dto.Open.Status,
|
||||||
tradeType: dto.Open.TradeType, ticker: dto.Open.Ticker, quantity: dto.Open.Quantity,
|
tradeType: dto.Open.TradeType, ticker: dto.Open.Ticker, quantity: dto.Open.Quantity,
|
||||||
@@ -305,7 +305,7 @@ public static class MongoMappers
|
|||||||
Status = dto.Status,
|
Status = dto.Status,
|
||||||
SignalIdentifier = dto.SignalIdentifier,
|
SignalIdentifier = dto.SignalIdentifier,
|
||||||
Identifier = dto.Identifier,
|
Identifier = dto.Identifier,
|
||||||
User = dto.User != null ? Map(dto.User) : null
|
User = Map(dto.User)
|
||||||
};
|
};
|
||||||
|
|
||||||
if (dto.StopLoss != null)
|
if (dto.StopLoss != null)
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ using Managing.Common;
|
|||||||
using Managing.Core;
|
using Managing.Core;
|
||||||
using Managing.Domain.MoneyManagements;
|
using Managing.Domain.MoneyManagements;
|
||||||
using Managing.Domain.Trades;
|
using Managing.Domain.Trades;
|
||||||
|
using Managing.Domain.Users;
|
||||||
using static Managing.Common.Enums;
|
using static Managing.Common.Enums;
|
||||||
|
|
||||||
namespace Managing.Infrastructure.Messengers.Discord
|
namespace Managing.Infrastructure.Messengers.Discord
|
||||||
@@ -73,7 +74,7 @@ namespace Managing.Infrastructure.Messengers.Discord
|
|||||||
TakeProfit = takeProfit.GetValueOrDefault(),
|
TakeProfit = takeProfit.GetValueOrDefault(),
|
||||||
};
|
};
|
||||||
var tradeCommand = new OpenPositionRequest(accountName, moneymanagement, direction, ticker,
|
var tradeCommand = new OpenPositionRequest(accountName, moneymanagement, direction, ticker,
|
||||||
PositionInitiator.User, DateTime.UtcNow);
|
PositionInitiator.User, DateTime.UtcNow, new User());
|
||||||
var result = await _openTradeCommandHandler.Handle(tradeCommand);
|
var result = await _openTradeCommandHandler.Handle(tradeCommand);
|
||||||
var builder = new ComponentBuilder().WithButton("Close Position",
|
var builder = new ComponentBuilder().WithButton("Close Position",
|
||||||
$"{Constants.DiscordButtonAction.ClosePosition}|{result.Open.ExchangeOrderId}");
|
$"{Constants.DiscordButtonAction.ClosePosition}|{result.Open.ExchangeOrderId}");
|
||||||
|
|||||||
@@ -273,6 +273,7 @@ namespace Managing.Infrastructure.Messengers.Discord
|
|||||||
ticker,
|
ticker,
|
||||||
initiator,
|
initiator,
|
||||||
DateTime.UtcNow,
|
DateTime.UtcNow,
|
||||||
|
defaultUser,
|
||||||
ignoreSLTP: ignoreSLTP);
|
ignoreSLTP: ignoreSLTP);
|
||||||
var position = await new OpenPositionCommandHandler(exchangeService, accountService, tradingService)
|
var position = await new OpenPositionCommandHandler(exchangeService, accountService, tradingService)
|
||||||
.Handle(tradeCommand);
|
.Handle(tradeCommand);
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ using Managing.Infrastructure.Evm.Models.Gmx.v2;
|
|||||||
using Managing.Infrastructure.Evm.Models.Proxy;
|
using Managing.Infrastructure.Evm.Models.Proxy;
|
||||||
using Nethereum.Web3;
|
using Nethereum.Web3;
|
||||||
using Managing.Domain.MoneyManagements;
|
using Managing.Domain.MoneyManagements;
|
||||||
|
using Managing.Domain.Users;
|
||||||
using static Managing.Common.Enums;
|
using static Managing.Common.Enums;
|
||||||
|
|
||||||
namespace Managing.Infrastructure.Evm.Services.Gmx;
|
namespace Managing.Infrastructure.Evm.Services.Gmx;
|
||||||
@@ -161,12 +162,13 @@ internal static class GmxV2Mappers
|
|||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
var position = new Position("",
|
var position = new Position("", "",
|
||||||
MiscExtensions.ParseEnum<TradeDirection>(gmxPosition.Direction),
|
MiscExtensions.ParseEnum<TradeDirection>(gmxPosition.Direction),
|
||||||
MiscExtensions.ParseEnum<Ticker>(gmxPosition.Ticker),
|
MiscExtensions.ParseEnum<Ticker>(gmxPosition.Ticker),
|
||||||
new MoneyManagement(),
|
new MoneyManagement(),
|
||||||
PositionInitiator.User,
|
PositionInitiator.User,
|
||||||
gmxPosition.Date);
|
gmxPosition.Date,
|
||||||
|
new User());
|
||||||
position.Open = Map(gmxPosition.Open);
|
position.Open = Map(gmxPosition.Open);
|
||||||
position.TakeProfit1 = Map(gmxPosition.TakeProfit1);
|
position.TakeProfit1 = Map(gmxPosition.TakeProfit1);
|
||||||
position.StopLoss = Map(gmxPosition.StopLoss);
|
position.StopLoss = Map(gmxPosition.StopLoss);
|
||||||
@@ -180,9 +182,11 @@ internal static class GmxV2Mappers
|
|||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
Console.WriteLine($"Error mapping GMX position {gmxPosition?.ExchangeOrderId}: {ex.Message} \n StackTrace: {ex.StackTrace}");
|
Console.WriteLine(
|
||||||
|
$"Error mapping GMX position {gmxPosition?.ExchangeOrderId}: {ex.Message} \n StackTrace: {ex.StackTrace}");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return positions;
|
return positions;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -35,7 +35,7 @@ namespace Managing.Infrastructure.Evm.Services
|
|||||||
endpoint = $"/{endpoint}";
|
endpoint = $"/{endpoint}";
|
||||||
}
|
}
|
||||||
|
|
||||||
var url = $"{_settings.BaseUrl}privy{endpoint}";
|
var url = $"{_settings.BaseUrl}/api/privy{endpoint}";
|
||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
@@ -62,7 +62,7 @@ namespace Managing.Infrastructure.Evm.Services
|
|||||||
endpoint = $"/{endpoint}";
|
endpoint = $"/{endpoint}";
|
||||||
}
|
}
|
||||||
|
|
||||||
var url = $"{_settings.BaseUrl}privy{endpoint}";
|
var url = $"{_settings.BaseUrl}/api/privy{endpoint}";
|
||||||
|
|
||||||
if (payload != null)
|
if (payload != null)
|
||||||
{
|
{
|
||||||
@@ -166,7 +166,8 @@ namespace Managing.Infrastructure.Evm.Services
|
|||||||
try
|
try
|
||||||
{
|
{
|
||||||
// Try to parse as structured error if it doesn't match the simple format
|
// Try to parse as structured error if it doesn't match the simple format
|
||||||
var structuredErrorResponse = await response.Content.ReadFromJsonAsync<Web3ProxyErrorResponse>(_jsonOptions);
|
var structuredErrorResponse =
|
||||||
|
await response.Content.ReadFromJsonAsync<Web3ProxyErrorResponse>(_jsonOptions);
|
||||||
|
|
||||||
if (structuredErrorResponse?.ErrorDetails != null)
|
if (structuredErrorResponse?.ErrorDetails != null)
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -321,7 +321,7 @@ export async function increaseOrderHelper(
|
|||||||
referralCodeForTxn: params.referralCodeForTxn,
|
referralCodeForTxn: params.referralCodeForTxn,
|
||||||
triggerPrice: params.limitPrice,
|
triggerPrice: params.limitPrice,
|
||||||
collateralTokenAddress: collateralToken.address,
|
collateralTokenAddress: collateralToken.address,
|
||||||
isLong: true,
|
isLong: params.isLong,
|
||||||
receiveTokenAddress: collateralTokenAddress,
|
receiveTokenAddress: collateralTokenAddress,
|
||||||
indexToken: marketInfo.indexToken,
|
indexToken: marketInfo.indexToken,
|
||||||
marketInfo,
|
marketInfo,
|
||||||
|
|||||||
Reference in New Issue
Block a user