Skip to content

Commit

Permalink
- Make the app single threaded for easy debugging.
Browse files Browse the repository at this point in the history
- Cache FindSymbolByIdentifier logic as well
  • Loading branch information
VibeNL committed Oct 30, 2023
1 parent 28937c9 commit 0564d6e
Show file tree
Hide file tree
Showing 3 changed files with 69 additions and 26 deletions.
10 changes: 4 additions & 6 deletions GhostfolioSidekick/FileImporter/RecordBaseImporter.cs
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,6 @@ public abstract class RecordBaseImporter<T> : IFileImporter
{
protected readonly IGhostfolioAPI api;

private ParallelOptions parallelOptions = new ParallelOptions { MaxDegreeOfParallelism = 1 };

protected RecordBaseImporter(IGhostfolioAPI api)
{
this.api = api;
Expand Down Expand Up @@ -47,15 +45,15 @@ public virtual async Task<bool> CanParseActivities(IEnumerable<string> filenames
CsvConfiguration csvConfig = GetConfig();

var list = new ConcurrentDictionary<string, Model.Activity>();
await Parallel.ForEachAsync(filenames, parallelOptions, async (filename, c1) =>
foreach (var filename in filenames)
{
using var streamReader = GetStreamReader(filename);
using var csvReader = new CsvReader(streamReader, csvConfig);
csvReader.Read();
csvReader.ReadHeader();
var records = csvReader.GetRecords<T>().ToList();

await Parallel.ForEachAsync(records, parallelOptions, async (record, c) =>
foreach (var record in records)
{
var orders = await ConvertOrders(record, account, records);

Expand All @@ -66,8 +64,8 @@ await Parallel.ForEachAsync(records, parallelOptions, async (record, c) =>
list.TryAdd(order.ReferenceCode, order);
}
}
});
});
};
};

SetActivitiesToAccount(account, list.Values);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,7 @@ Tuple<Asset, Currency, DateTime, decimal, decimal> GetKey(Activity x)
var account = await api.GetAccountByName(accountName) ?? throw new NotSupportedException($"Account not found {accountName}");
account.Balance.Empty();

Parallel.ForEach(filenames, filename =>
foreach (var filename in filenames)
{
CsvConfiguration csvConfig = GetConfig();

Expand All @@ -80,18 +80,18 @@ static string DetermineKey(BaaderBankRKKRecord x)
}
});
}
});
};

Parallel.ForEach(wumRecords, async record =>
foreach (var record in wumRecords)
{
var order = await ConvertToOrder(record, rkkRecords);
if (order != null)
{
list.TryAdd(GetKey(order), order);
}
});
};

Parallel.ForEach(rkkRecords, async record =>
foreach (var record in rkkRecords)
{
BaaderBankRKKRecord r = record.Value;
var order = await ConvertToOrder(r);
Expand All @@ -104,7 +104,7 @@ static string DetermineKey(BaaderBankRKKRecord x)
{
account.Balance.SetKnownBalance(new Money(r.Currency, r.UnitPrice.GetValueOrDefault(0), r.Date.ToDateTime(TimeOnly.MinValue)));
}
});
};

account.ReplaceActivities(list.Values);

Expand Down
73 changes: 59 additions & 14 deletions GhostfolioSidekick/Ghostfolio/API/GhostfolioAPI.cs
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ namespace GhostfolioSidekick.Ghostfolio.API
public partial class GhostfolioAPI : IGhostfolioAPI
{
private readonly IApplicationSettings settings;
private readonly IMemoryCache memoryCache;
private ILogger<GhostfolioAPI> logger;
private readonly ModelToContractMapper modelToContractMapper;
private readonly SymbolMapper mapper;
Expand All @@ -34,6 +35,7 @@ public GhostfolioAPI(
}

this.settings = settings;
this.memoryCache = memoryCache;
this.logger = logger ?? throw new ArgumentNullException(nameof(logger));
restCall = new RestCall(memoryCache, logger, settings.GhostfolioUrl, settings.GhostfolioAccessToken);
modelToContractMapper = new ModelToContractMapper(new CurrentPriceCalculator(this));
Expand Down Expand Up @@ -107,7 +109,7 @@ public async Task UpdateAccount(Model.Account account)
}
}

public async Task<Money?> GetMarketPrice(Model.Asset asset, DateTime date)
public async Task<Money?> GetMarketPrice(Asset asset, DateTime date)
{
var content = await restCall.DoRestGet($"api/v1/admin/market-data/{asset.DataSource}/{asset.Symbol}", CacheDuration.Short());
var market = JsonConvert.DeserializeObject<Contract.MarketDataList>(content);
Expand All @@ -122,21 +124,64 @@ public async Task UpdateAccount(Model.Account account)
return new Money(asset.Currency, marketData.MarketPrice, date);
}

public async Task<Model.Asset?> FindSymbolByIdentifier(string? identifier, Func<IEnumerable<Model.Asset>, Model.Asset?> selector)
public async Task<Asset?> FindSymbolByIdentifier(string? identifier, Func<IEnumerable<Asset>, Asset?> selector)
{
identifier = mapper.MapSymbol(identifier);
if (identifier == null)
{
return null;
}

var content = await restCall.DoRestGet($"api/v1/symbol/lookup?query={identifier.Trim()}", CacheDuration.Long());
if (memoryCache.TryGetValue(identifier, out Asset? asset))
{
return asset;
}

var mappedIdentifier = mapper.MapSymbol(identifier);

var content = await restCall.DoRestGet($"api/v1/symbol/lookup?query={mappedIdentifier.Trim()}", CacheDuration.Long());
var symbolProfileList = JsonConvert.DeserializeObject<SymbolProfileList>(content);

var assets = symbolProfileList.Items.Select(x => ContractToModelMapper.ParseSymbolProfile(x));

Asset? filteredAsset;
if (selector == null)
{
return LogIfEmpty(assets.FirstOrDefault(), identifier);
filteredAsset = DefaultSelector(assets);
AddToCache(identifier, filteredAsset, memoryCache);
return LogIfEmpty(filteredAsset, mappedIdentifier);
}

filteredAsset = selector(assets);
AddToCache(identifier, filteredAsset, memoryCache);
return LogIfEmpty(filteredAsset, mappedIdentifier);

static void AddToCache(string identifier, Asset? asset, IMemoryCache cache)
{
cache.Set(identifier, asset, CacheDuration.Long());
}

static Asset? DefaultSelector(IEnumerable<Asset> assets)
{
var asset = assets.OrderBy(x => CurrencyToNumber(x.Currency)).OrderBy(x => x.Name.Length).FirstOrDefault();
return asset;
}

return LogIfEmpty(selector(assets), identifier);
static int CurrencyToNumber(Currency currency)
{
// TODO: Make it more general
switch (currency.Symbol)
{
case "EUR":
return 1;
case "USD":
return 2;
case "GBP":
case "GBp":
return 3;
default:
return int.MaxValue;
}
}
}

public async Task<Money?> GetConvertedPrice(Money money, Currency targetCurrency, DateTime date)
Expand Down Expand Up @@ -196,7 +241,7 @@ public async Task DeleteSymbol(Model.SymbolProfile marketData)
logger.LogInformation($"Deleted symbol {marketData.Symbol}");
}

public async Task CreateManualSymbol(Model.Asset asset)
public async Task CreateManualSymbol(Asset asset)
{
var o = new JObject
{
Expand Down Expand Up @@ -280,7 +325,7 @@ public async Task UpdateMarketData(Model.SymbolProfile marketData)
var content = await restCall.DoRestGet($"api/v1/order", CacheDuration.None());
var existingActivities = JsonConvert.DeserializeObject<ActivityList>(content).Activities;

var assets = new ConcurrentDictionary<string, Model.Asset>();
var assets = new ConcurrentDictionary<string, Asset>();
return existingActivities.Select(x => ContractToModelMapper.MapActivity(x, assets));
}

Expand All @@ -306,7 +351,7 @@ private async Task<GenericInfo> GetInfo()
return JsonConvert.DeserializeObject<GenericInfo>(content);
}

private async Task WriteOrder(Ghostfolio.Contract.Activity activity)
private async Task WriteOrder(Contract.Activity activity)
{
if (activity.UnitPrice == 0 && activity.Quantity == 0)
{
Expand Down Expand Up @@ -369,7 +414,7 @@ private async Task UpdateBalance(Model.Account account, decimal balance)
await restCall.DoRestPut($"api/v1/account/{account.Id}", res);
}

private async Task<string> ConvertToBody(Ghostfolio.Contract.Activity activity)
private async Task<string> ConvertToBody(Contract.Activity activity)
{
var o = new JObject();
o["accountId"] = activity.AccountId;
Expand All @@ -394,7 +439,7 @@ private async Task<string> ConvertToBody(Ghostfolio.Contract.Activity activity)
return res;
}

private IEnumerable<MergeOrder> MergeOrders(IEnumerable<Ghostfolio.Contract.Activity> ordersFromFiles, IEnumerable<Contract.Activity> existingOrders)
private IEnumerable<MergeOrder> MergeOrders(IEnumerable<Contract.Activity> ordersFromFiles, IEnumerable<Contract.Activity> existingOrders)
{
var pattern = @"Transaction Reference: \[(.*?)\]";

Expand Down Expand Up @@ -437,7 +482,7 @@ private IEnumerable<MergeOrder> MergeOrders(IEnumerable<Ghostfolio.Contract.Acti
}).Union(existingOrdersWithMatchFlag.Where(x => !x.IsMatched).Select(x => new MergeOrder(Operation.Removed, null, x.Activity)));
}

private bool AreEquals(Ghostfolio.Contract.Activity fo, Contract.Activity eo)
private bool AreEquals(Contract.Activity fo, Contract.Activity eo)
{
return
(fo.SymbolProfile?.Symbol == eo.SymbolProfile?.Symbol || fo.Type == Ghostfolio.Contract.ActivityType.INTEREST) && // Interest create manual symbols
Expand All @@ -448,7 +493,7 @@ private bool AreEquals(Ghostfolio.Contract.Activity fo, Contract.Activity eo)
fo.Date == eo.Date;
}

private Model.Asset? LogIfEmpty(Model.Asset? asset, string identifier)
private Asset? LogIfEmpty(Asset? asset, string identifier)
{
if (asset == null)
{
Expand All @@ -458,7 +503,7 @@ private bool AreEquals(Ghostfolio.Contract.Activity fo, Contract.Activity eo)
return asset;
}

private Ghostfolio.Contract.Activity Round(Ghostfolio.Contract.Activity activity)
private Contract.Activity Round(Contract.Activity activity)
{
decimal Round(decimal? value)
{
Expand Down

0 comments on commit 0564d6e

Please sign in to comment.