public async Task <BackendResponse <AccountDepositWithdrawResponse> > AccountDeposit([FromBody] AccountDepositWithdrawRequest request) { var account = _accountsCacheService.Get(request.ClientId, request.AccountId); var changeTransferLimit = _marginSettings.IsLive && request.PaymentType == PaymentType.Transfer && !IsCrypto(account.BaseAssetId); try { var transactionId = await _accountManager.UpdateBalanceAsync(account, Math.Abs(request.Amount), AccountHistoryType.Deposit, "Account deposit", request.TransactionId, changeTransferLimit); _operationsLogService.AddLog($"account deposit {request.PaymentType}", request.ClientId, request.AccountId, request.ToJson(), true.ToJson()); return(BackendResponse <AccountDepositWithdrawResponse> .Ok( new AccountDepositWithdrawResponse { TransactionId = transactionId })); } catch (Exception e) { await _log.WriteErrorAsync(nameof(AccountsBalanceController), "AccountDeposit", request?.ToJson(), e); return(BackendResponse <AccountDepositWithdrawResponse> .Error(e.Message)); } }
public async Task TestAccountUpdate_Success(string accountId, string updatedTradingConditionId, decimal updatedWithdrawTransferLimit, bool isDisabled, bool isWithdrawalDisabled) { var account = Accounts.Single(x => x.Id == accountId); var time = DateService.Now().AddMinutes(1); var accountsProjection = AssertEnv(); var updatedContract = new AccountContract() { Id = accountId, ClientId = account.ClientId, TradingConditionId = updatedTradingConditionId, BaseAssetId = account.BaseAssetId, Balance = account.Balance, WithdrawTransferLimit = updatedWithdrawTransferLimit, LegalEntity = account.LegalEntity, IsDisabled = isDisabled, ModificationTimestamp = account.ModificationTimestamp, IsWithdrawalDisabled = account.IsWithdrawalDisabled, IsDeleted = false, AdditionalInfo = "{}" }; await accountsProjection.Handle(new AccountChangedEvent(time, "test", updatedContract, AccountChangedEventTypeContract.Updated)); var resultedAccount = _accountsCacheService.Get(accountId); Assert.AreEqual(updatedTradingConditionId, resultedAccount.TradingConditionId); Assert.AreEqual(updatedWithdrawTransferLimit, resultedAccount.WithdrawTransferLimit); Assert.AreEqual(isDisabled, resultedAccount.IsDisabled); Assert.AreEqual(isWithdrawalDisabled, resultedAccount.IsWithdrawalDisabled); }
public void Check_Account_Calculations_Correct() { var position1 = TestObjectsFactory.CreateOpenedPosition("EURUSD", Accounts[0], MarginTradingTestsUtils.TradingConditionId, 1000, 1.02M); _ordersCache.Positions.Add(position1); position1.UpdateClosePrice(1.04M); position1.GetFpl(); var account = _accountsCacheService.Get(position1.AccountId); Assert.IsNotNull(account); Assert.AreEqual(1000, account.Balance); Assert.AreEqual(20, Math.Round(account.GetPnl(), 5)); Assert.AreEqual(1020, account.GetTotalCapital()); Assert.AreEqual(6.8M, account.GetUsedMargin()); Assert.AreEqual(1009.8M, account.GetMarginAvailable()); Assert.AreEqual(150M, account.GetMarginUsageLevel()); var position2 = TestObjectsFactory.CreateOpenedPosition("EURUSD", Accounts[0], MarginTradingTestsUtils.TradingConditionId, -30000, 1.02M); _ordersCache.Positions.Add(position2); position2.UpdateClosePrice(1.04M); position2.GetFpl(); Assert.IsNotNull(account); Assert.AreEqual(1000, account.Balance); Assert.AreEqual(-580, Math.Round(account.GetPnl(), 5)); Assert.AreEqual(420, Math.Round(account.GetTotalCapital(), 5)); Assert.AreEqual(214.8m, account.GetUsedMargin()); Assert.AreEqual(97.8m, Math.Round(account.GetMarginAvailable(), 5)); Assert.AreEqual(1.95531m, Math.Round(account.GetMarginUsageLevel(), 5)); }
public bool IsEnoughBalance(Order order) { _fplService.CalculateMargin(order, order.FplData); var orderMargin = order.GetMarginInit(); var accountMarginAvailable = _accountsCacheService.Get(order.ClientId, order.AccountId).GetMarginAvailable(); return(accountMarginAvailable >= orderMargin); }
public bool IsEnoughBalance(Order order) { var volumeInAccountAsset = _cfdCalculatorService.GetVolumeInAccountAsset(order.GetOrderType(), order.AccountAssetId, order.Instrument, Math.Abs(order.Volume)); var account = _accountsCacheService.Get(order.ClientId, order.AccountId); var accountAsset = _accountAssetsCacheService.GetAccountAsset(order.TradingConditionId, order.AccountAssetId, order.Instrument); return(account.GetMarginAvailable() * accountAsset.LeverageInit >= volumeInAccountAsset); }
private void MakeOrderActive(Order order) { order.OpenDate = DateTime.UtcNow; order.Status = OrderStatus.Active; var account = _accountsCacheService.Get(order.ClientId, order.AccountId); _swapCommissionService.SetCommissionRates(account.TradingConditionId, account.BaseAssetId, order); _ordersCache.ActiveOrders.Add(order); _orderActivatedEventChannel.SendEvent(this, new OrderActivatedEventArgs(order)); }
public async Task FreezeWithdrawalMargin(string accountId, string operationId, decimal amount) { var account = _accountsCacheService.Get(accountId); if (account.AccountFpl.WithdrawalFrozenMarginData.TryAdd(operationId, amount)) { account.AccountFpl.WithdrawalFrozenMargin = account.AccountFpl.WithdrawalFrozenMarginData.Values.Sum(); //TODO: think about approach //await _accountMarginFreezingRepository.TryInsertAsync(new AccountMarginFreezing(operationId, // accountId, amount)); } }
public Task <AccountStatContract> GetAccountStats(string accountId) { try { var stats = _accountsCacheService.Get(accountId); return(Task.FromResult(stats.ConvertToContract())); } catch (AccountNotFoundException ex) { throw new LogInfoOnlyException(ex.Message, ex); } }
public void UpdateOrderFpl(IOrder order, FplData fplData) { fplData.AccountBaseAssetAccuracy = _assetsCache.GetAssetAccuracy(order.AccountAssetId); fplData.QuoteRate = _cfdCalculatorService.GetQuoteRateForQuoteAsset(order.AccountAssetId, order.Instrument); var fpl = order.GetOrderType() == OrderDirection.Buy ? (order.ClosePrice - order.OpenPrice) * fplData.QuoteRate * order.GetMatchedVolume() : (order.OpenPrice - order.ClosePrice) * fplData.QuoteRate * order.GetMatchedVolume(); fplData.Fpl = Math.Round(fpl, fplData.AccountBaseAssetAccuracy); var accountAsset = _accountAssetsCacheService.GetAccountAsset(order.TradingConditionId, order.AccountAssetId, order.Instrument); fplData.MarginInit = Math.Round(order.ClosePrice * order.GetMatchedVolume() * fplData.QuoteRate / accountAsset.LeverageInit, fplData.AccountBaseAssetAccuracy); fplData.MarginMaintenance = Math.Round(order.ClosePrice * order.GetMatchedVolume() * fplData.QuoteRate / accountAsset.LeverageMaintenance, fplData.AccountBaseAssetAccuracy); fplData.OpenCrossPrice = Math.Round(order.OpenPrice * fplData.QuoteRate, order.AssetAccuracy); fplData.CloseCrossPrice = Math.Round(order.ClosePrice * fplData.QuoteRate, order.AssetAccuracy); fplData.OpenPrice = order.OpenPrice; fplData.ClosePrice = order.ClosePrice; fplData.SwapsSnapshot = order.GetSwaps(); fplData.CalculatedHash = fplData.ActualHash; fplData.TotalFplSnapshot = order.GetTotalFpl(fplData.SwapsSnapshot); var account = _accountsCacheService.Get(order.ClientId, order.AccountId); account.CacheNeedsToBeUpdated(); }
public async Task Handle(FailLiquidationInternalCommand command, IEventPublisher publisher) { var executionInfo = await _operationExecutionInfoRepository.GetAsync <LiquidationOperationData>( operationName : LiquidationSaga.OperationName, id : command.OperationId); if (executionInfo?.Data == null) { return; } _accountUpdateService.RemoveLiquidationStateIfNeeded(executionInfo.Data.AccountId, $"Liquidation [{command.OperationId}] failed ({command.Reason})", command.OperationId, executionInfo.Data.LiquidationType); _chaosKitty.Meow( $"{nameof(FailLiquidationInternalCommand)}:" + $"Publish_LiquidationFailedInternalEvent:" + $"{command.OperationId}"); publisher.PublishEvent(new LiquidationFailedEvent { OperationId = command.OperationId, CreationTime = _dateService.Now(), Reason = command.Reason, LiquidationType = command.LiquidationType.ToType <LiquidationTypeContract>(), AccountId = executionInfo.Data.AccountId, AssetPairId = executionInfo.Data.AssetPairId, Direction = executionInfo.Data.Direction?.ToType <PositionDirectionContract>(), QuoteInfo = executionInfo.Data.QuoteInfo, ProcessedPositionIds = executionInfo.Data.ProcessedPositionIds, LiquidatedPositionIds = executionInfo.Data.LiquidatedPositionIds, OpenPositionsRemainingOnAccount = _ordersCache.Positions.GetPositionsByAccountIds(executionInfo.Data.AccountId).Count, CurrentTotalCapital = _accountsCache.Get(executionInfo.Data.AccountId).GetTotalCapital(), }); _liquidationEndEventChannel.SendEvent(this, new LiquidationEndEventArgs { OperationId = command.OperationId, CreationTime = _dateService.Now(), AccountId = executionInfo.Data.AccountId, LiquidatedPositionIds = executionInfo.Data.LiquidatedPositionIds, FailReason = command.Reason, }); }
public async Task <OrderInitialParameters> GetOrderInitialParameters(string assetPairId, string accountId) { var account = _accountsCacheService.Get(accountId); var equivalentPricesSettings = GetReportingEquivalentPricesSettings(account.LegalEntity); return(await GetOrderInitialParameters(assetPairId, account.LegalEntity, equivalentPricesSettings, account.BaseAssetId)); }
private void UpdatePendingOrderMargin(Position order, FplData fplData) { fplData.AccountBaseAssetAccuracy = _assetsCache.GetAssetAccuracy(order.AccountAssetId); CalculateMargin(order, fplData); fplData.CalculatedHash = fplData.ActualHash; _accountsCacheService.Get(order.AccountId).CacheNeedsToBeUpdated(); }
private async Task Handle(FreezeAmountForWithdrawalCommand command, IEventPublisher publisher) { var(executionInfo, _) = await _operationExecutionInfoRepository.GetOrAddAsync( operationName : OperationName, operationId : command.OperationId, factory : () => new OperationExecutionInfo <WithdrawalFreezeOperationData>( operationName: OperationName, id: command.OperationId, lastModified: _dateService.Now(), data: new WithdrawalFreezeOperationData { State = OperationState.Initiated, AccountId = command.AccountId, Amount = command.Amount, } )); MarginTradingAccount account = null; try { account = _accountsCacheService.Get(command.AccountId); } catch { publisher.PublishEvent(new AmountForWithdrawalFreezeFailedEvent(command.OperationId, _dateService.Now(), command.AccountId, command.Amount, $"Failed to get account {command.AccountId}")); return; } if (executionInfo.Data.SwitchState(OperationState.Initiated, OperationState.Started)) { if (account.GetFreeMargin() >= command.Amount) { await _accountUpdateService.FreezeWithdrawalMargin(command.AccountId, command.OperationId, command.Amount); _chaosKitty.Meow(command.OperationId); publisher.PublishEvent(new AmountForWithdrawalFrozenEvent(command.OperationId, _dateService.Now(), command.AccountId, command.Amount, command.Reason)); } else { publisher.PublishEvent(new AmountForWithdrawalFreezeFailedEvent(command.OperationId, _dateService.Now(), command.AccountId, command.Amount, "Not enough free margin")); } _chaosKitty.Meow(command.OperationId); await _operationExecutionInfoRepository.Save(executionInfo); } }
private void NotifyAccountStatsChanged(string accountId) { var account = _accountsCacheService.Get(accountId); account.CacheNeedsToBeUpdated(); // not needed right now //var stats = account.ToRabbitMqContract(); //_rabbitMqNotifyService.UpdateAccountStats(new AccountStatsUpdateMessage {Accounts = new[] {stats}}); }
public OrderBackendContract[] GetAccountOpenPositions([FromBody] AccountClientIdBackendRequest request) { var account = _accountsCacheService.Get(request.ClientId, request.AccountId); var positions = _ordersCache.ActiveOrders.GetOrdersByAccountIds(account.Id).Select(item => item.ToBackendContract()).ToList(); var orders = _ordersCache.WaitingForExecutionOrders.GetOrdersByAccountIds(account.Id).Select(item => item.ToBackendContract()).ToList(); positions.AddRange(orders); var result = positions.ToArray(); return(result); }
private string GetEquivalentAsset(string clientId, string accountId) { var account = _accountsCacheService.Get(clientId, accountId); var equivalentSettings = _marginSettings.ReportingEquivalentPricesSettings.FirstOrDefault(x => x.LegalEntity == account.LegalEntity); if (string.IsNullOrEmpty(equivalentSettings?.EquivalentAsset)) { throw new Exception($"No reporting equivalent prices asset found for legalEntity: {account.LegalEntity}"); } return(equivalentSettings.EquivalentAsset); }
private void UpdateOrderFplData(IOrder order, FplData fplData) { fplData.AccountBaseAssetAccuracy = _assetsCache.GetAssetAccuracy(order.AccountAssetId); fplData.FplRate = _cfdCalculatorService.GetQuoteRateForQuoteAsset(order.AccountAssetId, order.Instrument, order.LegalEntity, order.Volume * (order.ClosePrice - order.OpenPrice) > 0); var fpl = (order.ClosePrice - order.OpenPrice) * fplData.FplRate * order.Volume; fplData.Fpl = Math.Round(fpl, fplData.AccountBaseAssetAccuracy); CalculateMargin(order, fplData); fplData.OpenPrice = order.OpenPrice; fplData.ClosePrice = order.ClosePrice; fplData.SwapsSnapshot = order.GetSwaps(); fplData.CalculatedHash = fplData.ActualHash; fplData.TotalFplSnapshot = order.GetTotalFpl(fplData.SwapsSnapshot); _accountsCacheService.Get(order.ClientId, order.AccountId).CacheNeedsToBeUpdated(); }
private void OnClosed(OrderUpdateBaseEventArgs ea) { var order = ea.Order; _threadSwitcher.SwitchThread(async() => { _clientNotifyService.NotifyOrderChanged(order); var totalFpl = order.GetTotalFpl(); var account = _accountsCacheService.Get(order.ClientId, order.AccountId); await _accountManager.UpdateBalanceAsync(account, totalFpl, AccountHistoryType.OrderClosed, $"Balance changed on order close (id = {order.Id})", order.Id); await SendOrderChangedNotification(order.ClientId, order); }); }
public override void Start() { var orders = _marginTradingBlobRepository.Read <List <Order> >(LykkeConstants.StateBlobContainer, BlobName) ?? new List <Order>(); orders.ForEach(o => { // migrate orders to add LegalEntity field // todo: can be removed once published to prod if (o.LegalEntity == null) { o.LegalEntity = _accountsCacheService.Get(o.ClientId, o.AccountId).LegalEntity; } }); _orderCache.InitOrders(orders); base.Start(); }
public Task Handle(MarketStateChangedEvent e, ICommandSender sender) { if (!e.IsEnabled) { return(Task.CompletedTask); } // resuming liquidations on corresponding accounts upon market open _accountsCacheService.GetAll() .Where(a => a.IsInLiquidation()) .SelectMany(a => _ordersCache.Positions.GetPositionsByAccountIds(a.Id)) .GroupBy(p => p.AccountId) .Where(AnyAssetRelatesToMarket) .ForEach(account => SendResumeCommand(account.Key)); return(Task.CompletedTask); #region internal functions bool AnyAssetRelatesToMarket(IGrouping <string, Position> positions) => positions .Select(p => _assetPairsCache.GetAssetPairById(p.AssetPairId)) .Any(assetPair => assetPair.MarketId == e.Id); void SendResumeCommand(string accountId) { var account = _accountsCacheService.Get(accountId); sender.SendCommand(new ResumeLiquidationInternalCommand { OperationId = account.LiquidationOperationId, CreationTime = DateTime.UtcNow, IsCausedBySpecialLiquidation = false, ResumeOnlyFailed = true, Comment = "Trying to resume liquidation because market has been opened" }, _cqrsContextNamesSettings.TradingEngine); } #endregion }
public void PerformEmailNotification(DateTime calculationTime) { _threadSwitcher.SwitchThread(async() => { await _semaphore.WaitAsync(); try { var processedCalculations = (await _overnightSwapHistoryRepository.GetAsync(calculationTime, null)) .Where(x => x.IsSuccess && x.Time >= calculationTime) .ToList(); var notifications = processedCalculations .GroupBy(x => x.ClientId) .Select(c => new OvernightSwapNotification { CliendId = c.Key, CalculationsByAccount = c.GroupBy(a => a.AccountId) .Select(a => { var account = _accountsCacheService.Get(c.Key, a.Key); if (account == null) { return(null); } return(new OvernightSwapNotification.AccountCalculations() { AccountId = a.Key, AccountCurrency = account.BaseAssetId, Calculations = a.Select(calc => { var instrumentName = _assetPairsCache.GetAssetPairByIdOrDefault(calc.Instrument)?.Name ?? calc.Instrument; return new OvernightSwapNotification.SingleCalculation { Instrument = instrumentName, Direction = calc.Direction == OrderDirection.Buy ? "Long" : "Short", Volume = calc.Volume, SwapRate = calc.SwapRate, Cost = calc.Value, PositionId = calc.OpenOrderId, }; }).ToList() }); }).Where(x => x != null).ToList() } ); var clientsWithIncorrectMail = new List <string>(); var clientsSentEmails = new List <string>(); foreach (var notification in notifications) { try { var clientEmail = await _clientAccountService.GetEmail(notification.CliendId); if (string.IsNullOrEmpty(clientEmail)) { clientsWithIncorrectMail.Add(notification.CliendId); continue; } await _emailService.SendOvernightSwapEmailAsync(clientEmail, notification); clientsSentEmails.Add(notification.CliendId); } catch (Exception e) { await _log.WriteErrorAsync(nameof(OvernightSwapNotificationService), nameof(PerformEmailNotification), e, DateTime.UtcNow); } } if (clientsWithIncorrectMail.Any()) { await _log.WriteWarningAsync(nameof(OvernightSwapNotificationService), nameof(PerformEmailNotification), $"Emails of some clients are incorrect: {string.Join(", ", clientsWithIncorrectMail)}.", DateTime.UtcNow); } if (clientsSentEmails.Any()) { await _log.WriteInfoAsync(nameof(OvernightSwapNotificationService), nameof(PerformEmailNotification), $"Emails sent to: {string.Join(", ", clientsSentEmails)}.", DateTime.UtcNow); } } finally { _semaphore.Release(); } }); }
public void Check_Account_Calculations_Correct() { var order1 = new Order { Id = Guid.NewGuid().ToString("N"), Instrument = "EURUSD", AccountId = Accounts[0].Id, ClientId = Accounts[0].ClientId, TradingConditionId = MarginTradingTestsUtils.TradingConditionId, AccountAssetId = Accounts[0].BaseAssetId, AssetAccuracy = 5, LegalEntity = "LYKKETEST", Volume = 1000, MatchedOrders = new MatchedOrderCollection(new List<MatchedOrder> { new MatchedOrder {MatchedDate = DateTime.UtcNow, Volume = 1000} }), //need for GetMatchedVolume() OpenPrice = 1.02M, Status = OrderStatus.Active, }; _ordersCache.ActiveOrders.Add(order1); order1.UpdateClosePrice(1.04M); order1.GetFpl(); var account = _accountsCacheService.Get(order1.ClientId, order1.AccountId); Assert.IsNotNull(account); Assert.AreEqual(1000, account.Balance); Assert.AreEqual(20, Math.Round(account.GetPnl(), 5)); Assert.AreEqual(1020, account.GetTotalCapital()); Assert.AreEqual(6.93333333m, account.GetUsedMargin()); Assert.AreEqual(1009.60, account.GetMarginAvailable()); Assert.AreEqual(147.11538468611316571447748352m, account.GetMarginUsageLevel()); var order2 = new Order { Id = Guid.NewGuid().ToString("N"), Instrument = "EURUSD", AccountId = Accounts[0].Id, ClientId = Accounts[0].ClientId, TradingConditionId = MarginTradingTestsUtils.TradingConditionId, AccountAssetId = Accounts[0].BaseAssetId, AssetAccuracy = 5, LegalEntity = "LYKKETEST", Volume = -30000, MatchedOrders = new MatchedOrderCollection(new List<MatchedOrder> { new MatchedOrder { MatchedDate = DateTime.UtcNow, Volume = 30000 } }), //need for GetMatchedVolume() OpenPrice = 1.02M, Status = OrderStatus.Active, }; _ordersCache.ActiveOrders.Add(order2); order2.UpdateClosePrice(1.04M); order2.GetFpl(); Assert.IsNotNull(account); Assert.AreEqual(1000, account.Balance); Assert.AreEqual(-580, Math.Round(account.GetPnl(), 5)); Assert.AreEqual(420, Math.Round(account.GetTotalCapital(), 5)); Assert.AreEqual(214.93333333m, account.GetUsedMargin()); Assert.AreEqual(97.60, Math.Round(account.GetMarginAvailable(), 5)); Assert.AreEqual(1.95409m, Math.Round(account.GetMarginUsageLevel(), 5)); }
private async Task Handle(BlockAccountsForDeletionCommand command, IEventPublisher publisher) { var executionInfo = await _operationExecutionInfoRepository.GetOrAddAsync( operationName : OperationName, operationId : command.OperationId, factory : () => new OperationExecutionInfo <DeleteAccountsOperationData>( operationName: OperationName, id: command.OperationId, lastModified: _dateService.Now(), data: new DeleteAccountsOperationData { State = OperationState.Initiated } )); //todo think how to remove state saving from commands handler here and for Withdrawal if (executionInfo.Data.SwitchState(OperationState.Initiated, OperationState.Started)) { var failedAccounts = new Dictionary <string, string>(); foreach (var accountId in command.AccountIds) { MarginTradingAccount account = null; try { account = _accountsCacheService.Get(accountId); } catch (Exception exception) { failedAccounts.Add(accountId, exception.Message); continue; } var positionsCount = _orderReader.GetPositions().Count(x => x.AccountId == accountId); if (positionsCount != 0) { failedAccounts.Add(accountId, $"Account contain {positionsCount} open positions which must be closed before account deletion."); continue; } var orders = _orderReader.GetPending().Where(x => x.AccountId == accountId).ToList(); if (orders.Any()) { var(failedToCloseOrderId, failReason) = ((string)null, (string)null); foreach (var order in orders) { try { _tradingEngine.CancelPendingOrder(order.Id, order.AdditionalInfo, command.OperationId, $"{nameof(DeleteAccountsCommandsHandler)}: force close all orders.", OrderCancellationReason.AccountInactivated); } catch (Exception exception) { failedToCloseOrderId = order.Id; failReason = exception.Message; break; } } if (failedToCloseOrderId != null) { failedAccounts.Add(accountId, $"Failed to close order [{failedToCloseOrderId}]: {failReason}."); continue; } } if (account.AccountFpl.WithdrawalFrozenMarginData.Any()) { await _log.WriteErrorAsync(nameof(DeleteAccountsCommandsHandler), nameof(BlockAccountsForDeletionCommand), account.ToJson(), new Exception("While deleting an account it contained some frozen withdrawal data. Account is deleted.")); } if (account.AccountFpl.UnconfirmedMarginData.Any()) { await _log.WriteErrorAsync(nameof(DeleteAccountsCommandsHandler), nameof(BlockAccountsForDeletionCommand), account.ToJson(), new Exception("While deleting an account it contained some unconfirmed margin data. Account is deleted.")); } if (account.Balance != 0) { await _log.WriteErrorAsync(nameof(DeleteAccountsCommandsHandler), nameof(BlockAccountsForDeletionCommand), account.ToJson(), new Exception("While deleting an account it's balance on side of TradingCore was non zero. Account is deleted.")); } if (!await UpdateAccount(account, true, r => failedAccounts.Add(accountId, r), command.Timestamp)) { continue; } } publisher.PublishEvent(new AccountsBlockedForDeletionEvent( operationId: command.OperationId, eventTimestamp: _dateService.Now(), failedAccountIds: failedAccounts )); _chaosKitty.Meow($"{nameof(BlockAccountsForDeletionCommand)}: " + "Save_OperationExecutionInfo: " + $"{command.OperationId}"); await _operationExecutionInfoRepository.Save(executionInfo); } }
private void NotifyAccountStatsChanged(string clientId, string accountId) { var account = _accountsCacheService.Get(clientId, accountId); NotifyAccountStatsChanged(account); }
private async Task <Order> ExecuteOrderByMatchingEngineAsync(Order order, IMatchingEngineBase matchingEngine, bool checkStopout, OrderModality modality = OrderModality.Regular) { //TODO: think how not to execute one order twice!!! var now = _dateService.Now(); //just in case ) if (order.OrderType != OrderType.Market && order.Validity.HasValue && now.Date > order.Validity.Value.Date) { order.Expire(now); _orderCancelledEventChannel.SendEvent(this, new OrderCancelledEventArgs(order, new OrderCancelledMetadata { Reason = OrderCancellationReasonContract.Expired })); return(order); } order.StartExecution(_dateService.Now(), matchingEngine.Id); _orderExecutionStartedEvenChannel.SendEvent(this, new OrderExecutionStartedEventArgs(order)); if (order.PositionsToBeClosed.Any()) { var netVolume = 0M; var rejectReason = default(OrderRejectReason?); foreach (var positionId in order.PositionsToBeClosed) { if (!_ordersCache.Positions.TryGetPositionById(positionId, out var position)) { rejectReason = OrderRejectReason.ParentPositionDoesNotExist; continue; } if (position.Status != PositionStatus.Active) { rejectReason = OrderRejectReason.ParentPositionIsNotActive; continue; } netVolume += position.Volume; position.StartClosing(_dateService.Now(), order.OrderType.GetCloseReason(), order.Originator, ""); } if (netVolume == 0M && rejectReason.HasValue) { order.Reject(rejectReason.Value, rejectReason.Value == OrderRejectReason.ParentPositionDoesNotExist ? "Related position does not exist" : "Related position is not active", "", _dateService.Now()); _orderRejectedEventChannel.SendEvent(this, new OrderRejectedEventArgs(order)); return(order); } // there is no any global lock of positions / orders, that's why it is possible to have concurrency // in position close process // since orders, that have not empty PositionsToBeClosed should close positions and not open new ones // volume of executed order should be equal to position volume, but should have opposite sign if (order.Volume != -netVolume) { var metadata = new OrderChangedMetadata { OldValue = order.Volume.ToString("F2"), UpdatedProperty = OrderChangedProperty.Volume }; order.ChangeVolume(-netVolume, _dateService.Now(), order.Originator); _orderChangedEventChannel.SendEvent(this, new OrderChangedEventArgs(order, metadata)); } } var equivalentRate = _cfdCalculatorService.GetQuoteRateForQuoteAsset(order.EquivalentAsset, order.AssetPairId, order.LegalEntity); var fxRate = _cfdCalculatorService.GetQuoteRateForQuoteAsset(order.AccountAssetId, order.AssetPairId, order.LegalEntity); order.SetRates(equivalentRate, fxRate); var shouldOpenNewPosition = ShouldOpenNewPosition(order); if (modality == OrderModality.Regular && order.Originator != OriginatorType.System) { try { _validateOrderService.MakePreTradeValidation( order, shouldOpenNewPosition, matchingEngine); } catch (ValidateOrderException ex) { RejectOrder(order, ex.RejectReason, ex.Message, ex.Comment); return(order); } } var matchedOrders = await matchingEngine.MatchOrderAsync(order, shouldOpenNewPosition, modality); if (!matchedOrders.Any()) { RejectOrder(order, OrderRejectReason.NoLiquidity, "No orders to match", ""); return(order); } if (matchedOrders.SummaryVolume < Math.Abs(order.Volume)) { if (order.FillType == OrderFillType.FillOrKill) { RejectOrder(order, OrderRejectReason.NoLiquidity, "Not fully matched", ""); return(order); } else { order.PartiallyExecute(_dateService.Now(), matchedOrders); _ordersCache.InProgress.Add(order); return(order); } } if (order.Status == OrderStatus.ExecutionStarted) { var accuracy = _assetPairsCache.GetAssetPairByIdOrDefault(order.AssetPairId)?.Accuracy ?? AssetPairsCache.DefaultAssetPairAccuracy; order.Execute(_dateService.Now(), matchedOrders, accuracy); _orderExecutedEventChannel.SendEvent(this, new OrderExecutedEventArgs(order)); if (checkStopout) { var account = _accountsCacheService.Get(order.AccountId); var accountLevel = account.GetAccountLevel(); if (accountLevel == AccountLevel.StopOut) { CommitStopOut(account, null); } else if (accountLevel > AccountLevel.None) { _marginCallEventChannel.SendEvent(this, new MarginCallEventArgs(account, accountLevel)); } } } return(order); }
public void CalculateAndChargeSwaps() { _currentStartTimestamp = _dateService.Now(); var filteredOrders = GetOrdersForCalculation(); //start calculation in a separate thread _threadSwitcher.SwitchThread(async() => { using (await _mutex.LockAsync()) { foreach (var accountOrders in filteredOrders.GroupBy(x => x.AccountId)) { var clientId = accountOrders.First().ClientId; MarginTradingAccount account; try { account = _accountsCacheService.Get(clientId, accountOrders.Key); } catch (Exception ex) { await ProcessFailedOrders(accountOrders, accountOrders.Key, null, ex); continue; } foreach (var ordersByInstrument in accountOrders.GroupBy(x => x.Instrument)) { var firstOrder = ordersByInstrument.FirstOrDefault(); IAccountAssetPair accountAssetPair; try { accountAssetPair = _accountAssetsCacheService.GetAccountAsset( firstOrder?.TradingConditionId, firstOrder?.AccountAssetId, firstOrder?.Instrument); } catch (Exception ex) { await ProcessFailedOrders(ordersByInstrument, account.Id, ordersByInstrument.Key, ex); continue; } foreach (OrderDirection direction in Enum.GetValues(typeof(OrderDirection))) { var orders = ordersByInstrument.Where(order => order.GetOrderType() == direction).ToList(); if (orders.Count == 0) { continue; } try { await ProcessOrders(orders, ordersByInstrument.Key, account, accountAssetPair, direction); } catch (Exception ex) { await ProcessFailedOrders(orders, account.Id, ordersByInstrument.Key, ex); continue; } } } } } }); }
public async Task ExecuteAsync(IEventPublisher failurePublisher, string accountId, string operationId, string reason) { if (failurePublisher == null) { throw new ArgumentNullException(nameof(failurePublisher)); } if (string.IsNullOrEmpty(accountId)) { throw new ArgumentNullException(nameof(accountId)); } if (string.IsNullOrEmpty(operationId)) { throw new ArgumentNullException(nameof(operationId)); } var executionInfo = await _operationExecutionInfoRepository.GetAsync <LiquidationOperationData>( LiquidationSaga.OperationName, operationId); if (executionInfo?.Data == null) { await _log.WriteWarningAsync(nameof(LiquidationFailureExecutor), nameof(ExecuteAsync), new { operationId, accountId }.ToJson(), $"Unable to execute failure. Liquidation execution info was not found."); return; } _accountUpdateService.RemoveLiquidationStateIfNeeded(accountId, reason, operationId, executionInfo.Data.LiquidationType); var account = _accountsCache.Get(accountId); failurePublisher.PublishEvent(new LiquidationFailedEvent { OperationId = operationId, CreationTime = _dateService.Now(), Reason = reason, LiquidationType = executionInfo.Data.LiquidationType.ToType <LiquidationTypeContract>(), AccountId = executionInfo.Data.AccountId, AssetPairId = executionInfo.Data.AssetPairId, Direction = executionInfo.Data.Direction?.ToType <PositionDirectionContract>(), QuoteInfo = executionInfo.Data.QuoteInfo, ProcessedPositionIds = executionInfo.Data.ProcessedPositionIds, LiquidatedPositionIds = executionInfo.Data.LiquidatedPositionIds, OpenPositionsRemainingOnAccount = _ordersCache.Positions.GetPositionsByAccountIds(executionInfo.Data.AccountId).Count, CurrentTotalCapital = account.GetTotalCapital(), }); await _log.WriteInfoAsync(nameof(LiquidationFailureExecutor), nameof(ExecuteAsync), new { account, operationId, reason }.ToJson(), $"Successfully published {nameof(LiquidationFailedEvent)} event"); _liquidationEndEventChannel.SendEvent(this, new LiquidationEndEventArgs { OperationId = operationId, CreationTime = _dateService.Now(), AccountId = executionInfo.Data.AccountId, LiquidatedPositionIds = executionInfo.Data.LiquidatedPositionIds, FailReason = reason, }); await _log.WriteInfoAsync(nameof(LiquidationFailureExecutor), nameof(ExecuteAsync), new { account, operationId, reason }.ToJson(), $"Successfully published {nameof(LiquidationEndEventArgs)} event"); }
public void CalculateAndChargeSwaps() { _currentStartTimestamp = _dateService.Now(); var filteredOrders = GetOrdersForCalculation(); //start calculation in a separate thread _threadSwitcher.SwitchThread(async() => { await _semaphore.WaitAsync(); try { await _log.WriteInfoAsync(nameof(OvernightSwapService), nameof(CalculateAndChargeSwaps), $"Started, # of orders: {filteredOrders.Count}.", DateTime.UtcNow); foreach (var accountOrders in filteredOrders.GroupBy(x => x.AccountId)) { var clientId = accountOrders.First().ClientId; var orders = accountOrders.ToList(); MarginTradingAccount account; try { account = _accountsCacheService.Get(clientId, accountOrders.Key); } catch (Exception ex) { await ProcessFailedOrders(orders, clientId, accountOrders.Key, null, ex); continue; } foreach (var order in orders) { IAccountAssetPair accountAssetPair; try { accountAssetPair = _accountAssetsCacheService.GetAccountAsset( order?.TradingConditionId, order?.AccountAssetId, order?.Instrument); } catch (Exception ex) { await ProcessFailedOrder(order, clientId, account.Id, order.AccountId, ex); continue; } try { await ProcessOrder(order, account, accountAssetPair); } catch (Exception ex) { await ProcessFailedOrder(order, clientId, account.Id, order.AccountId, ex); continue; } } } await ClearOldState(); await _log.WriteInfoAsync(nameof(OvernightSwapService), nameof(CalculateAndChargeSwaps), $"Finished, # of calculations: {_overnightSwapCache.GetAll().Count(x => x.Time >= _currentStartTimestamp)}.", DateTime.UtcNow); } finally { _semaphore.Release(); } }); }
public MarginTradingAccountBackendContract GetAccount(string clientId, string accountId) { return(_accountsCacheService.Get(clientId, accountId).ToFullBackendContract(_settings.IsLive)); }
private void SendPositionHistoryEvent(Position position, PositionHistoryTypeContract historyType, decimal chargedPnl, string orderAdditionalInfo, Order dealOrder = null, decimal?dealVolume = null, PositionOpenMetadata metadata = null) { DealContract deal = null; if (dealOrder != null && dealVolume != null) { var sign = position.Volume > 0 ? 1 : -1; var accountBaseAssetAccuracy = AssetsConstants.DefaultAssetAccuracy; var fpl = Math.Round((dealOrder.ExecutionPrice.Value - position.OpenPrice) * dealOrder.FxRate * dealVolume.Value * sign, accountBaseAssetAccuracy); var balanceDelta = fpl - Math.Round(chargedPnl, accountBaseAssetAccuracy); var dealId = historyType == PositionHistoryTypeContract.Close ? position.Id : _identityGenerator.GenerateAlphanumericId(); deal = new DealContract { DealId = dealId, PositionId = position.Id, Volume = dealVolume.Value, Created = dealOrder.Executed.Value, OpenTradeId = position.OpenTradeId, OpenOrderType = position.OpenOrderType.ToType <OrderTypeContract>(), OpenOrderVolume = position.OpenOrderVolume, OpenOrderExpectedPrice = position.ExpectedOpenPrice, CloseTradeId = dealOrder.Id, CloseOrderType = dealOrder.OrderType.ToType <OrderTypeContract>(), CloseOrderVolume = dealOrder.Volume, CloseOrderExpectedPrice = dealOrder.Price, OpenPrice = position.OpenPrice, OpenFxPrice = position.OpenFxPrice, ClosePrice = dealOrder.ExecutionPrice.Value, CloseFxPrice = dealOrder.FxRate, Fpl = fpl, PnlOfTheLastDay = balanceDelta, AdditionalInfo = dealOrder.AdditionalInfo, Originator = dealOrder.Originator.ToType <OriginatorTypeContract>() }; var account = _accountsCacheService.Get(position.AccountId); _cqrsSender.PublishEvent(new PositionClosedEvent(account.Id, account.ClientId, deal.DealId, position.AssetPairId, balanceDelta)); _accountUpdateService.FreezeUnconfirmedMargin(position.AccountId, deal.DealId, balanceDelta) .GetAwaiter().GetResult(); //todo consider making this async or pass to broker } var positionContract = _convertService.Convert <Position, PositionContract>(position, o => o.ConfigureMap(MemberList.Destination).ForMember(x => x.TotalPnL, c => c.Ignore())); positionContract.TotalPnL = position.GetFpl(); var historyEvent = new PositionHistoryEvent { PositionSnapshot = positionContract, Deal = deal, EventType = historyType, Timestamp = _dateService.Now(), ActivitiesMetadata = metadata?.ToJson(), OrderAdditionalInfo = orderAdditionalInfo, }; _rabbitMqNotifyService.PositionHistory(historyEvent); }