This is Part 2 of a series of articles to build a finance app, calling Yahoo Finance API at the backend and enhancing the frontend with Angular Material.
Introduction
In Part 1, we created an Angular 13 frontend and .NET 6 backend from Visual Studio 2022. This article continues to build this finance app, calling Yahoo Finance API at the backend and enhance the frontend with Angular Material.
Yahoo Finance API
The Yahoo Finance API is a range of libraries/APIs/methods to obtain historical and real time data for a variety of financial markets and products, as shown on Yahoo Finance.
Some of the offerings include market data on Cryptocurrencies, regular currencies, stocks and bonds, fundamental and options data, and market analysis and news. One good reason to use Yahoo Finance API is because it can be completely free. Also, it’s simple and easy to use.
Before you start using this, you need to sign in to Yahoo finance to get your own API key.
Global Market API
Let’s back to GlobalMarketAPI
project. Delete WeatherForecastController.cs and WeatherForecast.cs, which are created by default. We'll build Finance Controller with Yahoo Finance API.
Look Yahoo Finance API specification, we use http Get /v6/finance/quote to get real time quote data for stocks, crypto coins, futures, etc.
Finance Controller
Right click “Controllers” folder to add a new controller.
Select “API Controller – Empty” to add an empty controller.
Name it as FinanceController.cs
, an empty API controller is created with the below code:
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
namespace GlobalMarketAPI.Controllers
{
[Route("api/[controller]")]
[ApiController]
public class FinanceController : ControllerBase
{
}
}
Quote Response Model
From Yahoo Finance API specification, we get the response JSON of Get Quote API.
Per the specification, we add Quote
class first, which has ShortName
, FullExchangeName
, QuoteType
, RegularMarketPrice
, RegularMarketDayHigh
, RegularMarketDayLow
, Bid
, Ask
.
namespace GlobalMarketAPI.Models
{
public class Quote
{
public string? ShortName { get; set; }
public string? FullExchangeName { get; set; }
public string? QuoteType { get; set; }
public decimal RegularMarketPrice { get; set; }
public decimal RegularMarketDayHigh { get; set; }
public decimal RegularMarketDayLow { get; set; }
public decimal Bid { get; set; }
public decimal Ask { get; set; }
}
}
Then add QuoteResult
class which has a list of Quote
and error string
.
namespace GlobalMarketAPI.Models
{
public class QuoteResult
{
public List<Quote>? Result { get; set; }
public string? Error { get; set; }
}
}
Finally YahooQuoteResponse
class which has QuoteResult
.
namespace GlobalMarketAPI.Models
{
public class YahooQuoteResponse
{
public QuoteResult? QuoteResponse { get; set; }
}
}
Yahoo Finance Settings
When we call Yahoo Finance API, we need API URL and API key. We put these in application settings. Add Yahoo Finance settings in appsettings.json.
{
"Logging": {
"LogLevel": {
"Default": "Information",
"Microsoft.AspNetCore": "Warning"
}
},
"AllowedHosts": "*",
"YahooFinanceSettings": {
"APIKey": "****************",
"BaseURL": "https://yfapi.net"
}
Base URL is constant string, https://yfapi.net. Replace API key with your own API key.
Create YahooFinanceSettings
class.
namespace GlobalMarketAPI.Settings
{
public class YahooFinanceSettings
{
public string? APIKey { get; set; }
public string? BaseURL { get; set; }
}
}
Inject Yahoo finance settings in Program.cs.
using GlobalMarketAPI.Settings;
var builder = WebApplication.CreateBuilder(args);
ConfigurationManager configuration = builder.Configuration;
builder.Services.AddTransient(p =>
{
YahooFinanceSettings settings = configuration.GetSection
(nameof(YahooFinanceSettings)).Get<YahooFinanceSettings>();
return settings;
});
Finance Service
Now create Yahoo Finance Service. The quote URL is /v6/finance/quote.
We can write "get quote" function like this:
var url = $"v6/finance/quote?symbols={symbol}";
var httpClient = new HttpClient();
httpClient.DefaultRequestHeaders.Add("X-API-KEY", new[] { settings.APIKey });
httpClient.BaseAddress = new Uri(settings.BaseURL ?? "");
var data = await httpClient.GetStringAsync(url);
Add Finance Service Interface:
using GlobalMarketAPI.Models;
namespace GlobalMarketAPI.Services
{
public interface IFinanceService
{
Task<Quote> GetQuote(string symbol);
}
}
Add Finance Service Class:
using GlobalMarketAPI.Models;
using Newtonsoft.Json;
using GlobalMarketAPI.Settings;
namespace GlobalMarketAPI.Services
{
public class FinanceService : IFinanceService
{
private readonly HttpClient _httpClient;
const string QuoteURL = "v6/finance/quote";
public FinanceService(YahooFinanceSettings settings)
{
_httpClient = new HttpClient();
_httpClient.DefaultRequestHeaders.Add
("X-API-KEY", new[] { settings.APIKey });
_httpClient.BaseAddress = new Uri(settings.BaseURL ?? "");
}
public async Task<Quote> GetQuote(string symbol)
{
var url = QuoteURL + $"?symbols={symbol}";
try
{
var data = await _httpClient.GetStringAsync(url);
var result = JsonConvert.DeserializeObject<YahooQuoteResponse>(data);
return result?.QuoteResponse?.Result?.FirstOrDefault() ?? new Quote();
}
catch (Exception)
{
throw;
}
}
}
}
Inject FinanceService
instance in Program.cs:
builder.Services.AddTransient<IFinanceService, FinanceService>();
Http Get Quote Endpoint
Now we can write HttpGet Quote
endpoint in Finance controller.
[HttpGet]
[Route("quote")]
public async Task<Quote> GetQuote([FromQuery] string symbol)
{
return await _financeService.GetQuote(symbol);
}
The route template for each controller method is the prefix plus the string
specified in the Route
attribute. For the GetQuote
method, the route
template includes string "quote
", and the URL contains string Symbol
as query string parameter.
Now click “Start” button to run the solution. Both GlobalMarket
and GlobalMarketAPI
are started.
You should be able to see GlobalMarketAPI
console window.
Then we can check Swagger UI to verify the API endpoint.
http://localhost:5219/swagger
Global Market Frontend
What we try to do?
Trading widget is working very well in our frontend. Now we just add a little bit fun. We want to have a symbol dropdown list. When we select a symbol from the dropdown list, call the backend API to get the real time quote of this symbol, in the meantime, re-load trading widget for this symbol.
Integrate Backend API Project
Change reverse proxy configure to connect backend finance controller.
Update proxy.config.js under GlobalMarket/projects/trading.
const PROXY_CONFIG = [
{
context: [
"/api",
],
target: "https://localhost:7219",
secure: false
}
]
module.exports = PROXY_CONFIG;
Although we can add multiple URLs in context, if you don’t want to change this proxy config file with every controller, can add the parent URL, such as “/api”.
Now we start to use Angular Material styling frontend.
Angular Material
The Angular team builds and maintains both common UI components and tools to help you build your own custom components. Angular Material is Material Design UI components for Angular applications.
Right click GlobalMarket
project in Visual Studio 2022 Solution Explorer, then click “Open in Terminal”.
Running the following command to install Angular Material.
ng add @angular/material
- Choose a prebuilt theme name, or "custom" for a custom theme.
- Set up global Angular Material typography styles.
- Set up browser animations for Angular Material.
Of course, we need bootstrap as well, the most popular CSS library.
npm install bootstrap --save
After install, we Import bootstrap and Angular Material CSS style in GlobalMarkt/projects/trading/src/styles.css.
@import "~bootstrap/dist/css/bootstrap.css";
@import '~@angular/material/prebuilt-themes/deeppurple-amber.css';
Angular Material provides high quality components. You can check it out at this link. Before using these components, you need to import component module. The below are components used in our frontend.
mat-card
<mat-card>
is a content container for text, photos, and actions in the context of a single subject.
mat-form-field
<mat-form-field>
is a component used to wrap several Angular Material components and apply common Text field styles such as the underline, floating label, and hint messages.
mat-select
<mat-select>
is a form control for selecting a value from a set of options, similar to the native <select>
element.
mat-chip-list
<mat-chip-list>
displays a list of values as individual, keyboard accessible, chips.
mat-progress-bar
<mat-progress-bar>
is a horizontal progress-bar for indicating progress and activity.
mat-divider
<mat-divider>
is a component that allows for Material styling of a line separator with various orientation options.
Import these modules in app.module.ts.
import { HttpClientModule } from '@angular/common/http';
import { NgModule } from '@angular/core';
import { BrowserModule } from '@angular/platform-browser';
import { AppComponent } from './app.component';
import { TradingviewWidgetModule } from 'tradingview-widget';
import { BrowserAnimationsModule } from '@angular/platform-browser/animations';
import { ClipboardModule } from '@angular/cdk/clipboard';
import { DragDropModule } from '@angular/cdk/drag-drop';
import { FormsModule, ReactiveFormsModule } from '@angular/forms';
import { MatCardModule } from '@angular/material/card';
import { MatChipsModule } from '@angular/material/chips'
import { MatDividerModule } from '@angular/material/divider'
import { MatFormFieldModule } from '@angular/material/form-field'
import { MatSelectModule } from '@angular/material/select'
import { MatProgressBarModule } from '@angular/material/progress-bar'
@NgModule({
declarations: [
AppComponent
],
imports: [
BrowserModule,
HttpClientModule,
TradingviewWidgetModule,
BrowserAnimationsModule,
FormsModule,
ReactiveFormsModule,
ClipboardModule,
DragDropModule,
MatCardModule,
MatChipsModule,
MatDividerModule,
MatFormFieldModule,
MatSelectModule,
MatProgressBarModule
],
providers: [],
bootstrap: [AppComponent]
})
export class AppModule { }
We want to use mat-form-field
and mat-select
, so need import FomrsModule
and ReactiveFormsModule
.
Now use these components to change app.component.html.
The whole page is divided two Cards. The top one is a Symbol card, and the other one is displaying trading widget.
<mat-card>
<mat-card-subtitle style="margin-top:-10px">Symbol Quote</mat-card-subtitle>
</mat-card>
<mat-divider></mat-divider>
<mat-card>
<mat-card-subtitle style="margin-top:-10px">Live Chart</mat-card-subtitle>
<tradingview-widget [widgetConfig]="widgetConfig"></tradingview-widget>
</mat-card>
Display Symbol Dropdown List
Open the AppComponent
class file and define symbols array.
symbols = ['MSFT',
'AAPL',
'AMZN',
'TSLA',
'WTC',
'BTCUSD',
'ETHUSD',
'CLN2022'
];
Open AppComponent
template file to add <mat-select>
. Using ngFor
to display list, and binding the selected symbol value to widgetConfig.symbol
.
<mat-form-field appearance="fill">
<mat-select id="symbol" class="symbol" [ngModel]="widgetConfig.symbol"
(ngModelChange)="onSymbolChange($event)" required>
<mat-option *ngFor="let symbol of symbols" [value]="symbol">{{symbol}}</mat-option>
</mat-select>
</mat-form-field>
We want to reload trading view widget when selected symbol changed. Open AppComponent
class file to add OnSymbolChange
event.
onSymbolChange(event: any) {
this.widgetConfig = {
symbol: event,
widgetType: 'widget',
allow_symbol_change: true,
height: 560,
width: 980,
hideideas: true,
hide_legend: false,
hide_side_toolbar: true,
hide_top_toolbar: false,
theme: Themes.LIGHT,
};
this.ngOnInit();
}
In Angular, calling ngOnInit
refreshes the component.
Display Getting Quote Result
Add a new app.model
class (app.model.ts), then define Quote
interface, and DataItem
interface.
export interface Quote {
shortName: string;
fullExchangeName: string;
quoteType: string;
regularMarketPrice: number;
regularMarketDayHigh: number;
regularMarketDayLow: number;
bid: number;
ask: number;
}
export interface DataItem {
name: string;
value: number;
}
Open AppComponent
class file to call backend get quote API.
this.http.get<Quote>(`/finance/quote?symbol=
${this.widgetConfig.symbol}`).subscribe(result => {
this.quote = result;
this.data = [
{ name: "Price", value: this.quote.regularMarketPrice },
{ name: "Day High", value: this.quote.regularMarketDayHigh },
{ name: "Day Low", value: this.quote.regularMarketDayLow },
{ name: "Ask", value: this.quote.ask },
{ name: "Bid", value: this.quote.bid },
];
}, error => console.error(error));
Open AppComponent template file to add <mat-chip-list>
to display get quote result.
<mat-chip-list class="symbol-price-chip"
cdkDropList
cdkDropListOrientation="horizontal"
(cdkDropListDropped)="drop($event)">
<mat-chip class="symbol-price-box"
cdkDrag
*ngFor="let item of data">
{{item.name}}: {{item.value}}
</mat-chip>
</mat-chip-list>
<mat-chip>
elements can change order with drag-drop. Add drop
event in AppComponent
class:
drop(event: CdkDragDrop<DataItem[]>) {
moveItemInArray(this.data, event.previousIndex, event.currentIndex);
}
Display Progress Bar
Open AppComponent template file to add <mat-progress-bar>
.
<mat-progress-bar color="warn" mode="indeterminate" *ngIf="isProgressing">
</mat-progress-bar>
Open AppComponent
class file to set isProgressing
. Before calling API, set isProgessing
to true
, then set isProgressing
to false
in subscription callback.
this.isProgressing = true;
this.http.get<Quote>(`/finance/quote?symbol=
${this.widgetConfig.symbol}`).subscribe(result => {
...
this.isProgressing = false;
}, error => console.error(error));
Global Market New Look
Now our app has a new look after backend and frontend changes. Press F5 or click “Start” button at the top menu bar. As we configured earlier, both GlobalMarketAPI
(backend) and GlobalMarket
(frontend) are started.
You can drag drop “Price” “Day High” “Day Low” “Ask” “Bid” to change to whatever order you want.
Change symbol to AAPL – Apple in symbol dropdown list.
Then you can see Apple company trading chart and quote information.
As you know, crypto coins crashed recently. So I really want to see Bitcoin trading chart. Change symbol to “BTCUSD” (bitcoin) in dropdown list.
Ouch, that’s a tragedy.
But wait, why there is no quote information? Let’s debug.
Debug Angular Frontend and .NET Backend
In frontend, we put breakpoints at calling get quote API and subscription callback.
In backend, we put a breakpoint at GetQuote
function of finance controller.
Now change symbol to BTCUSD
.
The first breakpoint is triggered. You can see that we’re passing symbol “BTCUSD
” correctly.
Press F5 or click “Continue” at menu bar to let it go.
The breakpoint at GetQuote
of finance controller is triggered. You also can see symbol “BTCISD
” passed correctly.
Let’s press F11 step into.
The Yahoo Finance API URL is correct as well. Let’s put breakpoint at exception catch.
Press F10 to step over.
There is no exception, but result appears nothing. That means Yahoo Finance cannot find anything about this symbol. Why? That because Yahoo Finance symbols are not all the same as trading view symbols. For example, Yahoo Finance symbol for “BTCUSD
” is “BTC-USD
”, and “ETHUSD
” is “ETH-USD
”, etc.
To resolve this issue, we need to add a mapping between trading view symbols and Yahoo Finance symbols.
We can add this mapping to GlobalMarketAPI
app settings.
"YahooFinanceSettings": {
"APIKey": "******",
"BaseURL": "https://yfapi.net",
"SymbolMapInfo": {
"WTC": "WTC.AX",
"BTCUSD": "BTC-USD",
"ETHUSD": "ETH-USD",
"CLN2022": "CLN22.NYM"
}
}
WTC
– Wise Tech Global BTCUSD
– Bit Coin ETHUSD
– Ethereum CLN2022
– Crude Oil July 2022
Now add dictionary to YahooFinaceSettings.cs in GlobalMarketAPI
. It will be loaded at program start.
namespace GlobalMarketAPI.Settings
{
public class YahooFinanceSettings
{
public string? APIKey { get; set; }
public string? BaseURL { get; set; }
public Dictionary<string, string>? SymbolMapInfo
{
get;
set;
}
}
}
Open FinanceService
class, add symbol map dictionary.
private readonly Dictionary<string, string> _symbolMap = new Dictionary<string, string>();
Set this dictionary in constructor.
if (settings.SymbolMapInfo != null && settings.SymbolMapInfo.Count > 0)
{
_symbolMap = settings.SymbolMapInfo.ToDictionary(x => x.Key, x => x.Value);
}
Replace the symbol if it has a mapping one.
symbol = _symbolMap.ContainsKey(symbol) ? _symbolMap[symbol] : symbol;
Now debug again.
Symbol
is the mapped one, “BTC-USD
”.
Yahoo Finance API returns result.
Press “F5” continue. Breakpoint at Angular http client call back function is triggered.
Press “F5” to continue. Finally, get bit coin quote information.
How to Use the Source Code
Download and extract source code, open GlobalMaket.sln with Visual Studio 2022.
Then, right click GlobalMarket
project in Solution Explorer, select “Open in Terminal”.
Need to build tradingview-widget
library first.
npm install
ng build tradingview-widget
Conclusion
Continuing from Part 1, in this article, we integrated Angular frontend with ASP.NET Core Web API and used Angular Material to style the frontend. At last, we showed you how easy it is to debug the frontend and backend together in Visual Studio 2022. You can get all source code from github. Enjoy coding!
History
- 16th May, 2022: Initial version