Skip to content

Commit

Permalink
Add finance plugin with stock price support
Browse files Browse the repository at this point in the history
  • Loading branch information
dkgv committed Jan 27, 2021
1 parent 2311536 commit 9b62c72
Show file tree
Hide file tree
Showing 11 changed files with 247 additions and 1 deletion.
2 changes: 1 addition & 1 deletion Pinpoint.Core/AppConstants.cs
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,6 @@ public static class AppConstants
public static readonly string SettingsFilePath = MainDirectory + "settings.json";
public static readonly string HotkeyIdentifier = "Show/Hide";

public const string Version = "0.0.6";
public const string Version = "0.0.7";
}
}
43 changes: 43 additions & 0 deletions Pinpoint.Plugin.Finance/FinancePlugin.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Pinpoint.Core;

namespace Pinpoint.Plugin.Finance
{
public class FinancePlugin : IPlugin
{
private readonly YahooFinanceApi _yahooFinanceApi = new YahooFinanceApi(TimeSpan.FromMinutes(2));

public PluginMeta Meta { get; set; } = new PluginMeta("Finance Plugin", PluginPriority.Highest);

public void Load()
{
}

public void Unload()
{
}

public async Task<bool> Activate(Query query)
{
return query.Parts.Length == 1
&& query.Prefix().Equals("$")
&& query.RawQuery.Length >= 3
&& query.Parts[0].Substring(1).All(ch => !char.IsDigit(ch) && (char.IsUpper(ch) || !char.IsLetter(ch)))
&& query.Parts[0].Substring(1).Length < 5;
}

public async IAsyncEnumerable<AbstractQueryResult> Process(Query query)
{
// $GME => GME
var ticker = query.Parts[0].Substring(1);
var response = await _yahooFinanceApi.Lookup(ticker);
if (response != null)
{
yield return new FinanceSearchResult(response);
}
}
}
}
33 changes: 33 additions & 0 deletions Pinpoint.Plugin.Finance/FinanceSearchResult.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
using System;
using FontAwesome5;
using Pinpoint.Core;

namespace Pinpoint.Plugin.Finance
{
public class FinanceSearchResult : UrlQueryResult
{
public FinanceSearchResult(YahooFinanceResponse response) : base(response.Url)
{
var pricePrefix = response.RegularMarketPrice > response.PreviousClose
? "+"
: "-";
var priceChange = Math.Abs(PriceChange(response));
var percentChange = Math.Abs(PercentChange(response));

Title = response.Symbol + " @ " + response.RegularMarketPrice + " " + pricePrefix + priceChange + " (" + pricePrefix + percentChange + "%)";
}

private double PriceChange(YahooFinanceResponse response)
{
return Math.Round(response.RegularMarketPrice - response.PreviousClose, 2);
}

private double PercentChange(YahooFinanceResponse response)
{
return Math.Round(Math.Abs(response.PreviousClose - response.RegularMarketPrice) /
((response.PreviousClose + response.RegularMarketPrice) / 2) * 100f, 2);
}

public override EFontAwesomeIcon FontAwesomeIcon => EFontAwesomeIcon.Solid_ChartLine;
}
}
11 changes: 11 additions & 0 deletions Pinpoint.Plugin.Finance/Pinpoint.Plugin.Finance.csproj
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
<Project Sdk="Microsoft.NET.Sdk">

<PropertyGroup>
<TargetFramework>netcoreapp3.1</TargetFramework>
</PropertyGroup>

<ItemGroup>
<ProjectReference Include="..\Pinpoint.Core\Pinpoint.Core.csproj" />
</ItemGroup>

</Project>
60 changes: 60 additions & 0 deletions Pinpoint.Plugin.Finance/YahooFinanceApi.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
using System;
using System.Linq;
using System.Net.Http;
using System.Threading.Tasks;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;

namespace Pinpoint.Plugin.Finance
{
public class YahooFinanceApi
{
private const string Url = "https://query1.finance.yahoo.com/v8/finance/chart/{0}?interval=2m&useYfid=true&range=1d";

public YahooFinanceApi(TimeSpan cacheTimeout)
{
Repository = new YahooFinanceRepository(cacheTimeout);
}

public YahooFinanceRepository Repository { get; }

public async Task<YahooFinanceResponse> Lookup(string ticker)
{
// Check if ticker is cached
if (Repository.TryGet(ticker, out var response))
{
return response;
}

var httpResponse = string.Empty;
try
{
using var httpClient = new HttpClient();
httpResponse = await httpClient.GetStringAsync(string.Format(Url, ticker));
}
catch (HttpRequestException)
{
return null;
}

var result = JObject.Parse(httpResponse)["chart"]["result"].ToArray();
if (result.Length == 0)
{
return null;
}

var meta = result[0]["meta"].ToString();
response = JsonConvert.DeserializeObject<YahooFinanceResponse>(meta);

// Don't consider non-equity instrument types
if (!response.InstrumentType.Equals("EQUITY"))
{
return null;
}

// Cache ticker response
Repository.Put(ticker, response);
return response;
}
}
}
44 changes: 44 additions & 0 deletions Pinpoint.Plugin.Finance/YahooFinanceRepository.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
using System;
using System.Collections.Generic;

namespace Pinpoint.Plugin.Finance
{
public class YahooFinanceRepository
{
public YahooFinanceRepository(TimeSpan cacheTimeout)
{
CacheTimeout = cacheTimeout;
}

public TimeSpan CacheTimeout { get; set; }

public Dictionary<string, YahooFinanceResponse> ResponseCache = new Dictionary<string, YahooFinanceResponse>();

public Dictionary<string, long> UpdateTimeCache = new Dictionary<string, long>();

public bool TryGet(string ticker, out YahooFinanceResponse response)
{
if (!ResponseCache.ContainsKey(ticker))
{
response = null;
return false;
}

var now = DateTimeOffset.Now.ToUnixTimeMilliseconds();
if (now - UpdateTimeCache[ticker] >= CacheTimeout.Milliseconds)
{
response = null;
return false;
}

response = ResponseCache[ticker];
return true;
}

public void Put(string ticker, YahooFinanceResponse response)
{
ResponseCache[ticker] = response;
UpdateTimeCache[ticker] = DateTimeOffset.Now.ToUnixTimeMilliseconds();
}
}
}
27 changes: 27 additions & 0 deletions Pinpoint.Plugin.Finance/YahooFinanceResponse.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
using Newtonsoft.Json;

namespace Pinpoint.Plugin.Finance
{
public class YahooFinanceResponse
{
[JsonProperty("currency")]
public string Currency { get; set; }

[JsonProperty("symbol")]
public string Symbol { get; set; }

[JsonProperty("exchangeName")]
public string ExchangeName { get; set; }

[JsonProperty("instrumentType")]
public string InstrumentType { get; set; }

[JsonProperty("regularMarketPrice")]
public float RegularMarketPrice { get; set; }

[JsonProperty("previousClose")]
public float PreviousClose { get; set; }

public string Url => "https://finance.yahoo.com/quote/" + Symbol;
}
}
1 change: 1 addition & 0 deletions Pinpoint.Win/Pinpoint.Win.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@
<ProjectReference Include="..\Pinpoint.Plugin.Currency\Pinpoint.Plugin.Currency.csproj" />
<ProjectReference Include="..\Pinpoint.Plugin.Dictionary\Pinpoint.Plugin.Dictionary.csproj" />
<ProjectReference Include="..\Pinpoint.Plugin.Everything\Pinpoint.Plugin.Everything.csproj" />
<ProjectReference Include="..\Pinpoint.Plugin.Finance\Pinpoint.Plugin.Finance.csproj" />
<ProjectReference Include="..\Pinpoint.Plugin.MetricConverter\Pinpoint.Plugin.MetricConverter.csproj" />
<ProjectReference Include="..\Pinpoint.Plugin.Snippets\Pinpoint.Plugin.Snippets.csproj" />
<ProjectReference Include="..\Pinpoint.Plugin.Calculator\Pinpoint.Plugin.Calculator.csproj" />
Expand Down
7 changes: 7 additions & 0 deletions Pinpoint.Win/Views/MainWindow.xaml.cs
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
using Pinpoint.Plugin.CommandLine;
using Pinpoint.Plugin.ControlPanel;
using Pinpoint.Plugin.Dictionary;
using Pinpoint.Plugin.Finance;
using Pinpoint.Win.Models;
using PinPoint.Plugin.Spotify;

Expand Down Expand Up @@ -63,6 +64,7 @@ private void LoadPlugins()
_pluginEngine.AddPlugin(new CommandLinePlugin());
_pluginEngine.AddPlugin(new SnippetsPlugin(_settingsWindow));
_pluginEngine.AddPlugin(new SpotifyPlugin());
_pluginEngine.AddPlugin(new FinancePlugin());
}

internal MainWindowModel Model
Expand Down Expand Up @@ -280,6 +282,11 @@ private void StopSearching()

private void OpenSelectedResult()
{
if (LstResults.SelectedItems.Count == 0)
{
return;
}

StopSearching();

var selection = LstResults.SelectedItems[0];
Expand Down
14 changes: 14 additions & 0 deletions Pinpoint.sln
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,8 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Pinpoint.Plugin.ControlPane
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Pinpoint.Plugin.Spotify", "Pinpoint.Plugin.Spotify\Pinpoint.Plugin.Spotify.csproj", "{19E48BA9-ABCA-4104-97E7-B6B421F210A6}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Pinpoint.Plugin.Finance", "Pinpoint.Plugin.Finance\Pinpoint.Plugin.Finance.csproj", "{5FD2B98D-A8E1-4F42-A7C4-815CA4CCA4F8}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Expand Down Expand Up @@ -194,6 +196,18 @@ Global
{19E48BA9-ABCA-4104-97E7-B6B421F210A6}.Release|x64.Build.0 = Release|Any CPU
{19E48BA9-ABCA-4104-97E7-B6B421F210A6}.Release|x86.ActiveCfg = Release|Any CPU
{19E48BA9-ABCA-4104-97E7-B6B421F210A6}.Release|x86.Build.0 = Release|Any CPU
{5FD2B98D-A8E1-4F42-A7C4-815CA4CCA4F8}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{5FD2B98D-A8E1-4F42-A7C4-815CA4CCA4F8}.Debug|Any CPU.Build.0 = Debug|Any CPU
{5FD2B98D-A8E1-4F42-A7C4-815CA4CCA4F8}.Debug|x64.ActiveCfg = Debug|Any CPU
{5FD2B98D-A8E1-4F42-A7C4-815CA4CCA4F8}.Debug|x64.Build.0 = Debug|Any CPU
{5FD2B98D-A8E1-4F42-A7C4-815CA4CCA4F8}.Debug|x86.ActiveCfg = Debug|Any CPU
{5FD2B98D-A8E1-4F42-A7C4-815CA4CCA4F8}.Debug|x86.Build.0 = Debug|Any CPU
{5FD2B98D-A8E1-4F42-A7C4-815CA4CCA4F8}.Release|Any CPU.ActiveCfg = Release|Any CPU
{5FD2B98D-A8E1-4F42-A7C4-815CA4CCA4F8}.Release|Any CPU.Build.0 = Release|Any CPU
{5FD2B98D-A8E1-4F42-A7C4-815CA4CCA4F8}.Release|x64.ActiveCfg = Release|Any CPU
{5FD2B98D-A8E1-4F42-A7C4-815CA4CCA4F8}.Release|x64.Build.0 = Release|Any CPU
{5FD2B98D-A8E1-4F42-A7C4-815CA4CCA4F8}.Release|x86.ActiveCfg = Release|Any CPU
{5FD2B98D-A8E1-4F42-A7C4-815CA4CCA4F8}.Release|x86.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
Expand Down
6 changes: 6 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ Searches across all control panel items.
### Currency Converter

Converts between currencies (including cryptocurrencies) using familiar syntax: `1 USD to EUR`, `£1`, `1 BTC in AUD`.

![](https://i.imgur.com/FoOoBDE.png)

### Dictionary Definitions
Expand All @@ -44,6 +45,11 @@ Quickly look up word definitions `smart definition`, `smart def`, `smart define`
Uses [Everything](https://www.voidtools.com/) to offer extremely fast file searches across the entire file system.
![](https://i.imgur.com/DafD0xs.png)

### Finance

Lookup real-time ticker prices through Yahoo Finance: `$TICKER`
![](https://i.imgur.com/Bj7HRPW.png)

### Metric Converter

Easily convert between all the common imperial and metric units: `1m to ft`.
Expand Down

0 comments on commit 9b62c72

Please sign in to comment.