public override Task <TradeActionResponse> DoTradeAction(TradeActionRequest request, ServerCallContext context) { var response = new TradeActionResponse(); switch (request.ActionName) { case StaticData.Constants._Actions._Trade.GET: response.Trade = GetDetailedTradeView(request.TradeUserInfo, context); var tradeUserState = GetTradeUserState(request.TradeUserInfo, context).Result; if (tradeUserState.Description.Length == 0) { // add IOI request.TradeUserInfo.State = new State { Description = _States._TradeUser.IOI }; AddTradeUserState(request.TradeUserInfo, context); } response.NextActions = GetTradeActions(request.TradeUserInfo, context).Result; break; default: break; } return(Task.FromResult(response)); }
public async Task <TradeActionResponse> ExecuteAction(bool simulate) { // Decrement from account, increment to account. this.FromAccount.Balance -= this.EstimatedCost + this.TransactionAmount; this.ToAccount.Balance += this.TransactionAmount; var resp = new TradeActionResponse() { ExecutionSuccessful = true, Transactions = null }; using (var ctx = new RBBotContext()) { ctx.Entry(this.FromAccount).State = System.Data.Entity.EntityState.Modified; ctx.Entry(this.ToAccount).State = System.Data.Entity.EntityState.Modified; await ctx.SaveChangesAsync(); if (!simulate) { #warning Still to implement the actual thing!! } throw new NotImplementedException(); } return(resp); }
public async Task <TradeActionResponse> ExecuteAction(bool simulate) { // Either from or to account must be the base currency. Else throw exception. if (this.FromAccount.Currency != this.BaseCurrency && this.ToAccount.Currency != this.BaseCurrency) { throw new NotSupportedException($"It is not supported to have trade actions where {this.BaseCurrency.Code} is not involved"); } var resp = new TradeActionResponse(); // This is where money is spent... be cautious ExchangeOrderResponse orderResponse = null; if (simulate == false) { orderResponse = await TradingIntegration.PlaceOrder(this.OrderType, this.TransactionAmount, this.BaseCurrency, this.TradePair); } else { orderResponse = new ExchangeOrderResponse() { Success = true, ExternalTransactionId = "Simulation-Tx" }; orderResponse.Fee = TradingIntegration.EstimateTransactionFee(this.OrderType, this.TransactionAmount, this.BaseCurrency, this.TradePair); } if (orderResponse.Success == false) { resp.ExecutionSuccessful = false; return(resp); } ; // Return nothing if there was a problem. // If the order type is a sell, then the FromAccount decreases and the ToAccount balance increases. // This means that if we're selling, then we'll have less ETH (from amount) and more BTC (to amount) // Therefore the from amount difference is just the TransactionAmount and +ve/-ve according to whether its a buy or sell. var fromAmount = this.TransactionAmount * (this.OrderType == ExchangeOrderType.Buy ? 1 : -1); var toAmount = -1 * fromAmount; // var exchangeRate = this.TradePair.LatestPrice; // Now we do the currency conversion. We'll do the conversion on the non-preferred currency. if (this.FromAccount.Currency == this.BaseCurrency) { toAmount = toAmount / exchangeRate; } else { fromAmount = fromAmount / exchangeRate; } var fromAccountFee = orderResponse.Fee.Currency == this.TradePair.TradePair.FromCurrency ? orderResponse.Fee.Amount : 0m; var toAccountFee = orderResponse.Fee.Currency == this.TradePair.TradePair.ToCurrency ? orderResponse.Fee.Amount : 0m; var fromAccountBalanceBefore = FromAccount.Balance; var toAccountBalanceBefore = ToAccount.Balance; var fromAccountBalanceAfter = fromAccountBalanceBefore + fromAmount - fromAccountFee; var toAccountBalanceAfter = toAccountBalanceBefore + toAmount - toAccountFee; var tx = new TradeOpportunityTransaction() { CreationDate = DateTime.UtcNow, ExchangeRate = exchangeRate, FromAccountId = FromAccount.Id, ToAccountId = ToAccount.Id, ExternalTransactionId = orderResponse.ExternalTransactionId, ExecutedOnExchangeId = this.TradePair.Exchange.Id, IsReal = !simulate, FromAccountFee = fromAccountFee, ToAccountFee = toAccountFee, FromAmount = fromAmount, ToAmount = toAmount, FromAccountBalanceBeforeTx = fromAccountBalanceBefore, ToAccountBalanceBeforeTx = toAccountBalanceBefore, EstimatedFromAccountBalanceAfterTx = fromAccountBalanceAfter, EstimatedToAccountBalanceAfterTx = toAccountBalanceAfter, }; FromAccount.Balance = fromAccountBalanceAfter; ToAccount.Balance = toAccountBalanceAfter; FromAccount.LastUpdate = DateTime.UtcNow; ToAccount.LastUpdate = DateTime.UtcNow; // Return the response. resp.Transactions = new TradeOpportunityTransaction[] { tx }; resp.ExecutionSuccessful = true; return(resp); }
public static async Task EndTradeOpportunity(TradeOpportunity opportunity, TradeOpportunityState finalState, TradeOpportunityValue finalOpportunityValue = null, TradeActionResponse tradeActionResponse = null) { // Note: This was initially written in a way that I first remove the trade opp from the concurrent dictionary and then write to the DB. // It was wrong as by the time we got back a response from the db, other same opportunities were being written! This caused a lot of same opportunities to be written! // Therefore what we do here is first we singal the removal from the database and only once this is done we try to remove it from the concurrent dictionary. // For this reason the trade opportunity is immediately locked. // This is the part we persist to the db. We first wait for // any possible semaphore on the opportunity and release it as soon as we're done. await opportunity.LockingSemaphore.WaitAsync(); // It might have happened that this opportunity was already written to the db. Ignore this call. if (opportunity.IsDbExecutedWritten) { return; } try { // Persist to db using (var ctx = new RBBotContext()) { opportunity.TradeOpportunityStateId = finalState.Id; opportunity.EndTime = DateTime.UtcNow; // If a final value is specified, add it now. if (finalOpportunityValue != null) { finalOpportunityValue.TradeOpportunityId = opportunity.Id; ctx.TradeOpportunityValues.Add(finalOpportunityValue); } // With transactions we also look into the accounts and update them. if (tradeActionResponse != null) { tradeActionResponse.Transactions.ToList().ForEach(x => x.TradeOpportunity = opportunity); ctx.TradeOpportunityTransactions.AddRange(tradeActionResponse.Transactions); foreach (var acc in opportunity.LatestOpportunity.GetAffectedAccounts()) { ctx.TradeAccounts.Attach(acc); ctx.Entry(acc).State = System.Data.Entity.EntityState.Modified; } } ctx.TradeOpportunities.Attach(opportunity); ctx.Entry(opportunity).State = System.Data.Entity.EntityState.Modified; await ctx.SaveChangesAsync(); opportunity.IsDbExecutedWritten = true; // This is an unmapped property that is used to make sure that if by any chance this trade opportunity is tried to be ended again, it won't succeed! // Now that we've written to the db, try removing it from the concurrent dictionary. TradeOpportunity time = null; bool removedSuccess = tradeOpportunities.TryRemove(opportunity.LatestOpportunity.UniqueIdentifier, out time); if ((removedSuccess) && (OnOpportunityExpired != null)) // If the opportunity could be removed, then raise event!. { OnOpportunityExpired(opportunity); } } } finally { opportunity.LockingSemaphore.Release(); } }