/// <summary> /// получить закрытые ордера по дате закрытия /// </summary> public RequestStatus GetHistoryOrdersByCloseDate(int?accountId, DateTime?startDate, out List <MarketOrder> orders) { using (var ctx = DatabaseContext.Instance.Make()) { try { var positions = from pos in ctx.POSITION_CLOSED where pos.AccountID == (accountId ?? pos.AccountID) && (pos.TimeExit >= startDate) || (!startDate.HasValue) select pos; orders = new List <MarketOrder>(); // ReSharper disable LoopCanBeConvertedToQuery foreach (var position in positions) // ReSharper restore LoopCanBeConvertedToQuery { orders.Add(LinqToEntity.DecorateOrder(position)); } return(RequestStatus.OK); } catch (Exception ex) { orders = null; Logger.ErrorFormat("Ошибка в GetHistoryOrdersByCloseDate({0}): {1}", accountId, ex); return(RequestStatus.ServerError); } } }
/// <summary> /// получить список открытых позиций /// </summary> public RequestStatus GetMarketOrders(int accountId, out List <MarketOrder> orders) { using (var ctx = DatabaseContext.Instance.Make()) try { var positions = from pos in ctx.POSITION where (pos.State == (int)(PositionState.Opened) || pos.State == (int)(PositionState.StartOpened) || pos.State == (int)(PositionState.StartClosed)) && pos.AccountID == accountId select pos; orders = new List <MarketOrder>(); // ReSharper disable LoopCanBeConvertedToQuery foreach (var position in positions) // ReSharper restore LoopCanBeConvertedToQuery { orders.Add(LinqToEntity.DecorateOrder(position)); } return(RequestStatus.OK); } catch (Exception ex) { orders = null; Logger.ErrorFormat("Ошибка в GetMarketOrders({0}): {1}", accountId, ex); return(RequestStatus.ServerError); } }
/// <summary> /// проверить SL, TP, трейлинги /// </summary> private void CheckOrders() { if (!WorkingDay.Instance.IsWorkingDay(DateTime.Now)) { return; } var curPrices = QuoteStorage.Instance.ReceiveAllData(); try { using (var ctx = DatabaseContext.Instance.Make()) { // позиции foreach (var order in ctx.POSITION) { if ((PositionState)order.State != PositionState.Opened) { continue; } try { tradeManager.CheckOrder(LinqToEntity.DecorateOrder(order), curPrices); } catch (Exception ex) { Logger.Error("AccountCheckStream.CheckOrder() error", ex); } } // отложенные ордера foreach (var order in ctx.PENDING_ORDER) { try { tradeManager.CheckPendingOrder(LinqToEntity.DecoratePendingOrder(order), curPrices); } catch (Exception ex) { Logger.Error("AccountCheckStream.CheckPendingOrder() error", ex); } } } } catch (Exception ex) { Logger.Error("Ошибка в CheckOrders()", ex); } }
/// <summary> /// Заполняет список allOrders позициями (сделками) из БД /// </summary> private void MakeOrders() { allOrders = new List <MarketOrder>(); using (var ctx = DatabaseContext.Instance.Make()) { foreach (var pos in ctx.POSITION) { allOrders.Add(LinqToEntity.DecorateOrder(pos)); } foreach (var pos in ctx.POSITION_CLOSED) { allOrders.Add(LinqToEntity.DecorateOrder(pos)); } } }
public bool CloseOrder(int orderId, decimal price, PositionExitReason exitReason) { MarketOrder orderDecor; using (var ctx = DatabaseContext.Instance.Make()) { var orderQuery = ctx.POSITION.Where(p => p.ID == orderId).ToList(); if (orderQuery.Count == 0) { Logger.ErrorFormat("CloseOrder(Id = {0}) - позиция не найдена", orderId); return(false); } orderDecor = LinqToEntity.DecorateOrder(orderQuery[0]); } return(CloseOrder(orderDecor, price, exitReason)); }
/// <summary> /// формирует модель для представления PositionDetails /// </summary> /// <param name="positionId">Уникальным идентификатором является сделки</param> /// <remarks>Не тестируется</remarks> public MarketOrder GetPositionItemDetails(int positionId) { MarketOrder positionItem = null; try { using (var ctx = DatabaseContext.Instance.Make()) { // Проверяем, есть ли указанная сделка среди открытых (потому что их меньше), если нет - ищём её среди закрытых if (ctx.POSITION.Any(x => x.ID == positionId)) { POSITION position; try { position = ctx.POSITION.Single(x => x.ID == positionId); } catch (Exception ex) { Logger.Error(string.Format("GetPositionItemDetails() - При запросе подробностей о сделке c id {0} не удалось получить конкретный элемент из POSITION", positionId), ex); return(null); } positionItem = LinqToEntity.DecorateOrder(position); } else { POSITION_CLOSED position; try { position = ctx.POSITION_CLOSED.Single(x => x.ID == positionId); } catch (Exception ex) { Logger.Error(string.Format("GetPositionItemDetails() - При запросе подробностей о сделке c id {0} не удалось получить конкретный элемент из POSITION_CLOSED", positionId), ex); return(null); } positionItem = LinqToEntity.DecorateOrder(position); } } } catch (Exception ex) { Logger.Error("GetPositionItemDetails Exception", ex); } return(positionItem); }
/// <summary> /// вернуть данные по счету для фермы роботов /// </summary> public bool GetAccountData(string login, string pwrd, int accountId, out Account account, out List <MarketOrder> openedOrders) { openedOrders = new List <MarketOrder>(); account = null; try { // проверить логин - пароль using (var db = DatabaseContext.Instance.Make()) { var user = db.PLATFORM_USER.FirstOrDefault(u => u.Login == login); if (user == null) { return(false); } if (user.Password != pwrd) { return(false); } // выдернуть ордер из списка var accountBase = db.ACCOUNT.FirstOrDefault(a => a.ID == accountId); if (accountBase == null) { return(false); } account = LinqToEntity.DecorateAccount(accountBase); // получить сделки var lstPos = db.POSITION.Where(p => p.AccountID == accountId).ToList(); foreach (var pos in lstPos) { openedOrders.Add(LinqToEntity.DecorateOrder(pos)); } } return(true); } catch (Exception ex) { Logger.Error("Ошибка в GetAccountData()", ex); return(false); } }
public Dictionary <int, List <MarketOrder> > GetSignallersOpenTrades(int accountId) { var ordersByService = new Dictionary <int, List <MarketOrder> >(); try { using (var ctx = DatabaseContext.Instance.Make()) { var subscribedServices = (from sub in ctx.SUBSCRIPTION join act in ctx.PLATFORM_USER_ACCOUNT on sub.User equals act.PlatformUser join srv in ctx.SERVICE on sub.Service equals srv.ID where act.RightsMask == (int)AccountRights.Управление && act.Account == accountId select srv).ToList(); var accountsOfManagers = subscribedServices.Select(s => s.AccountId).ToList(); // получить сделки по счетам из списка signallerIds var dealsList = ctx.POSITION.Where(p => p.State == (int)PositionState.Opened && accountsOfManagers.Contains(p.AccountID)); foreach (var deal in dealsList) { var dealDec = LinqToEntity.DecorateOrder(deal); var serviceId = subscribedServices.First(a => a.AccountId == dealDec.AccountID).ID; List <MarketOrder> ordersLst; if (ordersByService.TryGetValue(serviceId, out ordersLst)) { ordersLst.Add(dealDec); continue; } ordersLst = new List <MarketOrder> { dealDec }; ordersByService.Add(serviceId, ordersLst); } } } catch (Exception ex) { Logger.ErrorFormat("Ошибка в GetSignallersOpenTrades({0}): {1}", accountId, ex); } return(ordersByService); }
//TODO - Метод не оптимальный. Для каждой вытащенной записи из БД в цикле foreach производится мапинг в "PositionItem" (Для каждой по отдельности). //TODO - Метод не оптимальный. Происходи 2 обращения к БД - нуждо использовать join или unit /// <summary> /// Получить список сделок по их уникальным идентификаторам /// </summary> /// <param name="idList">Массив уникальных идентификаторов позиций, которые нужно получить</param> /// <remarks>Тестируется</remarks> public List <PositionItem> GetPositionsById(int[] idList) { var result = new List <PositionItem>(); if (idList == null || idList.Length == 0) { return(result); } try { using (var ctx = DatabaseContext.Instance.Make()) { // ReSharper disable LoopCanBeConvertedToQuery foreach (var source in ctx.POSITION.Where(x => idList.Contains(x.ID))) // ReSharper restore LoopCanBeConvertedToQuery { var marketOrder = LinqToEntity.DecorateOrder(source); var positionItem = new PositionItem(marketOrder); result.Add(positionItem); } // ReSharper disable LoopCanBeConvertedToQuery foreach (var source in ctx.POSITION_CLOSED.Where(x => idList.Contains(x.ID))) // ReSharper restore LoopCanBeConvertedToQuery { var marketOrder = LinqToEntity.DecorateOrder(source); //var positionItem = marketOrder as PositionItem; var positionItem = new PositionItem(marketOrder); result.Add(positionItem); } } } catch (Exception ex) { Logger.Error("GetPositionsById()", ex); } return(result); }
private void PerformStopout(ACCOUNT ac, Dictionary <string, QuoteData> quotes, TradeSharpConnection ctx) { try { var sb = new StringBuilder(); var posReq = from pos in ctx.POSITION where pos.AccountID == ac.ID && pos.State == (int)PositionState.Opened select pos; var totalCount = 0; var closedCount = 0; foreach (var pos in posReq) { totalCount++; // закрыть ордер QuoteData quote; quotes.TryGetValue(pos.Symbol, out quote); var price = quote == null ? 0 : quote.GetPrice(pos.Side > 0 ? QuoteType.Bid : QuoteType.Ask); if (price == 0) { sb.AppendFormat("Невозможно закрыть ордер {0} ({1}): нет цены", pos.ID, pos.Symbol); continue; } if (orderRepository.CloseOrder(LinqToEntity.DecorateOrder(pos), (decimal)price, PositionExitReason.Stopout)) { closedCount++; } sb.AppendFormat("позиция {0} закрыта по цене {1:f4} - стопаут. ", pos.ID, price); } if (ShouldLogStopout(ac.ID)) { Logger.Info(string.Format("Счет {0}. Закрыто {1} из {2} позиций. ", ac.ID, closedCount, totalCount) + sb); } } catch (Exception ex) { loggerNoFlood.LogMessageFormatCheckFlood(LogEntryType.Error, LogMagicStopout, 60 * 1000, "Ошибка при выполнении стопаута {0}: {1}", ac.ID, ex); } }
public MarketOrder GetMarketOrder(int orderId) { using (var ctx = DatabaseContext.Instance.Make()) try { var ps = from pos in ctx.POSITION where pos.ID == orderId select pos; // ReSharper disable LoopCanBeConvertedToQuery foreach (var position in ps) { // ReSharper restore LoopCanBeConvertedToQuery return(LinqToEntity.DecorateOrder(position)); } return(null); } catch (Exception ex) { Logger.ErrorFormat("Ошибка в GetMarketOrder({0}): {1}", orderId, ex); return(null); } }
public RequestStatus SendCloseRequest(POSITION order, PositionExitReason reason) { Account account; var dealer = GetDealerByAccount(order.AccountID, out account); if (dealer == null) { return(RequestStatus.GroupUnsupported); } // проверить маржинальные требования decimal equity; if (!tradeManager.IsEnterEnabled(order.AccountID, order.Symbol, -order.Side, order.Volume, out equity)) { return(RequestStatus.MarginOrLeverageExceeded); } try { var res = dealer.SendCloseRequest(LinqToEntity.DecorateOrder(order), reason); if (res == RequestStatus.OK) { Logger.InfoFormat("Ордер {0} закрыт, счет {1} символ {2}, объем {3}, тип {4}", order.ID, order.AccountID, order.Symbol, order.Volume, order.Side == 1 ? "buy": "sell"); } else { Logger.ErrorFormat("Ошибка закрытия ордера {0} - ответ дилера:{1}", order.ID, res); } return(res); } catch (Exception ex) { Logger.ErrorFormat("Ошибка дилера {0}: {1}", dealer, ex); return(RequestStatus.DealerError); } }
public RequestStatus EditPosition(string hash, string userLogin, long localTime, int accountId, int orderId, float stopLoss, float takeProfit, int magic, string comment) { try { using (var ctx = DatabaseContext.Instance.Make()) { var status = CheckCredentials(hash, userLogin, localTime, accountId, true, ctx); if (status != RequestStatus.OK) { return(status); } var pos = ctx.POSITION.FirstOrDefault(p => p.AccountID == accountId && p.ID == orderId); if (pos == null) { return(RequestStatus.IncorrectData); } var order = LinqToEntity.DecorateOrder(pos); order.StopLoss = stopLoss == 0 ? (float?)null : stopLoss; order.TakeProfit = takeProfit == 0 ? (float?)null : takeProfit; order.Magic = magic; order.Comment = comment; status = managerTrade.SendEditMarketRequest(ProtectedOperationContext.MakeServerSideContext(), order); return(status); } } catch (Exception ex) { Logger.ErrorFormat("Ошибка в EditPosition(user={0}, pos={1}): {2}", userLogin, orderId, ex); return(RequestStatus.ServerError); } }
public static int GetNewOrderVolume(TradeSignalActionTrade action, TradeSharpConnection ctx, SUBSCRIPTION_SIGNAL tradeSets) { // получить ордера по счету, посчитать актуальный баланс (средства) счета try { var account = ctx.ACCOUNT.FirstOrDefault(a => a.ID == tradeSets.TargetAccount.Value); if (account == null) { Logger.ErrorFormat("Ошибка при расчете объема по счету {0}, ордер {1}: счет не найден", tradeSets.TargetAccount, action.OrderId); return(0); } var equity = (double)account.Balance; // ордера var orders = ctx.POSITION.Where(p => p.AccountID == tradeSets.TargetAccount && p.State == (int)PositionState.Opened).ToList().Select(o => LinqToEntity.DecorateOrder(o)).ToList(); // проверить - нет ли противоположного ордера? var quotes = Contract.Util.BL.QuoteStorage.Instance.ReceiveAllData(); if (orders.Count > 0) { if (!(tradeSets.HedgingOrdersEnabled ?? false)) { var hasOrdersCounter = orders.Any(p => p.Symbol == action.Ticker && p.Side != action.Side); if (hasOrdersCounter) { logNoFlood.LogMessageFormatCheckFlood(LogEntryType.Info, LogMsgContraOrders, 1000 * 60 * 90, "Ордер {0} {1}, счет {2} - есть встречные ордера", action.Side > 0 ? "BUY" : "SELL", action.Ticker, tradeSets.TargetAccount); return(0); } } // посчитать профит по позам bool noQuoteError; var openResult = DalSpot.Instance.CalculateOpenedPositionsCurrentResult( orders, quotes, account.Currency, out noQuoteError); if (noQuoteError) { logNoFlood.LogMessageFormatCheckFlood(LogEntryType.Error, LogMsgVolmCalcNoQuote, 1000 * 60 * 90, "GetNewOrderVolume - нет котировки"); return(0); } equity += openResult; } if (equity <= 0) { logNoFlood.LogMessageFormatCheckFlood(LogEntryType.Error, LogMsgEquityIsNil, 1000 * 60 * 90, "Баланс счета {0}: {1}", account.ID, equity); return(0); } // посчитать объем var volumeDepo = (tradeSets.PercentLeverage ?? 100) / 100f * equity * (double)action.Leverage; // пересчитать объем в базовую валюту string errorStr; var volumeBase = DalSpot.Instance.ConvertToTargetCurrency(action.Ticker, true, account.Currency, volumeDepo, quotes, out errorStr); if (!volumeBase.HasValue) { logNoFlood.LogMessageFormatCheckFlood(LogEntryType.Error, LogMsgVolmToBase, 1000 * 60 * 90, "GetNewOrderVolume - невозможно посчитать объем по сделке {0}, валюта счета {1}: {2}", action.Ticker, account.Currency, errorStr); return(0); } // округлить объем var volume = MarketOrder.RoundDealVolume((int)volumeBase, (VolumeRoundType)(tradeSets.VolumeRound ?? (int)VolumeRoundType.Ближайшее), tradeSets.MinVolume ?? 10000, tradeSets.StepVolume ?? 10000); if (volume == 0) { var msg = string.Format("Полученный объем входа ({0}) округлен до 0", (int)volumeBase); logNoFlood.LogMessageFormatCheckFlood(LogEntryType.Error, LogMsgVolmRoundToZero, 1000 * 60 * 90, msg); return(0); } // проверить на превышение плеча if ((tradeSets.MaxLeverage ?? 0) > 0) { var totalExposure = 0M; var ordersBySignaller = orders.Where(position => { int orderSignalCatId, parentDealId; if (MarketOrder.GetTradeSignalFromDeal(position, out orderSignalCatId, out parentDealId)) { return(orderSignalCatId == action.ServiceId); } return(false); }).GroupBy(o => o.Symbol).ToDictionary(o => o.Key, o => o.ToList()); foreach (var orderByTicker in ordersBySignaller) { // суммарная экспозиция var exp = orderByTicker.Value.Sum(o => o.Side * o.Volume); if (orderByTicker.Key == action.Ticker) { exp += action.Side * volume; } if (exp == 0) { continue; } // перевести экспозицию в валюту депозита var expBase = DalSpot.Instance.ConvertToTargetCurrency(action.Ticker, true, account.Currency, volumeDepo, quotes, out errorStr); if (!expBase.HasValue) { logNoFlood.LogMessageFormatCheckFlood(LogEntryType.Error, LogMsgExposureNoQuote, 1000 * 60 * 90, "GetNewOrderVolume - невозможно посчитать экспозицию по сделке " + "{0}, тикер {1}: {2}", action.Ticker, orderByTicker.Key, errorStr); return(0); } totalExposure += Math.Abs(expBase.Value); } var totalLeverage = (double)totalExposure / equity; // ReSharper disable PossibleInvalidOperationException if (totalLeverage > tradeSets.MaxLeverage.Value) // ReSharper restore PossibleInvalidOperationException { logNoFlood.LogMessageFormatCheckFlood(LogEntryType.Error, LogMsgMaxLevExceeded, 1000 * 60 * 90, "GetNewOrderVolume - макс плечо ({0}) превышено, " + "тикер {1}: плечо составит {2}", tradeSets.MaxLeverage.Value, action.Ticker, totalLeverage); return(0); } } return(volume); } catch (Exception ex) { Logger.ErrorFormat("Ошибка при расчете объема по счету {0}, ордер {1}: {2}", tradeSets.TargetAccount, action.OrderId, ex); } return(0); }
/// <summary> /// Редактирование 'опасных' полей сделок /// </summary> /// <param name="strId">Уникальные идентификаторы редактируемых сделок</param> /// <param name="currentState">Статус редактируемых сделок</param> /// <param name="newTicker">Новое значение инструмента</param> /// <param name="newSide">Новое значение типа сделки</param> /// <param name="newVolume">Новое значение объёма сделки</param> /// <param name="newEnterPrice">Новое значение цены входа</param> /// <param name="newExitPrice">Новое значение цены выхода</param> public bool EditDangerDeal(string strId, PositionState currentState, string newTicker, int?newSide, int?newVolume, float?newEnterPrice, float?newExitPrice) { var id = strId.ToIntArrayUniform(); using (var ctx = DatabaseContext.Instance.Make()) { var selOrders = new List <MarketOrder>(); // ReSharper disable LoopCanBeConvertedToQuery if (currentState == PositionState.Closed) { foreach (var order in ctx.POSITION_CLOSED.Where(x => id.Contains(x.ID))) { selOrders.Add(LinqToEntity.DecorateOrder(order)); } } else { foreach (var order in ctx.POSITION.Where(x => id.Contains(x.ID))) { selOrders.Add(LinqToEntity.DecorateOrder(order)); } } // ReSharper restore LoopCanBeConvertedToQuery // Группируем все сделки по счётам var selOrderGroupByAccount = selOrders.GroupBy(x => x.AccountID); // Идём по счетам в группе foreach (var selOrderGroup in selOrderGroupByAccount) { var acc = accountRepository.GetAccount(selOrderGroup.Key); try { // Идём по сделкам в счёте foreach (var order in selOrderGroup) { decimal?sl = null; if (order.StopLoss.HasValue) { sl = Convert.ToDecimal(order.StopLoss); } decimal?tp = null; if (order.TakeProfit.HasValue) { tp = Convert.ToDecimal(order.TakeProfit); } var timeEnter = order.TimeEnter; var timeExit = order.TimeExit; var price = newEnterPrice.HasValue ? newEnterPrice : order.PriceEnter; var priceExit = newExitPrice.HasValue ? newExitPrice : order.PriceExit; var volume = newVolume.HasValue ? newVolume : order.Volume; var symbol = !string.IsNullOrEmpty(newTicker) && newTicker.ToLower() != "null" ? newTicker : order.Symbol; var side = newSide.HasValue ? newSide : order.Side; var state = order.State; #region MarketOrder ordClosed = null; if (state == PositionState.Closed) { // посчитать профит по сделке ordClosed = order.MakeCopy(); ordClosed.State = PositionState.Closed; ordClosed.PriceExit = priceExit.Value; ordClosed.TimeExit = timeExit.Value; string errorStr = null; if (!priceExit.HasValue || !DealProfitCalculator.CalculateOrderProfit(ordClosed, acc.Currency, priceExit.Value, out errorStr)) { if (!string.IsNullOrEmpty(errorStr)) { Logger.Error("Сделка " + order.ID + " не будет закрыта : " + errorStr); } else { Logger.Error("Сделка " + order.ID + ". не будет закрыта : не указана priceExit."); } continue; } } try { //Если сделка из 'открытых' if (state != PositionState.Closed) { // поправить открытую позицию var pos = ctx.POSITION.First(p => p.ID == order.ID); pos.PriceEnter = Convert.ToDecimal(price.Value); pos.TimeEnter = timeEnter; pos.Stoploss = sl; pos.Takeprofit = tp; pos.Volume = volume.Value; pos.Symbol = symbol; pos.Side = side.Value; pos.State = (int)state; } else { #region поправить закрытую позу и скорректировать результат var pos = ctx.POSITION_CLOSED.First(p => p.ID == order.ID); pos.PriceEnter = Convert.ToDecimal(price.Value); pos.TimeEnter = timeEnter; pos.Stoploss = sl; pos.Takeprofit = tp; pos.Volume = volume.Value; pos.Symbol = symbol; pos.Side = side.Value; pos.PriceExit = Convert.ToDecimal(priceExit.Value); pos.TimeExit = timeExit.Value; pos.ResultDepo = (decimal)ordClosed.ResultDepo; pos.ResultBase = (decimal)ordClosed.ResultBase; pos.ResultPoints = (decimal)ordClosed.ResultPoints; #endregion // поправить проводку и баланс UpdateBalanceChange(ctx, ordClosed, false); } } catch (Exception ex) { Logger.Error("Ошибка при редактировании сделки " + order.ID, ex); } #endregion Logger.Info("Сделка " + order.ID + " отредактирована удачно."); } // коммит ctx.SaveChanges(); } catch (Exception ex) { Logger.Error("EditDangerDeal", ex); return(false); } if (currentState == PositionState.Closed) { ReCalculateAccountBalance(ctx, acc.ID); } } return(true); } }
public List <int> CancelingClosedPositions(string strId) { Logger.Info("Начинаем отменяем закрытые сделки " + strId); var result = new List <int>(); 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_CLOSED.Where(x => id.Contains(x.ID))) { selOrders.Add(LinqToEntity.DecorateOrder(order)); } // ReSharper restore LoopCanBeConvertedToQuery if (selOrders.Count != id.Length) { Logger.Error("CancelingClosedPositions() - в таблице 'POSITION_CLOSED' не найдены некоторые или все сделки с идентификаторами " + strId); } // Группируем все сделки по счетам var selOrderGroupByAccount = selOrders.GroupBy(x => x.AccountID); // Перебираем все счета foreach (var orderGroup in selOrderGroupByAccount) { // Список удачно отменённых закрытых сделок текущего счёта var successCanceledPositions = new List <int>(); var acc = accountRepository.GetAccount(orderGroup.Key); if (acc == null) { Logger.Error("CancelingClosedPositions() - не удалось получить счёт " + orderGroup.Key); continue; } Logger.Info("Начинаем отменять закрытые сделки в счёте " + orderGroup.Key); Logger.Debug(string.Format("для счёта {0} количество BALANCE_CHANGE до отмены открытых сделок равно {1}", orderGroup.Key, ctx.BALANCE_CHANGE.Count(c => c.AccountID == orderGroup.Key))); // Перебираем все сделки в текущем счёте foreach (var order in orderGroup) { #region убрать сделку из числа закрытых try { var pos = ctx.POSITION_CLOSED.FirstOrDefault(p => p.ID == order.ID); if (pos == null) { Logger.Error("CancelingClosedPositions() - запись о сделке " + order.ID + " не найдена в таблице POSITION_CLOSED. Сделка не может быть отменена"); continue; } ctx.POSITION_CLOSED.Remove(pos); Logger.Info("запись о сделке " + order.ID + " удалена из таблици POSITION_CLOSED"); } catch (Exception ex) { Logger.Error("CancelingClosedPositions() - Ошибка при попытке убрать сделку из числа закрытых", ex); continue; } #endregion var dealDescr = string.Format("{1} #{0}", order.ID, Resource.TitleMarketOrderResult); var trans = ctx.BALANCE_CHANGE.FirstOrDefault(c => c.AccountID == order.AccountID && (c.ChangeType == (int)BalanceChangeType.Profit || c.ChangeType == (int)BalanceChangeType.Loss) && c.Description.Equals(dealDescr, StringComparison.OrdinalIgnoreCase)); if (trans == null) { Logger.Error("CancelingClosedPositions() - Ошибка при попытке убрать сделку из числа закрытых, не удалось найти транзакцию"); continue; } ctx.BALANCE_CHANGE.Remove(trans); successCanceledPositions.Add(order.ID); } Logger.Info("Начинаем вспомогательное сохранение в базу данных"); ctx.SaveChanges(); Logger.Debug(string.Format("для счёта {0} количество BALANCE_CHANGE после отмены открытых сделок равно {1}", orderGroup.Key, ctx.BALANCE_CHANGE.Count(c => c.AccountID == orderGroup.Key))); ReCalculateAccountBalance(ctx, acc.ID); Logger.Info("Начинаем сохранять в базу данных изменения по счёту " + orderGroup.Key); ctx.SaveChanges(); result.AddRange(successCanceledPositions); } Logger.Info("Изменения сохранены. Cделки " + string.Join(", ", result) + " отменены"); if (id.Length - result.Count != 0) { Logger.Error("CancelingClosedPositions() - по каким то причинам, не удалось отменить " + (id.Length - result.Count) + " сделок"); } } } catch (Exception ex) { Logger.Error("CancelingClosedPositions() - возникла ошибка при попытке сохранить изменения в базу данных. Не удалось отменить открытые сделки " + strId, ex); } Logger.Info("Сделки " + strId + " отменены"); return(result); }
/// <summary> /// Отмена открытых сделок, без возможности восстановления /// </summary> /// <param name="strId">Уникальные идентификаторы отменяемых открытых сделок, перечисленные через запятую</param> /// <returns>Уникальные идентификаторы успешно отменённых открытых сделок</returns> public List <int> CancelingOpenPositions(string strId) { Logger.Info("Начинаем отменять открытые сделки " + strId); var result = new List <int>(); 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("CancelingOpenPositions() - в таблице 'POSITION' не найдены некоторые или все сделки с идентификаторами " + strId); } // Группируем все сделки по счётам var selOrderGroupByAccount = selOrders.GroupBy(x => x.AccountID); // Перебираем все счета foreach (var orderGroup in selOrderGroupByAccount) { // Список удачно отменённых открытых сделок текущего счёта var successCanceledPositions = new List <int>(); var acc = accountRepository.GetAccount(orderGroup.Key); if (acc == null) { Logger.Error("CancelingOpenPositions() - не удалось получить счёт " + orderGroup.Key); continue; } Logger.Info("Начинаем отменять открытые сделки в счёте " + orderGroup.Key); // Перебираем все сделки в текущем счёте foreach (var order in orderGroup) { // убрать сделку из числа открытых try { var pos = ctx.POSITION.FirstOrDefault(p => p.ID == order.ID); if (pos == null) { Logger.Error("CancelingOpenPositions() - запись о сделке " + order.ID + " не найдена в таблице POSITION. Сделка не может быть отменена"); continue; } ctx.POSITION.Remove(pos); Logger.Info("запись о сделке " + order.ID + " удалена из таблици POSITION"); } catch (Exception ex) { Logger.Error("CancelingOpenPositions() - Ошибка при попытке убрать сделку из числа открытых", ex); continue; } successCanceledPositions.Add(order.ID); } Logger.Info("Начинаем сохранять в базу данных изменения по счёту " + orderGroup.Key); ctx.SaveChanges(); result.AddRange(successCanceledPositions); } Logger.Info("Изменения сохранены. Cделки " + string.Join(", ", result) + " отменены"); if (id.Length - result.Count != 0) { Logger.Error("CancelingOpenPositions() - по каким то причинам, не удалось отменить " + (id.Length - result.Count) + " сделок"); } } } catch (Exception ex) { Logger.Error("CancelingOpenPositions() - возникла ошибка при попытке сохранить изменения в базу данных. Не удалось отменить открытые сделки " + strId, ex); } Logger.Info("Сделки " + strId + " отменены"); return(result); }
/// <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); }
/// <summary> /// Переоткрытие закрытых сделок /// </summary> /// <param name="strId">Уникальные идентификаторы сделокЮ которые нужно переоткрыть, перечисленные через запятую</param> /// <returns></returns> public bool ReopenPositions(string strId) { var id = strId.ToIntArrayUniform(); using (var ctx = DatabaseContext.Instance.Make()) { // Метод GetPositionsById тут использовать не надо т.к. в нём тот же код, но с лишним обращением к таблице POSITION, а так же // тут не нужно лишнее преобразование MarketOrder к PositionItem var selOrders = new List <MarketOrder>(); // ReSharper disable LoopCanBeConvertedToQuery foreach (var order in ctx.POSITION_CLOSED.Where(x => id.Contains(x.ID))) { selOrders.Add(LinqToEntity.DecorateOrder(order)); } // ReSharper restore LoopCanBeConvertedToQuery // Группируем все сделки по счётам var selOrderGroupByAccount = selOrders.GroupBy(x => x.AccountID); // Идем по группам сделок в выборке foreach (var selOrderGroup in selOrderGroupByAccount) { var balanceDelta = -selOrderGroup.Sum(o => o.ResultDepo); try { // "освежить" ордера и запомнить, какие транзакции нужно будет удалить var balanceChangeDescript = new List <Cortege2 <int, string> >(); foreach (var order in selOrderGroup) { balanceChangeDescript.Add(new Cortege2 <int, string>(order.ResultDepo >= 0 ? (int)BalanceChangeType.Profit : (int)BalanceChangeType.Loss, string.Format("{1} #{0}", order.ID, Resource.TitleMarketOrderResult))); order.ResultBase = 0; order.ResultDepo = 0; order.ResultPoints = 0; order.State = PositionState.Opened; order.PriceExit = null; order.TimeExit = null; } // удалить закрытые позиции foreach (var oldOrder in selOrderGroup) { var ord = ctx.POSITION_CLOSED.FirstOrDefault(o => o.ID == oldOrder.ID); if (ord != null) { ctx.POSITION_CLOSED.Remove(ord); } } // корректировать баланс var account = ctx.ACCOUNT.FirstOrDefault(a => a.ID == selOrderGroup.Key); if (account != null) { account.Balance += (decimal)balanceDelta; //ctx.ACCOUNT.ApplyCurrentValues(account); //TODO убрал ApplyCurrentValues } // удалить трансферы foreach (var transInfo in balanceChangeDescript) { var changeType = transInfo.a; var transTitle = transInfo.b; var trans = ctx.BALANCE_CHANGE.FirstOrDefault(t => t.AccountID == selOrderGroup.Key && t.ChangeType == changeType && t.Description == transTitle); if (trans != null) { ctx.BALANCE_CHANGE.Remove(trans); } } // добавить открытые позиции var ordersDb = selOrderGroup.Select(LinqToEntity.UndecorateOpenedPosition).ToList(); foreach (var ord in ordersDb) { ctx.POSITION.Add(ord); } // завершить транзакцию ctx.SaveChanges(); } catch (Exception ex) { Logger.Error("ReopenPositions(string strId)", ex); } } } return(true); }