public async Task <Data.Daily.Price> GetLastPrice(Data.Asset asset) { DateTime lastMarketClose = DateTime.UtcNow - new TimeSpan(1, 0, 0, 0); object result = await Alpaca.GetTime_LastMarketClose(); if (result is DateTime dt) { lastMarketClose = dt; } else { Prompt.WriteLine("Unable to access market schedule/times via Alpaca API"); } DateTime validity = await Database.GetValidity_Daily(asset); if (validity.CompareTo(lastMarketClose) < 0) { await Update_TSD(new List <Data.Asset>() { asset }); await Task.Delay(2000); // Allow Database update thread to get ahead } Data.Daily dd = await Database.GetData_Daily(asset); return(dd.Prices.Last()); }
public async Task Update_TSD(List <Data.Asset> assets) { List <Task> threads = new List <Task>(); DateTime lastMarketClose = DateTime.UtcNow - new TimeSpan(1, 0, 0, 0); object result = await Alpaca.GetTime_LastMarketClose(); if (result is DateTime dt) { lastMarketClose = dt; } else { Prompt.WriteLine("Unable to access market schedule/times via Alpaca API"); } int retryCounter = 0; await Settings.ClearCache(); // Routine emptying of cache directory // Iterate all symbols in list (assets), call API to download data, write to files in library for (int i = 0; i < assets.Count; i++) { Prompt.Write($" [{i + 1:0000} / {assets.Count:0000}] {assets[i].Symbol,-8} "); /* Check validity timestamp against last known market close */ DateTime validity = await Database.GetValidity_Daily(assets[i]); if (validity.CompareTo(lastMarketClose) > 0) { await Task.Delay(10); // Allows GUI responsiveness Prompt.WriteLine("Database current. Skipping."); continue; } Prompt.Write("Requesting data. "); object output = null; Data.Daily dd = new Data.Daily(); if (Settings.Library_DataProvider == Settings.Option_DataProvider.Alpaca) { output = await Alpaca.GetData_Daily(assets[i], Settings.Library_LimitDailyEntries); } else if (Settings.Library_DataProvider == Settings.Option_DataProvider.AlphaVantage) { string apiOutput = ""; string apiFilePath = Path.Combine(Settings.GetCacheDirectory(), Path.GetRandomFileName()); string apiCache = $"{apiFilePath}.cache"; string apiLockout = $"{apiFilePath}.lockout"; DateTime timeoutTime = DateTime.Now + new TimeSpan(0, 1, 0); // 1 minute timeout _ = AlphaVantage.CacheData_Daily(assets[i].Symbol, apiCache, apiLockout); while (!File.Exists(apiLockout) && DateTime.Now <= timeoutTime) { await Task.Delay(500); } if (File.Exists(apiLockout)) { if (!File.Exists(apiCache)) { apiOutput = "ERROR:TIMEOUT"; } else { StreamReader sr = new StreamReader(apiCache); apiOutput = await sr.ReadToEndAsync(); sr.Close(); sr.Dispose(); } try { if (File.Exists(apiCache)) { File.Delete(apiCache); } if (File.Exists(apiLockout)) { File.Delete(apiLockout); } } catch (Exception ex) { await Log.Error($"{MethodBase.GetCurrentMethod().DeclaringType}: {MethodBase.GetCurrentMethod().Name}", ex); } } else { apiOutput = "ERROR:TIMEOUT"; } if (apiOutput == "ERROR:INVALID" || apiOutput == "ERROR:INVALIDKEY" || apiOutput == "ERROR:EXCEEDEDCALLS" || apiOutput == "ERROR:EXCEPTION" || apiOutput == "ERROR:TIMEOUT" || apiOutput.StartsWith("ERROR:WEBEXCEPTION:")) { output = apiOutput; } else { Prompt.Write("Parsing. "); output = await AlphaVantage.ParseData_Daily(apiOutput, Settings.Library_LimitDailyEntries); } } if (output is Data.Daily pmdd) { dd = pmdd; retryCounter = 0; } else if (output is string pms) { if (pms == "Too Many Requests" || // Alpaca's return message for exceeding API calls pms == "ERROR:EXCEEDEDCALLS") // Alpha Vantage's return message for exceeding API calls { Prompt.WriteLine("Exceeded API calls per minute- pausing for 30 seconds."); await Task.Delay(30000); i--; retryCounter = 0; continue; } else if (pms == "ERROR:EXCEPTION" || pms == "ERROR:TIMEOUT") { if (pms == "ERROR:EXCEPTION") { Prompt.WriteLine($"Error, Attempt #{retryCounter + 1}"); } else if (pms == "ERROR:TIMEOUT") { Prompt.WriteLine($"Timeout, Attempt #{retryCounter + 1}"); } if (retryCounter < 4) { i--; retryCounter++; } else { retryCounter = 0; } continue; } else { Prompt.WriteLine($"Error: {output}"); retryCounter = 0; continue; } } else if (output == null) { Prompt.WriteLine($"Error: See Error Log."); retryCounter = 0; continue; } dd.Asset = assets[i]; /* Calculate metrics, stock indicators */ Prompt.Write("Calculating indicators. "); dd = await Calculate.Metrics(dd); /* Save to Database * Use threading for highly improved speed! */ Prompt.WriteLine("Updating Database.", ConsoleColor.Green); Task thread = new Task(async() => { await Database.SetData_Daily(dd); }); thread.Start(); threads.Add(thread); int awaiting = threads.FindAll(t => t.Status == TaskStatus.Running).Count; if (awaiting >= 10) { await Task.Delay(1000); } } int finishing = threads.FindAll(t => t.Status == TaskStatus.Running).Count; if (finishing > 0) { Prompt.WriteLine($" Completing {finishing} remaining background Database tasks."); await Task.Delay(5000); finishing = threads.FindAll(t => t.Status == TaskStatus.Running).Count; } }
// Calculates all the indicator metrics for a dataset public static async Task <Data.Daily> Metrics(Data.Daily dd) { // Prepare the data structures and tie Prices <-> Metrics dd.Prices.Sort((a, b) => a.Date.CompareTo(b.Date)); foreach (Data.Daily.Price p in dd.Prices) { Data.Daily.Metric m = new Data.Daily.Metric() { Price = p }; p.Metric = m; dd.Metrics.Add(m); } // Run all calculations! Get all indicators! int amount = dd.Prices.Count; SmaResult[] sma7 = amount > 7 ? Indicator.GetSma(dd.Prices, 7).ToArray() : null; SmaResult[] sma20 = amount > 20 ? Indicator.GetSma(dd.Prices, 20).ToArray() : null; SmaResult[] sma50 = amount > 50 ? Indicator.GetSma(dd.Prices, 50).ToArray() : null; SmaResult[] sma100 = amount > 100 ? Indicator.GetSma(dd.Prices, 100).ToArray() : null; SmaResult[] sma200 = amount > 200 ? Indicator.GetSma(dd.Prices, 200).ToArray() : null; EmaResult[] ema7 = amount > 110 ? Indicator.GetEma(dd.Prices, 7).ToArray() : null; EmaResult[] ema20 = amount > 120 ? Indicator.GetEma(dd.Prices, 20).ToArray() : null; EmaResult[] ema50 = amount > 150 ? Indicator.GetEma(dd.Prices, 50).ToArray() : null; EmaResult[] dema7 = amount > 120 ? Indicator.GetDoubleEma(dd.Prices, 7).ToArray() : null; EmaResult[] dema20 = amount > 140 ? Indicator.GetDoubleEma(dd.Prices, 20).ToArray() : null; EmaResult[] dema50 = amount > 200 ? Indicator.GetDoubleEma(dd.Prices, 50).ToArray() : null; EmaResult[] tema7 = amount > 130 ? Indicator.GetTripleEma(dd.Prices, 7).ToArray() : null; EmaResult[] tema20 = amount > 160 ? Indicator.GetTripleEma(dd.Prices, 20).ToArray() : null; EmaResult[] tema50 = amount > 250 ? Indicator.GetTripleEma(dd.Prices, 50).ToArray() : null; RocResult[] roc7 = amount > 8 ? Indicator.GetRoc(dd.Prices, 7).ToArray() : null; RocResult[] roc14 = amount > 15 ? Indicator.GetRoc(dd.Prices, 14).ToArray() : null; RocResult[] roc50 = amount > 51 ? Indicator.GetRoc(dd.Prices, 50).ToArray() : null; RocResult[] roc100 = amount > 101 ? Indicator.GetRoc(dd.Prices, 100).ToArray() : null; RocResult[] roc200 = amount > 201 ? Indicator.GetRoc(dd.Prices, 200).ToArray() : null; RsiResult[] rsi = amount > 140 ? Indicator.GetRsi(dd.Prices).ToArray() : null; BollingerBandsResult[] bb = amount > 20 ? Indicator.GetBollingerBands(dd.Prices).ToArray() : null; MacdResult[] macd = amount > 140 ? Indicator.GetMacd(dd.Prices).ToArray() : null; StochResult[] stoch = amount > 20 ? Indicator.GetStoch(dd.Prices).ToArray() : null; ChopResult[] chop = amount > 15 ? Indicator.GetChop(dd.Prices).ToArray() : null; // Put indicator data back into data set for usability for (int i = 0; i < dd.Metrics.Count; i++) { try { dd.Metrics[i].SMA7 = sma7?[i].Sma; dd.Metrics[i].SMA20 = sma20?[i].Sma; dd.Metrics[i].SMA50 = sma50?[i].Sma; dd.Metrics[i].SMA100 = sma100?[i].Sma; dd.Metrics[i].SMA200 = sma200?[i].Sma; dd.Metrics[i].EMA7 = ema7?[i].Ema; dd.Metrics[i].EMA20 = ema20?[i].Ema; dd.Metrics[i].EMA50 = ema50?[i].Ema; dd.Metrics[i].DEMA7 = dema7?[i].Ema; dd.Metrics[i].DEMA20 = dema20?[i].Ema; dd.Metrics[i].DEMA50 = dema50?[i].Ema; dd.Metrics[i].TEMA7 = tema7?[i].Ema; dd.Metrics[i].TEMA20 = tema20?[i].Ema; dd.Metrics[i].TEMA50 = tema50?[i].Ema; dd.Metrics[i].ROC7 = roc7?[i].Roc; dd.Metrics[i].ROC14 = roc14?[i].Roc; dd.Metrics[i].ROC50 = roc50?[i].Roc; dd.Metrics[i].ROC100 = roc100?[i].Roc; dd.Metrics[i].ROC200 = roc200?[i].Roc; dd.Metrics[i].Choppiness = chop?[i].Chop; dd.Metrics[i].RSI = rsi?[i].Rsi; dd.Metrics[i].BB = bb?[i]; dd.Metrics[i].MACD = macd?[i]; dd.Metrics[i].Stochastic = stoch?[i]; } catch (Exception ex) { Prompt.WriteLine($"Error casting indicators to dataset for {dd.Asset.Symbol}!", ConsoleColor.Red); await Log.Error($"{MethodBase.GetCurrentMethod().DeclaringType}: {MethodBase.GetCurrentMethod().Name}", ex); } } return(dd); }