250 lines
10 KiB
C#

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<string, Prices> priceFile = new Dictionary<string, Prices>();
public static Dictionary<string, Prices> GetAllPrices()
{
if (priceFile.Count == 0)
{
//HydrateDictionaryCSV();
HydrateDictionaryTarkovDev();
}
return priceFile;
}
private static void HydrateDictionaryTarkovDev()
{
var request = new Dictionary<string, string>()
{
{"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<TarkovDevResponse>(responseContent, new JsonSerializerOptions());
// iterate over all items returned and filter out bad prices
foreach (var item in parsedResponse.data.items)
{
if (item.historicalPrices == null || 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 ?? 0,
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}");
}
}
}
}
/// <summary>
/// Get items average flea price from all readings taken over the past 14 days
/// </summary>
/// <param name="item"></param>
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,
};
}
}
}