using MarketPriceLookup.Helpers; using Microsoft.VisualBasic.FileIO; using System; using System.Collections.Generic; using System.IO; using System.Linq; using System.Net.Http; using System.Net.Http.Json; using System.Text.Json; using MathNet.Numerics.Statistics; using System.Data; namespace MarketPriceLookup.Common.Helpers { public static class MarketPricesHelper { private static readonly Dictionary priceFile = new Dictionary(); public static Dictionary GetAllPrices() { if (priceFile.Count == 0) { //HydrateDictionaryCSV(); HydrateDictionaryTarkovDev(); } return priceFile; } private static void HydrateDictionaryTarkovDev() { var request = new Dictionary() { {"query", "{ items(lang: en) { name avg24hPrice changeLast48hPercent id historicalPrices{ price timestamp } }}"} }; using (var httpClient = new HttpClient()) { // call tarkov dev api var httpResponse = httpClient.PostAsJsonAsync("https://api.tarkov.dev/graphql", request).GetAwaiter().GetResult(); var responseContent = httpResponse.Content.ReadAsStringAsync().GetAwaiter().GetResult(); var parsedResponse = JsonSerializer.Deserialize(responseContent, new JsonSerializerOptions()); // iterate over all items returned and filter out bad prices foreach (var item in parsedResponse.data.items) { if (item.historicalPrices.Length == 0) { LoggingHelpers.LogError($"unable to add item {item.id} {item.name} with no historical prices, ignoring"); continue; } if (item.changeLast48hPercent > 110) { LoggingHelpers.LogWarning($"Item {item.id} {item.name} Has had recent {item.changeLast48hPercent}% increase in price. {item.historicalPrices.Length} price values"); continue; } var averagedItemPrice = GetAveragedPrice(item); if (averagedItemPrice == 0) { LoggingHelpers.LogError($"unable to add item {item.id} {item.name} with average price of 0, ignoring"); continue; } if (item.name.Contains(" (0/")) { LoggingHelpers.LogWarning($"Skipping 0 durability item: {item.id} {item.name}"); continue; } if (priceFile.ContainsKey(item.id)) { // Item already exists in the csv var existingItem = priceFile[item.id]; LoggingHelpers.LogError($"Unable to add item: {item.id} {item.name} (price: {item.avg24hPrice}). existing item: {existingItem.TemplateId} {existingItem.Name} (price: {existingItem.Average24hPrice})"); if (existingItem.Average24hPrice < averagedItemPrice) { LoggingHelpers.LogError($"Price diff found: existing item price: {existingItem.Average7DaysPrice} new item price: {averagedItemPrice}, using larger price"); priceFile.Remove(item.id); } } // Item not already in dictionary, add it if (!priceFile.ContainsKey(item.id)) { priceFile.Add(item.id, new Prices { Name = item.name, //Price = price, Average24hPrice = item.avg24hPrice, Average7DaysPrice = averagedItemPrice, PricePoints = item.historicalPrices.Length, //Trader = trader, //BuyPackPrice = buyBackPrice, //Currency = currency, TemplateId = item.id, //TraderPrice = traderPrice, }); LoggingHelpers.LogSuccess($"Adding item: {item.id} {item.name}"); } } } } /// /// Get items average flea price from all readings taken over the past 14 days /// /// private static int GetAveragedPrice(Item item) { var fourteenDaysAgoTimestamp = DateTimeOffset.UtcNow.AddDays(-14).ToUnixTimeSeconds(); var filteredPrices = item.historicalPrices.Where(x => long.Parse(x.timestamp) > fourteenDaysAgoTimestamp).OrderBy(x=> x.price).ToList(); if (filteredPrices.Count == 0) { // x day filter means no prices, use all data filteredPrices = item.historicalPrices.ToList(); } if (filteredPrices.Count == 1) { return 0; } var prices = filteredPrices.Select(x => (double)x.price).ToArray(); var avgMean = prices.Average(); var standardDev = prices.StandardDeviation(); var upperCutoff = standardDev * 1.5; var lowerCutoff = standardDev * 2; var lowerBound = avgMean - lowerCutoff; var upperBound = avgMean + upperCutoff; //var outliers = prices.Where(x => x < lowerBound || x > upperBound).ToList(); var pricesWithOutliersRemoved = prices.Where(x => x >= lowerBound && x <= upperBound).ToList(); return (int)Math.Round(pricesWithOutliersRemoved.Average()); } public static Prices GetItemPrice(string key) { // parse csv if dictionary is empty if (priceFile.Count == 0) { //HydrateDictionaryCSV(); HydrateDictionaryTarkovDev(); } if (!priceFile.ContainsKey(key)) { return null; } return priceFile[key]; } private static void HydrateDictionaryCSV() { var workingPath = Directory.GetCurrentDirectory(); var inputPath = $"{workingPath}//input"; var filePath = $"{inputPath}//marketPrices.csv"; DiskHelpers.CreateDirIfDoesntExist(inputPath); using (TextFieldParser csvParser = new TextFieldParser(filePath)) { csvParser.CommentTokens = new string[] { "#" }; csvParser.SetDelimiters(new string[] { "," }); csvParser.HasFieldsEnclosedInQuotes = true; // Skip the row with the column names csvParser.ReadLine(); csvParser.ReadLine(); while (!csvParser.EndOfData) { // Read current line fields, pointer moves to the next line. string[] fields = csvParser.ReadFields(); //string uid = fields[0]; string name = fields[1]; int price = int.Parse(fields[2]); int avg24hPrice = int.Parse(fields[3]); int avg7daysPrice = int.Parse(fields[4]); string trader = fields[5]; //int buyBackPrice = int.Parse(fields[6]); string currency = GetCurrencyType(fields[7]); string bsgId = fields[8]; int traderPrice = int.Parse(fields[9]); if (avg7daysPrice == 0) { LoggingHelpers.LogError($"unable to add item {bsgId} {name} with average price of 0, ignoring"); continue; } if (name.Contains(" (0/")) { LoggingHelpers.LogWarning($"Skipping 0 durability item: {bsgId} {name}"); continue; } if (priceFile.ContainsKey(bsgId)) { //oh no, item already exists in the csv var existingItem = priceFile[bsgId]; LoggingHelpers.LogError($"Unable to add item: {bsgId} {name}. existing item: {existingItem.TemplateId} {existingItem.Name}"); if (existingItem.Average7DaysPrice < avg7daysPrice) { LoggingHelpers.LogError($"Price diff found: existing item price: {existingItem.Average7DaysPrice} new item price: {avg7daysPrice}, using larger price"); priceFile.Remove(bsgId); } } if (!priceFile.ContainsKey(bsgId)) { priceFile.Add(bsgId, new Prices { Name = name, Price = price, Average24hPrice = avg24hPrice, Average7DaysPrice = avg7daysPrice, Trader = trader, //BuyPackPrice = buyBackPrice, Currency = currency, TemplateId = bsgId, TraderPrice = traderPrice, }); } LoggingHelpers.LogSuccess($"Adding item: {bsgId} {name}"); } } } private static string GetCurrencyType(string input) { return input switch { "₽" => "Rouble", "$" => "Dollar", "€" => "Euro", _ => string.Empty, }; } } }