private SymbolData GetOrAddData(ISymbolInfo symbol) { if (!SymbolsData.TryGetValue(symbol.Key, out SymbolData data)) { data = new SymbolData(symbol); _SymbolsData[symbol.Key] = data; } return(data); }
private SymbolData GetSymbolData(SymbolInfo sym) { SymbolData symbolData; if (!_SymbolsData.TryGetValue(sym.Key, out symbolData)) { symbolData = new SymbolData(sym); _SymbolsData.Add(sym.Key, symbolData); } return(symbolData); }
private async Task ManageSymbol(SymbolData symData, TimeSlice slice) { var symbol = symData.Feed.Symbol; //foreach operation check if the entry is enough foreach (Operation operation in symData.ActiveOperations) { MyOperationData execData = InitializeOperationData(operation); //check if it needs to be closed if (operation.IsEntryExpired(Algo.Time) && (operation.AmountInvested <= 0 || operation.AmountRemaining / operation.AmountInvested <= 0.03m)) { operation.ScheduleClose(Algo.Time.AddSeconds(5)); continue; } //check if entry needed if (operation.AmountInvested <= 0 && !operation.IsEntryExpired(Algo.Time) && !Algo.EntriesSuspended) { var gotEntry = operation.Type == OperationType.BuyThenSell ? (decimal)symData.Feed.Ask <= operation.Signal.PriceEntry : (decimal)symData.Feed.Bid >= operation.Signal.PriceEntry; //todo check if the last entry order was effectively executed //check if we have already sent an order if (execData.LastEntryOrder == null) { var originalAmount = AssetAmount.Convert(operation.AmountTarget, operation.Symbol.Asset, symData.Feed); var stillToBuy = originalAmount - operation.AmountInvested; if (stillToBuy / originalAmount > 0.2m) { //TODO check requirements for order: price precision, minimum amount, min notional, available money var orderInfo = new OrderInfo() { Symbol = symbol.Key, Type = OrderType.Market, Effect = Algo.DoMarginTrading ? MarginOrderEffect.OpenPosition : MarginOrderEffect.None, Amount = stillToBuy, ClientOrderId = operation.GetNewOrderId(), Direction = execData.EntryDirection }; var ticket = await Algo.Market.PostNewOrder(orderInfo); if (ticket.Status == RequestStatus.Completed) { //Market order was executed - we should expect the trade on next update execData.LastEntryOrder = ticket.Result; } else { //todo log error } } } } //check if exit needed if (execData.LastExitOrder == null) { var shouldExit = operation.Signal.ExpireDate < Algo.Time; var gotTarget = operation.Type == OperationType.BuyThenSell ? (decimal)symData.Feed.Bid >= operation.Signal.PriceTarget : (decimal)symData.Feed.Ask <= operation.Signal.PriceTarget; if (shouldExit || gotTarget) { var amount = operation.AmountRemaining; //TODO check requirements for order: price precision, minimum amount, min notional, available money var orderInfo = new OrderInfo() { Symbol = symbol.Key, Type = OrderType.Market, Effect = Algo.DoMarginTrading ? MarginOrderEffect.ClosePosition : MarginOrderEffect.None, Amount = amount, ClientOrderId = operation.GetNewOrderId(), Direction = execData.ExitDirection }; var ticket = await Algo.Market.PostNewOrder(orderInfo); if (ticket.Status == RequestStatus.Completed) { //Market order was executed - we should expect the trade on next update execData.LastExitOrder = ticket.Result; } else { //TODO log error } } } } }
public async Task Update(TimeSlice slice) { LastUpdate = Market.Time; //update selected symbols var changes = await SymbolsFilter.UpdateAsync(slice); if (changes != SelectedSymbolsChanges.None) { var selectedForOperationsActive = this.ActiveOperations.Select(ao => ao.Symbol).GroupBy(ao => ao.Key).Select(g => g.First()).ToList(); //release feeds of unused symbols foreach (var sym in changes.RemovedSymbols) { SymbolData symbolData = GetSymbolData(sym); symbolData.IsSelectedForTrading = false; //if it doesn't have acrive operations if (!selectedForOperationsActive.Any(aos => aos.Key == sym.Key)) { if (symbolData.Feed != null) { symbolData.Feed.OnData -= Feed_OnData; this.ReleaseFeed(symbolData.Feed); symbolData.Feed = null; } } } //add feeds for added symbols and those that have open operations foreach (var sym in changes.AddedSymbols) { SymbolData symbolData = GetSymbolData(sym); symbolData.IsSelectedForTrading = true; } foreach (var sym in changes.AddedSymbols.Concat(selectedForOperationsActive)) { SymbolData symbolData = GetSymbolData(sym); if (symbolData.Feed == null) { symbolData.Feed = await this.GetSymbolFeed(sym.Key); symbolData.Feed.OnData -= Feed_OnData; symbolData.Feed.OnData += Feed_OnData; } } if (Sentry != null) { await Sentry.OnSymbolsChanged(changes); } if (Allocator != null) { Allocator.OnSymbolsChanged(changes); } if (Executor != null) { Executor.OnSymbolsChanged(changes); } if (RiskManager != null) { RiskManager.OnSymbolsChanged(changes); } } // register trades with their linked operations Operation[] oldOperations = null; List <Operation> operationsResumed = new List <Operation>(); foreach (ITrade trade in slice.Trades) { //first search in active operations var symData = _SymbolsData[trade.Symbol]; var activeOp = symData.ActiveOperations.FirstOrDefault(op => op.IsTradeAssociated(trade)); if (activeOp != null) { if (activeOp.AddTrade(trade)) { Logger.Info($"{Time} - New trade for operation {activeOp.ToString()}: {trade.ToString()}"); } } else { //let's search in closed operations by getting ligtweigth instances if (oldOperations == null) { //lock (DbLock) // oldOperations = oldOperations ?? DbClosedOperations.Find(o => o.CreationTime >= Market.Time.AddDays(-5)).ToArray(); lock (DbLock) oldOperations = QueryClosedOperations(doc => doc["CreationTime"].AsDateTime >= Market.Time.AddDays(-4)); } var oldOp = oldOperations.FirstOrDefault(op => op.IsTradeAssociated(trade)); //if we found an operation let's get a real instance if (oldOp != null) { oldOp = DbClosedOperations.FindById(oldOp.Id); if (oldOp == null) { Logger.Error($"Operation found then not found! {oldOp}."); } } if (oldOp != null) { operationsResumed.Add(oldOp); if (oldOp.AddTrade(trade)) { Logger.Info($"{Time} - New trade for 'old' operation {activeOp}: {trade.ToString()}"); } //check if it got resumed by this new trade if (!oldOp.IsClosed) { this.ResumeOperation(oldOp); Logger.Info($"Resuming 'old' operation {activeOp}."); } else { oldOp.Dispose(); //otherwise we must dispose it } } else { Logger.Debug($"{Time} - New trade {trade.ToString()} without any associated operation"); } } } //dispose the operations that we didn't use if (oldOperations != null) { foreach (var oper in oldOperations.Where(oo => !operationsResumed.Contains(oo))) { oper.Dispose(); } } // call OnUpdate await OnUpdate(slice); // get signals if (Sentry != null) { Sentry.UpdateAsync(slice); } //create operations if (Allocator != null) { Allocator.Update(slice); } //close operations that have been in close queue for enough time lock (DbLock) { this.Db?.BeginTrans(); List <Operation> operationsToClose = _ActiveOperations.Where(op => this.Time >= op.CloseDeadTime).ToList(); foreach (var op in operationsToClose) { if (op.AmountInvested > 0) { Logger.Info("{0} - Closing operation {1}.", Time, op.ToString("c")); } else { Logger.Debug("{0} - Closing operation {1}.", Time, op.ToString("c")); } op.Close(); _ActiveOperations.Remove(op); if (this.Config.SaveData || op.AmountInvested > 0) { this._ClosedOperations.Add(op); } this.SymbolsData[op.Symbol.Key].CloseOperation(op); if (this.Config.SaveData) { lock (DbLock) { //update database DbActiveOperations.Delete(op.Id); DbClosedOperations.Upsert(op); } } } this.Db?.Commit(); } //add new operations that have been created foreach (var op in slice.NewOperations) { AddActiveOperation(op); } //manage orders if (Executor != null) { await Executor.Update(slice); } //manage risk if (RiskManager != null) { await RiskManager.Update(slice); } while (Commands.Count > 0) { try { if (Commands.TryDequeue(out Command command)) { await command.Run(); } } catch (Exception ex) { Logger.Error($"Error while running command: {ex.Message}."); } } if (Config.SaveData) { SaveNonVolatileVars(); } }
public (decimal price, decimal amount) ClampOrderAmount(SymbolData symData, TradeDirection tradeDirection, (decimal price, decimal amount) adj)