2021-09-19 18:29:16 +01:00
using MarketPriceLookup.Helpers ;
using Microsoft.VisualBasic.FileIO ;
using System ;
using System.Collections.Generic ;
using System.IO ;
2023-04-12 13:23:31 +01:00
using System.Linq ;
using System.Net.Http ;
using System.Net.Http.Json ;
using System.Text.Json ;
2023-06-29 12:36:11 +01:00
using MathNet.Numerics.Statistics ;
using System.Data ;
2021-09-19 18:29:16 +01:00
namespace MarketPriceLookup.Common.Helpers
{
public static class MarketPricesHelper
{
private static readonly Dictionary < string , Prices > priceFile = new Dictionary < string , Prices > ( ) ;
2023-02-05 11:57:03 +00:00
public static Dictionary < string , Prices > GetAllPrices ( )
{
if ( priceFile . Count = = 0 )
{
2023-04-12 13:23:31 +01:00
//HydrateDictionaryCSV();
HydrateDictionaryTarkovDev ( ) ;
2023-02-05 11:57:03 +00:00
}
return priceFile ;
}
2023-04-12 13:23:31 +01:00
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 . Length = = 0 )
{
LoggingHelpers . LogError ( $"unable to add item {item.id} {item.name} with no historical prices, ignoring" ) ;
continue ;
}
2024-03-18 11:12:49 +00:00
if ( item . changeLast48hPercent > 110 )
2023-04-12 13:23:31 +01:00
{
LoggingHelpers . LogWarning ( $"Item {item.id} {item.name} Has had recent {item.changeLast48hPercent}% increase in price. {item.historicalPrices.Length} price values" ) ;
2024-03-18 11:12:49 +00:00
continue ;
2023-04-12 13:23:31 +01:00
}
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 ,
2023-11-12 10:45:54 +00:00
PricePoints = item . historicalPrices . Length ,
2023-04-12 13:23:31 +01:00
//Trader = trader,
//BuyPackPrice = buyBackPrice,
//Currency = currency,
TemplateId = item . id ,
//TraderPrice = traderPrice,
} ) ;
LoggingHelpers . LogSuccess ( $"Adding item: {item.id} {item.name}" ) ;
}
}
}
}
/// <summary>
2023-06-29 12:36:11 +01:00
/// Get items average flea price from all readings taken over the past 14 days
2023-04-12 13:23:31 +01:00
/// </summary>
/// <param name="item"></param>
private static int GetAveragedPrice ( Item item )
{
2023-06-29 12:36:11 +01:00
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 )
2023-04-12 13:23:31 +01:00
{
2023-06-29 12:36:11 +01:00
// x day filter means no prices, use all data
filteredPrices = item . historicalPrices . ToList ( ) ;
2023-04-12 13:23:31 +01:00
}
2023-06-29 12:36:11 +01:00
if ( filteredPrices . Count = = 1 )
2023-04-12 13:23:31 +01:00
{
2023-06-29 12:39:35 +01:00
return 0 ;
2023-04-12 13:23:31 +01:00
}
2023-06-29 12:36:11 +01:00
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 ( ) ) ;
2023-04-12 13:23:31 +01:00
}
2021-09-19 18:29:16 +01:00
public static Prices GetItemPrice ( string key )
{
// parse csv if dictionary is empty
if ( priceFile . Count = = 0 )
{
2023-04-12 13:23:31 +01:00
//HydrateDictionaryCSV();
HydrateDictionaryTarkovDev ( ) ;
2023-02-05 11:57:03 +00:00
}
if ( ! priceFile . ContainsKey ( key ) )
{
return null ;
}
return priceFile [ key ] ;
}
2023-04-12 13:23:31 +01:00
private static void HydrateDictionaryCSV ( )
2023-02-05 11:57:03 +00:00
{
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 ( ) ;
2021-09-19 18:29:16 +01:00
2023-02-05 11:57:03 +00:00
while ( ! csvParser . EndOfData )
2021-09-19 18:29:16 +01:00
{
2023-02-05 11:57:03 +00:00
// 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 ;
}
2021-09-19 18:29:16 +01:00
2023-02-05 11:57:03 +00:00
if ( name . Contains ( " (0/" ) )
{
LoggingHelpers . LogWarning ( $"Skipping 0 durability item: {bsgId} {name}" ) ;
continue ;
}
2021-09-19 18:29:16 +01:00
2023-02-05 11:57:03 +00:00
if ( priceFile . ContainsKey ( bsgId ) )
2021-09-19 18:29:16 +01:00
{
2023-02-05 11:57:03 +00:00
//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 )
2021-09-19 18:29:16 +01:00
{
2023-02-05 11:57:03 +00:00
LoggingHelpers . LogError ( $"Price diff found: existing item price: {existingItem.Average7DaysPrice} new item price: {avg7daysPrice}, using larger price" ) ;
priceFile . Remove ( bsgId ) ;
2021-09-19 18:29:16 +01:00
}
2023-02-05 11:57:03 +00:00
}
if ( ! priceFile . ContainsKey ( bsgId ) )
{
priceFile . Add ( bsgId , new Prices
2021-09-19 18:29:16 +01:00
{
2023-02-05 11:57:03 +00:00
Name = name ,
Price = price ,
Average24hPrice = avg24hPrice ,
Average7DaysPrice = avg7daysPrice ,
Trader = trader ,
//BuyPackPrice = buyBackPrice,
Currency = currency ,
TemplateId = bsgId ,
TraderPrice = traderPrice ,
} ) ;
2021-09-19 18:29:16 +01:00
}
2023-02-05 11:57:03 +00:00
LoggingHelpers . LogSuccess ( $"Adding item: {bsgId} {name}" ) ;
2021-09-19 18:29:16 +01:00
2023-02-05 11:57:03 +00:00
}
}
2021-09-19 18:29:16 +01:00
}
private static string GetCurrencyType ( string input )
{
return input switch
{
"₽" = > "Rouble" ,
"$" = > "Dollar" ,
"€" = > "Euro" ,
_ = > string . Empty ,
} ;
}
}
}