public bool CloseOrder(MarketOrder order, decimal price, PositionExitReason exitReason) { using (var ctx = DatabaseContext.Instance.Make()) { var account = ctx.ACCOUNT.FirstOrDefault(ac => ac.ID == order.AccountID); if (account == null) { Logger.ErrorFormat("Закрытие ордера #{0}: невозможно прочитать данные счета ({1})", order.ID, order.AccountID); return(false); } // провести ордер через биллинг ORDER_BILL bill = null; if (order.State == PositionState.Opened) { bill = BillingManager.ProcessPriceForOrderClosing(order, LinqToEntity.DecorateAccount(account), ctx); } // посчитать результат // и обновить объект order.State = PositionState.Closed; order.PriceExit = (float?)price; var deltaAbs = order.Side * (order.PriceExit.Value - order.PriceEnter); order.ResultPoints = DalSpot.Instance.GetPointsValue(order.Symbol, deltaAbs); var deltaDepo = deltaAbs * order.Volume; var quotes = QuoteStorage.Instance.ReceiveAllData(); string errorStr; var resultedDepo = DalSpot.Instance.ConvertToTargetCurrency(order.Symbol, false, account.Currency, deltaDepo, quotes, out errorStr, false); if (!resultedDepo.HasValue) { Logger.ErrorFormat("#{0} ({1} {2}{3}, {4:f1} пп): ошибка расчета прибыли в валюте депозита - {5}", order.ID, order.Side > 0 ? "B" : "S", order.Symbol, order.Volume, order.ResultPoints, errorStr); return(false); } order.ResultDepo = (float)resultedDepo.Value; //order.Swap = (float)swap; order.ExitReason = exitReason; order.TimeExit = DateTime.Now; var posClosed = LinqToEntity.UndecorateClosedPosition(order); POSITION pos = null; try { // занести ордер в список закрытых позиций (создать новую запись "истории") ctx.POSITION_CLOSED.Add(posClosed); // удалить открытый ордер pos = ctx.POSITION.FirstOrDefault(p => p.ID == order.ID); if (pos == null) { Logger.ErrorFormat("CloseOrder - позиция {0} не найдена", order.ID); ServiceManagerClientManagerProxy.Instance.CloseOrderResponse(null, RequestStatus.ServerError, "crudsav"); return(false); } ctx.POSITION.Remove(pos); // посчитать профиты if (bill != null) { BillingManager.ProcessOrderClosing(order, account, bill, ctx, quotes, brokerRepository.BrokerCurrency); } // сохранить изменения ctx.SaveChanges(); // обновить баланс var resultAbs = Math.Abs(order.ResultDepo); if (!UpdateAccountBalance(ctx, account, (decimal)resultAbs, order.ResultDepo >= 0 ? BalanceChangeType.Profit : BalanceChangeType.Loss, string.Format("результат сделки #{0}", posClosed.ID), DateTime.Now, order.ID)) { Logger.ErrorFormat("Не удалось применить обновление баланса #{0}", posClosed.ID); } } catch (OptimisticConcurrencyException ex) { Logger.Error("CloseOrder - OptimisticConcurrencyException", ex); ctx.Entry(posClosed).State = EntityState.Modified; ((IObjectContextAdapter)ctx).ObjectContext.Refresh(RefreshMode.ClientWins, posClosed); if (pos != null) { ctx.Entry(pos).State = EntityState.Modified; ((IObjectContextAdapter)ctx).ObjectContext.Refresh(RefreshMode.ClientWins, pos); } ctx.SaveChanges(); } catch (Exception ex) { Logger.ErrorFormat("Ошибка закрытия позиции {0} (счет #{1}) (фиксация в БД): {2}", order.ID, order.AccountID, ex); ServiceManagerClientManagerProxy.Instance.CloseOrderResponse(null, RequestStatus.ServerError, "crudsave"); return(false); } } // уведомить клиента ServiceManagerClientManagerProxy.Instance.CloseOrderResponse(order, RequestStatus.OK, ""); // разослать торговый сигнал MakeOrderClosedSignal(order.AccountID, order.ID, (float)price); return(true); }
/// <summary> /// Закрытие сделок /// </summary> /// <param name="strId">Уникальные идентификаторы закрываемых сделок, перечисленные через запятую</param> /// <param name="timeExit">Время выхода</param> /// <param name="exitReason">Symbol, Side (в виде Ask и Bid), Price</param> /// <param name="lstPrice">Причина закрытия сделки, указанная пользователем</param> public List <string> ClosingPositions(string strId, DateTime timeExit, PositionExitReason exitReason, List <Tuple <string, int, float> > lstPrice) { Logger.Info("Начинаем закрывать сделки " + strId); var result = new List <string>(); var id = strId.ToIntArrayUniform(); try { using (var ctx = DatabaseContext.Instance.Make()) { // Вытаскиваем все открытые сделки, которые нужно закрыть var selOrders = new List <MarketOrder>(); // ReSharper disable LoopCanBeConvertedToQuery foreach (var order in ctx.POSITION.Where(x => id.Contains(x.ID))) { selOrders.Add(LinqToEntity.DecorateOrder(order)); } // ReSharper restore LoopCanBeConvertedToQuery if (selOrders.Count != id.Length) { Logger.Error("ClosingPositions() - в таблице 'POSITION' не найдены некоторые или все сделки с идентификаторами " + strId); } // Группируем все сделки по счётам var selOrderGroupByAccount = selOrders.GroupBy(x => x.AccountID); // Перебираем все счета foreach (var orderGroup in selOrderGroupByAccount) { // Список удачно закрытых сделок в текущем счёте var successClosedPositions = new List <string>(); var acc = accountRepository.GetAccount(orderGroup.Key); if (acc == null) { Logger.Error("ClosingPositions() - не удалось получить счёт " + orderGroup.Key); continue; } Logger.Info("Начинаем закрывать сделки в счёте " + orderGroup.Key); // Перебираем все сделки в текущем счёте foreach (var order in orderGroup) { #region //Ищем цену выхода, указанную пользователем, для сделок с таким тикером и направлением var priceExitTuple = lstPrice.FirstOrDefault(x => x.Item1 == order.Symbol && x.Item2 == order.Side); if (priceExitTuple == null) { Logger.Error(string.Format("ClosingPositions() - не найдена цена выхода, указанная пользователем, для сделок счёта {0} с тикером {1} и направлением {2}", order.ID, order.Symbol, order.Side)); continue; } var closedOrder = order.MakeCopy(); closedOrder.State = PositionState.Closed; closedOrder.TimeExit = timeExit; closedOrder.PriceExit = priceExitTuple.Item3; closedOrder.ExitReason = exitReason; // посчитать прибыль string errorStr; if (!DealProfitCalculator.CalculateOrderProfit(closedOrder, acc.Currency, priceExitTuple.Item3, out errorStr)) { if (!string.IsNullOrEmpty(errorStr)) { Logger.Error("Сделка " + closedOrder.ID + " не будет закрыта - не удалось пересчитать прибыль : " + errorStr); } continue; } var balance = new BALANCE_CHANGE { AccountID = order.AccountID, ChangeType = closedOrder.ResultBase > 0 ? (int)BalanceChangeType.Profit : (int)BalanceChangeType.Loss, Description = string.Format("результат сделки #{0}", order.ID), Amount = (decimal)Math.Abs(closedOrder.ResultDepo), ValueDate = closedOrder.TimeExit.Value }; // убрать сделку из числа открытых, добавить закрытую и добавить проводку по счету try { // убрать var pos = ctx.POSITION.FirstOrDefault(p => p.ID == order.ID); ctx.POSITION.Remove(pos); Logger.Info("запись о сделке " + order.ID + " удалена из таблици POSITION"); ctx.POSITION_CLOSED.Add(LinqToEntity.UndecorateClosedPosition(closedOrder)); Logger.Info("запись о сделке " + order.ID + " добавленав таблицу POSITION_CLOSED"); // добавить проводку по счету ctx.BALANCE_CHANGE.Add(balance); var acBase = ctx.ACCOUNT.FirstOrDefault(ac => ac.ID == order.AccountID); if (acBase == null) { Logger.Error("ClosingPositions() - не удалось найти счёт " + order.AccountID + " в таблице 'ACCOUNT', что бы добавить проводку"); continue; } acBase.Balance += (decimal)closedOrder.ResultDepo; Logger.Info("Баланс счёта " + order.AccountID + " изменён на величину " + (decimal)closedOrder.ResultDepo); } catch (Exception ex) { Logger.Error("ClosingPositions() - Ошибка при попытке убрать сделку из числа открытых, добавить закрытую и добавить проводку по счету", ex); continue; } #endregion successClosedPositions.Add(order.ID.ToString()); Logger.Error("Сделка " + order.ID + " отредактирована удачно"); } ReCalculateAccountBalance(ctx, acc.ID); Logger.Info("Начинаем сохранять в базу данных изменения по счёту " + orderGroup.Key); ctx.SaveChanges(); result.AddRange(successClosedPositions); } if (result.Count == 0) { Logger.Info("Не удалось закрыть ни одной из указанных сделок"); } else { Logger.Info("Изменения сохранены. Из указанных сделок " + strId + " закрыты следующие: " + string.Join(", ", result)); } } } catch (Exception ex) { Logger.Error("ClosingPositions() - возникла ошибка при попытке сохранить изменения в базу данных. Не удалось закрыть сделки " + strId, ex); } return(result); }
private static void SaveTrackInDatabase(RobotContextBacktest context, int accountId, List <BalanceChange> transfers, int transfersInDbCount) { try { Logger.InfoFormat("Сохранение изменений в БД для счета {0}", accountId); int nextPosId; using (var conn = DatabaseContext.Instance.Make()) { nextPosId = Math.Max(conn.POSITION.Max(p => p.ID), conn.POSITION_CLOSED.Max(p => p.ID)) + 1; } Logger.InfoFormat("Запись {0} позиций для счета {1}", context.PosHistory.Count, accountId); // закрытые ордера var listPos = new List <POSITION_CLOSED>(); foreach (var pos in context.PosHistory) { var orderClosed = LinqToEntity.UndecorateClosedPosition(pos); orderClosed.ID = ++nextPosId; listPos.Add(orderClosed); } using (var conn = DatabaseContext.Instance.Make()) { conn.BulkInsert(listPos); conn.SaveChanges(); } // трансферы... var listTrans = transfers.Skip(transfersInDbCount).Select(t => new BALANCE_CHANGE { AccountID = accountId, Amount = t.Amount, ChangeType = (int)t.ChangeType, ValueDate = t.ValueDate, }).ToList(); // + трансферы по закрытым ордерам foreach (var pos in listPos) { var transfer = new BALANCE_CHANGE { AccountID = accountId, Amount = Math.Abs(pos.ResultDepo), ChangeType = (int)(pos.ResultDepo >= 0 ? BalanceChangeType.Profit : BalanceChangeType.Loss), ValueDate = pos.TimeExit, Position = pos.ID, }; listTrans.Add(transfer); } using (var conn = DatabaseContext.Instance.Make()) { conn.BulkInsert(listTrans); conn.SaveChanges(); } // открытые сделки - как есть using (var conn = DatabaseContext.Instance.Make()) { foreach (var pos in context.Positions) { var orderOpened = LinqToEntity.UndecorateOpenedPosition(pos); orderOpened.ID = ++nextPosId; conn.POSITION.Add(orderOpened); } conn.SaveChanges(); } Logger.InfoFormat("Сохранение успешно для счета {0}: {1} сделок сохранено", accountId, context.PosHistory.Count + context.Positions.Count); } catch (Exception ex) { Logger.Error("Ошибка сохранения трека пользователя в БД", ex); } }