/// <summary> /// Sets the associated quantfund. /// </summary> /// <param name="quantfund">The quantfund.</param> public virtual void SetQuantFund(IQuantFund quantfund) { if (QuantFund == null && quantfund != null) { QuantFund = quantfund; } }
/// <summary> /// Add cash /// </summary> /// <param name="quantfund"></param> /// <param name="currency"></param> /// <param name="cash"></param> /// <param name="settlementutc"></param> public void AddCash(CurrencyType currency, decimal cash, IQuantFund quantfund = null, DateTime?settlementutc = null) { //Get funds for quant fund CashPosition cashposition; if (quantfund == null) { cashposition = GetBaseAccount(currency); } else { //Check if we know this quant fund if (!_cashknown.ContainsKey(quantfund.FundId)) { _cashknown[quantfund.FundId] = new Dictionary <CurrencyType, CashPosition>(); } //Check if we know this currency type if (!_cashknown[quantfund.FundId].TryGetValue(currency, out cashposition)) { cashposition = new CashPosition(currency, 0); _cashknown[quantfund.FundId].Add(currency, cashposition); } } //Set items cashposition.AddCash(settlementutc.HasValue ? new UnsettledCash(cash, settlementutc.Value) : new SettledCash(cash)); }
/// <summary> /// Set initial fund allocation to quant fund, can only be set once /// </summary> /// <param name="quantfund"></param> /// <param name="basecurrency"></param> /// <param name="cash"></param> public void AddQuantFund(IQuantFund quantfund, CurrencyType basecurrency, decimal cash) { lock (_locker) { //Can only be set once if (_cashknown.ContainsKey(quantfund.FundId)) { return; } //Check cash integrity cash = Math.Abs(cash); //Get base balance var baseposition = GetCashPositions()[basecurrency]; //Check if we have enough funds if (baseposition.TotalCash < cash) { _log.Warn($"You are allocating more cash than the account has on cash available. Avalaible = {baseposition.TotalCash}, Allocate ({quantfund.FundId}) = {cash}"); } _cashknown.Add(quantfund.FundId, new Dictionary <CurrencyType, CashPosition> { { basecurrency, new CashPosition(basecurrency, cash) } }); //Some logging _log.Info($"Allocated {cash} {basecurrency} amount to fund with name {quantfund.Name}"); //Remove from base baseposition.AddCash(new SettledCash(-cash)); } }
/// <summary> /// Sets the associated quantfund. /// </summary> /// <param name="quantfund">The quantfund.</param> public override void SetQuantFund(IQuantFund quantfund) { if (QuantFund == null && quantfund != null) { QuantFund = quantfund; SetQuantFund(quantfund); } }
/// <summary> /// Removes all the scheduled actions for the specified quantfund. /// </summary> /// <param name="quantfund">The quantfund.</param> public void Remove(IQuantFund quantfund) { lock (_locker) { var found = _eventActions.Where(x => x.Value.FundId == quantfund.FundId).ToArray(); found.ForEach(e => _eventActions.Remove(e.Key)); } }
/// <summary> /// Removes the quant fund from this portfolio. /// </summary> /// <param name="quantfund">The quantfund.</param> /// <exception cref="NotImplementedException"></exception> public void RemoveFund(IQuantFund quantfund) { throw new NotImplementedException(); //Send updates (so we know this quant fund was remvoved) EventRunner.Enqueue(FundInfoMessage.Generate(Id, quantfund), true); //Remove funds CashManager.RemoveQuantFund(quantfund); }
/// <summary> /// Generate fund information message /// </summary> /// <param name="portfolioid"></param> /// <param name="quantfund"></param> /// <returns></returns> public static FundInfoMessage Generate(string portfolioid, IQuantFund quantfund) { return(new FundInfoMessage { Status = quantfund.State, Id = quantfund.FundId, IsRunning = quantfund.IsRunning, Name = quantfund.Name, ROI = quantfund.Results.QuantFund.ROI, PortfolioId = portfolioid, StartedUtc = quantfund.StartedDTUtc }); }
/// <summary> /// Adds a datasubscription for all non market price data available (delisting, splits, trading status etc) /// </summary> /// <param name="security">The security.</param> /// <param name="quantfund"></param> public void AddSubscription(IQuantFund quantfund, Security security) { //Helper function void AddSubscriptionType(DataType datatype) { var subscriptionrequest = DataSubscriptionRequest.CreateSubscriptionRequest(security.Ticker, _datafeed.DataSource, null, datatype); AddSubscription(subscriptionrequest); //Add subscription to registered subscriptions if (!_registeredsubscriptions.ContainsKey(quantfund.FundId)) { _registeredsubscriptions.Add(quantfund.FundId, new List <DataSubscription>() { new DataSubscription(quantfund.FundId, subscriptionrequest, security, TimeZone.Utc, null) }); } else if (_registeredsubscriptions[quantfund.FundId].Count(x => x.Request.GetSubscriptionName() == subscriptionrequest.GetSubscriptionName()) == 0) { _registeredsubscriptions[quantfund.FundId].Add(new DataSubscription(quantfund.FundId, subscriptionrequest, security, TimeZone.Utc, null)); } } //Add delisting information AddSubscriptionType(DataType.Delisting); //Add dividend notifications AddSubscriptionType(DataType.Dividend); //Add earning reports AddSubscriptionType(DataType.Earning); //Add financial reports AddSubscriptionType(DataType.Financial); //Add key stats AddSubscriptionType(DataType.KeyStat); //Add stock splits AddSubscriptionType(DataType.Split); //Add trading status updates AddSubscriptionType(DataType.TradingStatus); //Check base conversion AddBaseCurrencyConversionFeed(security); }
/// <summary> /// Processes the submit ticket. /// </summary> /// <param name="ticket">The ticket.</param> /// <param name="quantfund">Associated Quant Fund</param> /// <returns></returns> private OrderTicket ProcessSubmitTicket(SubmitOrderTicket ticket, IQuantFund quantfund) { if (quantfund != null && quantfund.IsBackfilling) { ticket.SetResponse( OrderTicketResponse.Error(ticket.OrderId, OrderTicketResponseErrorCode.QuantFundBackfilling, $"Quant fund {ticket.FundId} is currently backfilling, cannot process orders.")); } else { _orderTicketQueue.Add(ticket); ticket.SetResponse(OrderTicketResponse.Processed(ticket.OrderId), OrderTicketState.Processing); } return(ticket); }
/// <summary> /// Removes the quant fund associated subscriptions. /// </summary> /// <param name="quantfund">The quantfund.</param> public void RemoveQuantFundSubscriptions(IQuantFund quantfund) { //get all tickers var tickers = ActiveTickers(quantfund); //Remove quant fund _registeredsubscriptions.Remove(quantfund.FundId); //Get all items which are no longer needed var active = _registeredsubscriptions .SelectMany(x => x.Value.Select(n => n.Ticker)) .Distinct(); //Remove all no longer used tickers.Where(x => !active.Contains(x)) .ForEach(RemoveSubscription); }
/// <summary> /// Cancels the order ticket. /// </summary> /// <param name="ticket">The ticket.</param> /// <param name="quantfund">Ticket associated quant fund</param> /// <returns></returns> private OrderTicket ProcessCancelTicket(CancelOrderTicket ticket, IQuantFund quantfund) { //Try and get current order ticket if (!OrderTracker.TryGetOrder(ticket.OrderId, out PendingOrder pendingorder)) { ticket.SetResponse(OrderTicketResponse.Error(ticket.OrderId, OrderTicketResponseErrorCode.UnableToFindOrder)); return(ticket); } try { //Try and process cancel ticket if (pendingorder.OrderState.IsDone()) { _log.Error($"Order is already of state {pendingorder.OrderState} while trying to cancel this order"); ticket.SetResponse(OrderTicketResponse.Error(pendingorder.OrderId, OrderTicketResponseErrorCode.InvalidOrderStatus)); } else if (quantfund != null && quantfund.IsBackfilling) { ticket.SetResponse(OrderTicketResponse.Error(pendingorder.OrderId, OrderTicketResponseErrorCode.QuantFundBackfilling)); } else { // update the order status var order = pendingorder.Order as OrderImpl; order.State = OrderState.CancelPending; pendingorder.UpdateOrder(order); // notify the portfolio with an order event HandleOrderTicketEvent(OrderTicketEvent.Cancelled(pendingorder.OrderId)); // send the request to be processed ticket.SetResponse(OrderTicketResponse.Processed(ticket.OrderId), OrderTicketState.Processing); _orderTicketQueue.Add(ticket); } } catch (Exception exc) { _log.Error(exc); ticket.SetResponse(OrderTicketResponse.Error(pendingorder.OrderId, OrderTicketResponseErrorCode.ProcessingError, exc.Message)); } //return result return(ticket); }
/// <summary> /// Remove quant fund from following cash positions /// </summary> /// <param name="quantfund"></param> public void RemoveQuantFund(IQuantFund quantfund) { lock (_locker) { //Check if we know this quantfund if (!_cashknown.ContainsKey(quantfund.FundId)) { return; } //Remove balance from quant fund var cashholder = GetCashPositions(quantfund)[BaseCurrency]; var baseholder = GetCashPositions()[BaseCurrency]; //Move from cash holder and add to base baseholder.AddCash(new SettledCash(cashholder.TotalSettledCash)); baseholder.AddCash(new UnsettledCash(cashholder.TotalUnsettledCash, cashholder.DateTimeAllIsSettledUtc)); //Remove fund _cashknown.Remove(quantfund.FundId); } }
/// <summary> /// Processes the update ticket. /// </summary> /// <param name="ticket">The ticket.</param> /// <param name="quantfund">Ticket associated quant fund</param> /// <returns></returns> private OrderTicket ProcessUpdateTicket(UpdateOrderTicket ticket, IQuantFund quantfund) { //Try and get current order ticket if (!OrderTracker.TryGetOrder(ticket.OrderId, out PendingOrder pendingorder)) { ticket.SetResponse(OrderTicketResponse.Error(ticket.OrderId, OrderTicketResponseErrorCode.UnableToFindOrder)); return(ticket); } try { //Try and process cancel ticket if (pendingorder.OrderState.IsDone()) { _log.Error($"Order is already of state {pendingorder.OrderState} while trying to update this order"); ticket.SetResponse(OrderTicketResponse.Error(pendingorder.OrderId, OrderTicketResponseErrorCode.InvalidOrderStatus)); } else if (quantfund != null && quantfund.IsBackfilling) { ticket.SetResponse(OrderTicketResponse.Error(pendingorder.OrderId, OrderTicketResponseErrorCode.QuantFundBackfilling)); } else { // send the request to be processed ticket.SetResponse(OrderTicketResponse.Processed(ticket.OrderId), OrderTicketState.Processing); _orderTicketQueue.Add(ticket); } } catch (Exception exc) { _log.Error(exc); ticket.SetResponse(OrderTicketResponse.Error(pendingorder.OrderId, OrderTicketResponseErrorCode.ProcessingError, exc.Message)); } //Return what we have return(ticket); }
/// <summary> /// Gets the cash positions that are part of a quant fund. /// </summary> /// <param name="quantfund">The quantfund.</param> /// <returns></returns> public Dictionary <CurrencyType, CashPosition> GetCashPositions(IQuantFund quantfund) => _cashknown[quantfund.FundId];
/// <summary> /// Settle funds to account right away /// </summary> /// <param name="account">The account.</param> /// <param name="quantfund">The quantfund.</param> /// <param name="security">The security.</param> /// <param name="occureddtutc">The occureddtutc.</param> /// <param name="amount">The amount.</param> /// <exception cref="NullReferenceException">Could not find quant fund for settlement</exception> public void SettleFunds(BrokerAccount account, Security security, DateTime occureddtutc, decimal amount, IQuantFund quantfund = null) { //check if we found agent if (quantfund == null) { throw new NullReferenceException("Could not find quant fund for settlement"); } //Add settled funds account.Cash.AddCash(security.BaseCurrency, amount, quantfund); }
/// <summary> /// Settle funds, delayed /// </summary> /// <param name="account">The account.</param> /// <param name="quantfund">The quantfund.</param> /// <param name="security">The security.</param> /// <param name="occureddtutc">The occureddtutc.</param> /// <param name="amount">The amount.</param> public void SettleFunds(BrokerAccount account, Security security, DateTime occureddtutc, decimal amount, IQuantFund quantfund = null) { //Added funds if (amount > 0) { //Get exchangeModel based local time DateTime settlementdate = security.Exchange.LocalTime; //Check for date based on market opened date and time for (int i = 0; i < DelayedDays; i++) { settlementdate = settlementdate.AddDays(i); if (!security.Exchange.IsOpenOnDate(settlementdate)) { i--; } } //Get correct date and time settlementdate = settlementdate.Add(TimeOfDay); //Convert time of day from local exchangeModel timezone to utc based time settlementdate = settlementdate.ConvertTo(security.Exchange.TimeZone, TimeZone.Utc); //Add unsettled funds (to be settled on a later time and date) account.Cash.AddCash(security.BaseCurrency, amount, quantfund, settlementdate); } else //Used funds, settle right away { account.Cash.AddCash(security.BaseCurrency, amount, quantfund); } }
/// <summary> /// Initialize new scheduled action instance /// </summary> /// <param name="quantfund"></param> /// <param name="date"></param> /// <param name="time"></param> /// <param name="action"></param> /// <param name="name"></param> public ScheduledAction(IQuantFund quantfund, DateComposite date, TimeComposite time, Action <string, DateTime> action, string name = "") : this(date, time, action, name) => FundId = quantfund.FundId;
/// <summary> /// Gets the currently active tickers by quant fund /// </summary> /// <param name="quantfund">The quantfund.</param> /// <returns></returns> public string[] ActiveTickers(IQuantFund quantfund) => _registeredsubscriptions.ContainsKey(quantfund.FundId) ? _registeredsubscriptions[quantfund.FundId].Select(x => x.Ticker).Distinct().ToArray() : new string[0];
/// <summary> /// Adds a datasubscription which is derived from the requested data aggregator instance /// Force tick will force the data to contain the highest granularity (otherwise it might be based on 1-minute data) /// TODO: add unit test, if we request 1 minute data and than request tick data we should keep the tick data request and replace all 1 minute request with the tick data request? (so that we only keep the tick data request) /// TODO: we will only do ticks or tradebars! (where a trade bar is based on any data) /// </summary> /// <param name="quantfund"></param> /// <param name="security">The security.</param> /// <param name="aggregator">The aggregator.</param> /// <param name="forcetick">if set to <c>true</c> [forcetick].</param> /// <returns>Can be a different dataggregator due to change in data requested</returns> public DataAggregator AddSubscription(IQuantFund quantfund, Security security, DataAggregator aggregator, bool forcetick = false) { //Initial values TimeSpan?aggregationneeded = null; DataType datatypeneeded = DataType.Tick; TimeSpan preaggregated = TimeSpan.FromMinutes(1); if (!forcetick) { //TradeBar -> TradeBar if (aggregator is TimeSerieAggregator <TradeBar, TradeBar> tradetotrade && tradetotrade.IsTimeBased) { if (tradetotrade.Period.Value.TotalSeconds % 60 == 0D) { aggregator = new TradeAggregator(tradetotrade.Period.Value); aggregationneeded = preaggregated; datatypeneeded = DataType.TradeBar; } } //Tick -> TradeBar if (aggregator is TimeSerieAggregator <Tick, TradeBar> ticktobar && ticktobar.IsTimeBased) { if (ticktobar.Period.Value.TotalSeconds % 60 == 0D) { aggregator = new TickQuoteBarAggregator(ticktobar.Period.Value); aggregationneeded = TimeSpan.FromMinutes(1); datatypeneeded = DataType.TradeBar; } } } //get and add subscription var subscription = DataSubscriptionRequest.CreateSubscriptionRequest(security.Ticker, _datafeed.DataSource, aggregationneeded, datatypeneeded); subscription = AddSubscription(subscription); //Add base currency conversion AddBaseCurrencyConversionFeed(security); //Check if we already have a similar data aggregator, reuse the existing version if possible if (_registeredsubscriptions.ContainsKey(quantfund.FundId)) { var found = _registeredsubscriptions[quantfund.FundId].FirstOrDefault(x => x.Request.GetSubscriptionName() == subscription.GetSubscriptionName()); var existing = found?.Aggregators.FirstOrDefault(x => x.Name == aggregator.Name); if (existing != null) { return(existing); } else if (found == null) { _registeredsubscriptions[quantfund.FundId].Add(new DataSubscription(quantfund.FundId, subscription, security, security.Exchange.TimeZone, aggregator)); } else { found.Aggregators.Add(aggregator); } } else { //Add new _registeredsubscriptions.Add(quantfund.FundId, new List <DataSubscription>()); _registeredsubscriptions[quantfund.FundId].Add(new DataSubscription(quantfund.FundId, subscription, security, security.Exchange.TimeZone, aggregator)); } //Return our current aggregator return(aggregator); }
/// <summary> /// Update cash position based on account action /// TODO: add sync action to scheduler for live trading (get current funds from account via broker API) => will be done via tickethandler (it will contact the api) /// </summary> /// <param name="action"></param> /// <param name="currency"></param> /// <param name="amount"></param> /// <param name="quantfund">In case the cash position affects a specific quant fund</param> public void Process(AccountActionType action, CurrencyType currency, decimal amount, IQuantFund quantfund = null) { //Check action type and process if (action == AccountActionType.Sync) { SyncFunds(currency, amount); } else if (action == AccountActionType.Credit) { throw new NotImplementedException(); } else if (action == AccountActionType.Deposit) { throw new NotImplementedException(); } else if (action == AccountActionType.Dividend) { throw new NotImplementedException(); } else if (action == AccountActionType.Withdrawal) { throw new NotImplementedException(); } //TODO: What to do with currently set funds and fund withdrawals }
/// <summary> /// Get current buying power for quant fund /// </summary> /// <param name="quantfund">Corresponding quant fund to request buying power for</param> /// <returns></returns> public decimal GetBuyingPower(IQuantFund quantfund) => _cashknown[quantfund.FundId].Values.Sum(x => _conversion.Convert(x.TotalSettledCash, x.BaseCurrency, BaseCurrency)) * _brokeraccount.Leverage;
/// <summary> /// Gets the calculated funds. /// </summary> /// <param name="quantfund">The quantfund.</param> /// <param name="account">Current account object</param> /// <returns></returns> public CalculatedFunds GetCalculatedFunds(IQuantFund quantfund, BrokerAccount account) => new CalculatedFunds(GetUnsettledCash(quantfund), GetCash(quantfund), BaseCurrency, quantfund.Positions, _leverage, account.PatternDayTradingHit, account.DayTradingOrdersLeft);
public decimal GetRemainingMargin(IQuantFund quantfund = null) { throw new NotImplementedException(); }
/// <summary> /// Get total amount of unsettled cash for quant fund /// </summary> /// <param name="quantfund"></param> /// <returns></returns> public decimal GetUnsettledCash(IQuantFund quantfund) => _cashknown[quantfund.FundId].Values.Sum(x => _conversion.Convert(x.TotalUnsettledCash, x.BaseCurrency, BaseCurrency));