This commit is contained in:
Marin Bratanov 2020-01-18 16:55:00 +02:00
Коммит eab782f24a
69 изменённых файлов: 4320 добавлений и 0 удалений

350
.gitignore поставляемый Normal file
Просмотреть файл

@ -0,0 +1,350 @@
## Ignore Visual Studio temporary files, build results, and
## files generated by popular Visual Studio add-ons.
##
## Get latest from https://github.com/github/gitignore/blob/master/VisualStudio.gitignore
# User-specific files
*.rsuser
*.suo
*.user
*.userosscache
*.sln.docstates
# User-specific files (MonoDevelop/Xamarin Studio)
*.userprefs
# Mono auto generated files
mono_crash.*
# Build results
[Dd]ebug/
[Dd]ebugPublic/
[Rr]elease/
[Rr]eleases/
x64/
x86/
[Aa][Rr][Mm]/
[Aa][Rr][Mm]64/
bld/
[Bb]in/
[Oo]bj/
[Ll]og/
[Ll]ogs/
# Visual Studio 2015/2017 cache/options directory
.vs/
# Uncomment if you have tasks that create the project's static files in wwwroot
#wwwroot/
# Visual Studio 2017 auto generated files
Generated\ Files/
# MSTest test Results
[Tt]est[Rr]esult*/
[Bb]uild[Ll]og.*
# NUnit
*.VisualState.xml
TestResult.xml
nunit-*.xml
# Build Results of an ATL Project
[Dd]ebugPS/
[Rr]eleasePS/
dlldata.c
# Benchmark Results
BenchmarkDotNet.Artifacts/
# .NET Core
project.lock.json
project.fragment.lock.json
artifacts/
# StyleCop
StyleCopReport.xml
# Files built by Visual Studio
*_i.c
*_p.c
*_h.h
*.ilk
*.meta
*.obj
*.iobj
*.pch
*.pdb
*.ipdb
*.pgc
*.pgd
*.rsp
*.sbr
*.tlb
*.tli
*.tlh
*.tmp
*.tmp_proj
*_wpftmp.csproj
*.log
*.vspscc
*.vssscc
.builds
*.pidb
*.svclog
*.scc
# Chutzpah Test files
_Chutzpah*
# Visual C++ cache files
ipch/
*.aps
*.ncb
*.opendb
*.opensdf
*.sdf
*.cachefile
*.VC.db
*.VC.VC.opendb
# Visual Studio profiler
*.psess
*.vsp
*.vspx
*.sap
# Visual Studio Trace Files
*.e2e
# TFS 2012 Local Workspace
$tf/
# Guidance Automation Toolkit
*.gpState
# ReSharper is a .NET coding add-in
_ReSharper*/
*.[Rr]e[Ss]harper
*.DotSettings.user
# TeamCity is a build add-in
_TeamCity*
# DotCover is a Code Coverage Tool
*.dotCover
# AxoCover is a Code Coverage Tool
.axoCover/*
!.axoCover/settings.json
# Visual Studio code coverage results
*.coverage
*.coveragexml
# NCrunch
_NCrunch_*
.*crunch*.local.xml
nCrunchTemp_*
# MightyMoose
*.mm.*
AutoTest.Net/
# Web workbench (sass)
.sass-cache/
# Installshield output folder
[Ee]xpress/
# DocProject is a documentation generator add-in
DocProject/buildhelp/
DocProject/Help/*.HxT
DocProject/Help/*.HxC
DocProject/Help/*.hhc
DocProject/Help/*.hhk
DocProject/Help/*.hhp
DocProject/Help/Html2
DocProject/Help/html
# Click-Once directory
publish/
# Publish Web Output
*.[Pp]ublish.xml
*.azurePubxml
# Note: Comment the next line if you want to checkin your web deploy settings,
# but database connection strings (with potential passwords) will be unencrypted
*.pubxml
*.publishproj
# Microsoft Azure Web App publish settings. Comment the next line if you want to
# checkin your Azure Web App publish settings, but sensitive information contained
# in these scripts will be unencrypted
PublishScripts/
# NuGet Packages
*.nupkg
# NuGet Symbol Packages
*.snupkg
# The packages folder can be ignored because of Package Restore
**/[Pp]ackages/*
# except build/, which is used as an MSBuild target.
!**/[Pp]ackages/build/
# Uncomment if necessary however generally it will be regenerated when needed
#!**/[Pp]ackages/repositories.config
# NuGet v3's project.json files produces more ignorable files
*.nuget.props
*.nuget.targets
# Microsoft Azure Build Output
csx/
*.build.csdef
# Microsoft Azure Emulator
ecf/
rcf/
# Windows Store app package directories and files
AppPackages/
BundleArtifacts/
Package.StoreAssociation.xml
_pkginfo.txt
*.appx
*.appxbundle
*.appxupload
# Visual Studio cache files
# files ending in .cache can be ignored
*.[Cc]ache
# but keep track of directories ending in .cache
!?*.[Cc]ache/
# Others
ClientBin/
~$*
*~
*.dbmdl
*.dbproj.schemaview
*.jfm
*.pfx
*.publishsettings
orleans.codegen.cs
# Including strong name files can present a security risk
# (https://github.com/github/gitignore/pull/2483#issue-259490424)
#*.snk
# Since there are multiple workflows, uncomment next line to ignore bower_components
# (https://github.com/github/gitignore/pull/1529#issuecomment-104372622)
#bower_components/
# RIA/Silverlight projects
Generated_Code/
# Backup & report files from converting an old project file
# to a newer Visual Studio version. Backup files are not needed,
# because we have git ;-)
_UpgradeReport_Files/
Backup*/
UpgradeLog*.XML
UpgradeLog*.htm
ServiceFabricBackup/
*.rptproj.bak
# SQL Server files
*.mdf
*.ldf
*.ndf
# Business Intelligence projects
*.rdl.data
*.bim.layout
*.bim_*.settings
*.rptproj.rsuser
*- [Bb]ackup.rdl
*- [Bb]ackup ([0-9]).rdl
*- [Bb]ackup ([0-9][0-9]).rdl
# Microsoft Fakes
FakesAssemblies/
# GhostDoc plugin setting file
*.GhostDoc.xml
# Node.js Tools for Visual Studio
.ntvs_analysis.dat
node_modules/
# Visual Studio 6 build log
*.plg
# Visual Studio 6 workspace options file
*.opt
# Visual Studio 6 auto-generated workspace file (contains which files were open etc.)
*.vbw
# Visual Studio LightSwitch build output
**/*.HTMLClient/GeneratedArtifacts
**/*.DesktopClient/GeneratedArtifacts
**/*.DesktopClient/ModelManifest.xml
**/*.Server/GeneratedArtifacts
**/*.Server/ModelManifest.xml
_Pvt_Extensions
# Paket dependency manager
.paket/paket.exe
paket-files/
# FAKE - F# Make
.fake/
# CodeRush personal settings
.cr/personal
# Python Tools for Visual Studio (PTVS)
__pycache__/
*.pyc
# Cake - Uncomment if you are using it
# tools/**
# !tools/packages.config
# Tabs Studio
*.tss
# Telerik's JustMock configuration file
*.jmconfig
# BizTalk build output
*.btp.cs
*.btm.cs
*.odx.cs
*.xsd.cs
# OpenCover UI analysis results
OpenCover/
# Azure Stream Analytics local run output
ASALocalRun/
# MSBuild Binary and Structured Log
*.binlog
# NVidia Nsight GPU debugger configuration file
*.nvuser
# MFractors (Xamarin productivity tool) working folder
.mfractor/
# Local History for Visual Studio
.localhistory/
# BeatPulse healthcheck temp database
healthchecksdb
# Backup folder for Package Reference Convert tool in Visual Studio 2017
MigrationBackup/
# Ionide (cross platform F# VS Code tools) working folder
.ionide/

Просмотреть файл

@ -0,0 +1,37 @@

Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio Version 16
VisualStudioVersion = 16.0.29521.150
MinimumVisualStudioVersion = 10.0.40219.1
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "BlazorFinancePortfolio.Server", "Server\BlazorFinancePortfolio.Server.csproj", "{98239239-B3F1-48CE-870F-AF18D5667417}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "BlazorFinancePortfolio.Client", "Client\BlazorFinancePortfolio.Client.csproj", "{38F39FB3-AA4B-4D48-B07B-CEC28603B931}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "BlazorFinancePortfolio.Shared", "Shared\BlazorFinancePortfolio.Shared.csproj", "{6EC3914B-B550-4BA6-A22E-12D8BC5A9624}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Release|Any CPU = Release|Any CPU
EndGlobalSection
GlobalSection(ProjectConfigurationPlatforms) = postSolution
{98239239-B3F1-48CE-870F-AF18D5667417}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{98239239-B3F1-48CE-870F-AF18D5667417}.Debug|Any CPU.Build.0 = Debug|Any CPU
{98239239-B3F1-48CE-870F-AF18D5667417}.Release|Any CPU.ActiveCfg = Release|Any CPU
{98239239-B3F1-48CE-870F-AF18D5667417}.Release|Any CPU.Build.0 = Release|Any CPU
{38F39FB3-AA4B-4D48-B07B-CEC28603B931}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{38F39FB3-AA4B-4D48-B07B-CEC28603B931}.Debug|Any CPU.Build.0 = Debug|Any CPU
{38F39FB3-AA4B-4D48-B07B-CEC28603B931}.Release|Any CPU.ActiveCfg = Release|Any CPU
{38F39FB3-AA4B-4D48-B07B-CEC28603B931}.Release|Any CPU.Build.0 = Release|Any CPU
{6EC3914B-B550-4BA6-A22E-12D8BC5A9624}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{6EC3914B-B550-4BA6-A22E-12D8BC5A9624}.Debug|Any CPU.Build.0 = Debug|Any CPU
{6EC3914B-B550-4BA6-A22E-12D8BC5A9624}.Release|Any CPU.ActiveCfg = Release|Any CPU
{6EC3914B-B550-4BA6-A22E-12D8BC5A9624}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {CE15B802-C89C-483C-96AB-8E3F2F626994}
EndGlobalSection
EndGlobal

10
Client/App.razor Normal file
Просмотреть файл

@ -0,0 +1,10 @@
<Router AppAssembly="@typeof(Program).Assembly">
<Found Context="routeData">
<RouteView RouteData="@routeData" DefaultLayout="@typeof(MainLayout)" />
</Found>
<NotFound>
<LayoutView Layout="@typeof(MainLayout)">
<p>Sorry, there's nothing at this address.</p>
</LayoutView>
</NotFound>
</Router>

Просмотреть файл

@ -0,0 +1,46 @@
<Project Sdk="Microsoft.NET.Sdk.Web">
<PropertyGroup>
<TargetFramework>netstandard2.1</TargetFramework>
<RazorLangVersion>3.0</RazorLangVersion>
<BlazorLinkOnBuild>false</BlazorLinkOnBuild>
</PropertyGroup>
<PropertyGroup>
<ManifestShortName>My Stocks</ManifestShortName>
<ManifestLongName>My Blazor Stocks PWA</ManifestLongName>
<ServiceWorkerForce>true</ServiceWorkerForce>
<ServiceWorkerCacheVersion>21</ServiceWorkerCacheVersion>
<ServiceWorkerRegisterInstallableType>installable-blazor</ServiceWorkerRegisterInstallableType>
<ServiceWorkerBlazorAssembly>BlazorFinancePortfolio.Client</ServiceWorkerBlazorAssembly>
<ServiceWorkerBlazorInstallMethod>InstallPwaPrompt</ServiceWorkerBlazorInstallMethod>
<ServiceWorkerBaseUrl>/blazor-stocks-portfolio/</ServiceWorkerBaseUrl>
<ManifestBaseUrl>/blazor-stocks-portfolio/</ManifestBaseUrl>
</PropertyGroup>
<Target Name="CopyStyles" AfterTargets="BeforeCompile">
<Copy SourceFiles="$(MSBuildProjectDirectory)\Styles\styles.min.css" DestinationFolder="$(MSBuildProjectDirectory)\wwwroot\css" />
<Message Importance="high" Text="Copying the minified builld styles to WWWROOT" />
</Target>
<ItemGroup>
<Content Include="Styles\styles.scss" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="BlazorPWA.MSBuild" Version="1.0.2">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
<PackageReference Include="BuildWebCompiler" Version="1.12.405" />
<PackageReference Include="Microsoft.AspNetCore.Blazor" Version="3.1.0-preview4.19579.2" />
<PackageReference Include="Microsoft.AspNetCore.Blazor.Build" Version="3.1.0-preview4.19579.2" PrivateAssets="all" />
<PackageReference Include="Microsoft.AspNetCore.Blazor.HttpClient" Version="3.1.0-preview4.19579.2" />
<PackageReference Include="Microsoft.AspNetCore.Blazor.DevServer" Version="3.1.0-preview4.19579.2" PrivateAssets="all" />
<PackageReference Include="Telerik.UI.for.Blazor.Trial" Version="2.6.1" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\Shared\BlazorFinancePortfolio.Shared.csproj" />
</ItemGroup>
</Project>

Просмотреть файл

@ -0,0 +1,17 @@
<div class="k-button-group">
<NavLink href=""
Match="@NavLinkMatch.All"
ActiveClass="@ActiveClass"
class="k-button k-group-start">
Result List
</NavLink>
<NavLink href="real-time"
Match="@NavLinkMatch.All"
ActiveClass="@ActiveClass"
class="k-button k-group-end">
Data Virtualization
</NavLink>
</div>
@code{
string ActiveClass = "k-state-active k-primary";
}

Просмотреть файл

@ -0,0 +1,13 @@
<TelerikDatePicker Value="@StartDate" ValueChanged="@( (DateTime d) => StartChanged(d) )"
Min="@( MinDate )"
Max="@GetHigherDate()"
Width="50%" Format="dd MMM yyyy" />
<span style="display: inline-block; width: 5px;"></span>
<TelerikDatePicker Value="@EndDate" ValueChanged="@( (DateTime d) => EndChanged(d) )"
Min="@GetLowerDate()"
Max="@( MaxDate )"
Width="50%" Format="dd MMM yyyy" />
@if (ShowErrorMessage)
{
<div class="k-bg-error">@WrongRangeMessage</div>
}

Просмотреть файл

@ -0,0 +1,86 @@
using Microsoft.AspNetCore.Components;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
namespace BlazorFinancePortfolio.Client.Components.StocksChart
{
public partial class DateRangePicker
{
[Parameter] public DateTime StartDate { get; set; } = DateTime.Now.Date;
[Parameter] public DateTime EndDate { get; set; } = DateTime.Now.Date;
[Parameter] public DateTime MinDate { get; set; } = DateTime.Now.AddMonths(-2).Date;
[Parameter] public DateTime MaxDate { get; set; } = DateTime.Now.Date;
[Parameter] public EventCallback<Tuple<DateTime, DateTime>> SelectionChanged { get; set; }
string WrongRangeMessage = "Start date must be before end date. We reset the selection, try again.";
bool ShowErrorMessage { get; set; }
protected override Task OnParametersSetAsync()
{
StartDate = GetLowerDate();
return base.OnParametersSetAsync();
}
bool IsValid()
{
if (StartDate <= EndDate)
{
return true;
}
return false;
}
async Task FlashErrorMessage()
{
ShowErrorMessage = true;
await Task.Delay(3000);
ShowErrorMessage = false;
}
async void Update()
{
if (IsValid())
{
ShowErrorMessage = false;
await SelectionChanged.InvokeAsync(new Tuple<DateTime, DateTime>(StartDate, EndDate));
}
}
async void StartChanged(DateTime userChoice)
{
if (userChoice > GetHigherDate())
{
await FlashErrorMessage();
}
else
{
StartDate = userChoice;
Update();
}
}
async void EndChanged(DateTime userChoice)
{
if (userChoice < GetLowerDate())
{
await FlashErrorMessage();
}
else
{
EndDate = userChoice;
Update();
}
}
DateTime GetLowerDate()
{
return StartDate <= EndDate ? StartDate : EndDate;
}
DateTime GetHigherDate()
{
return StartDate >= EndDate ? StartDate : EndDate;
}
}
}

Просмотреть файл

@ -0,0 +1,102 @@
@implements IDisposable
@using BlazorFinancePortfolio.Models
@using BlazorFinancePortfolio.Helpers
<TelerikChart Transitions="true" RenderAs="@RenderingMode.SVG" @ref="@ChartRef">
<ChartSeriesItems>
<ChartSeries Type="@MainChartType" Data="@ChartData"
Field="@( nameof(StockIntervalDetails.Close) )"
CategoryField="@( nameof(StockIntervalDetails.Date) )"
Style="@ChartSeriesStyle.Smooth"
Color="@( MainChartType == ChartSeriesType.Line ? "#2D73F5" : "#007BFF" )">
<ChartSeriesLine Style="@ChartSeriesLineStyle.Smooth" />
</ChartSeries>
<ChartSeries Type="@ChartSeriesType.Column" Data="@ChartData"
Field="@( nameof(StockIntervalDetails.Volume) )"
CategoryField="@( nameof(StockIntervalDetails.Date) )"
ColorField="@( nameof(StockIntervalDetails.ColumnColor) )"
Aggregate="@ChartSeriesAggregate.Sum"
Gap="0.75"
Axis="Volume"></ChartSeries>
</ChartSeriesItems>
<ChartCategoryAxes>
<ChartCategoryAxis Type="@ChartCategoryAxisType.Date"
BaseUnit="@IntervalFilter.Interval.Unit"
BaseUnitStep="@IntervalFilter.Interval.Step"
MaxDateGroups="20">
<ChartCategoryAxisLabels Format="dd/yyyy">
<ChartCategoryAxisLabels Step="@LabelStep"></ChartCategoryAxisLabels>
</ChartCategoryAxisLabels>
<ChartCategoryAxisMajorGridLines Visible="true" />
<ChartCategoryAxisMinorGridLines Visible="false" />
</ChartCategoryAxis>
</ChartCategoryAxes>
<ChartValueAxes>
<ChartValueAxis>
<ChartValueAxisLabels Format="@( SelectedCurrency.Sign + "{0}" )" />
<ChartValueAxisMinorGridLines Visible="false" />
</ChartValueAxis>
<ChartValueAxis Name="Volume" Min="0" Max="@VolumeValueAxisMax()" Visible="false"></ChartValueAxis>
</ChartValueAxes>
<ChartLegend Visible="false" />
</TelerikChart>
@code{
[Parameter] public ChartSeriesType MainChartType { get; set; }
[Parameter] public List<StockIntervalDetails> ChartData { get; set; }
[Parameter] public int LabelStep { get; set; }
[Parameter] public IntervalFilter IntervalFilter { get; set; } = new IntervalFilter() { Duration = Constants.MS_1_HOUR, Interval = new Interval { Step = 60, Unit = ChartCategoryAxisBaseUnit.Minutes } };
[CascadingParameter] public Currency SelectedCurrency { get; set; }
TelerikChart ChartRef { get; set; }
long VolumeValueAxisMax()
{
if (ChartData == null || ChartData.Count == 0)
{
return 100;
}
return (long)Math.Round(ChartData.Max(d => d.Volume) * 6);
}
protected override void OnInitialized()
{
WindowResizeDispatcher.WindowResize += ResizeChart;
base.OnInitialized();
}
protected override void OnParametersSet()
{
AdjustLbelStep();
base.OnParametersSet();
}
async Task ResizeChart()
{
if (ChartRef != null)
{
AdjustLbelStep();
ChartRef.Refresh();
}
}
void AdjustLbelStep()
{
if (ChartData.Any())
{
var chartDataTimeRangeInMs = (long)(ChartData.Last().Date - ChartData.First().Date).TotalMilliseconds;
int currStep = (int)(chartDataTimeRangeInMs / IntervalFilter.Duration / Constants.MaxLabelStepsInStocksChart);
if (WindowResizeDispatcher.WindowWidth != null && WindowResizeDispatcher.WindowWidth < 768)
{
currStep = currStep * 5;
}
LabelStep = currStep;
StateHasChanged();
}
}
public void Dispose()
{
WindowResizeDispatcher.WindowResize -= ResizeChart;
}
}

Просмотреть файл

@ -0,0 +1,71 @@
@using BlazorFinancePortfolio.Models
<div class="row py-4 d-flex justify-content-between align-items-center">
<div class="col col-4 d-flex daterange-input-wrap">
<DateRangePicker StartDate="@Start"
EndDate="@End"
MinDate="@MinDate"
MaxDate="@MaxDate"
SelectionChanged="@( (Tuple<DateTime, DateTime> d) => DatesChanged(d) )" />
</div>
<ul class="k-reset d-flex col-12 col-sm-8 col-md-6 col-lg-3 justify-content-center justify-content-sm-start">
@foreach (TimeFilter filter in TimeFilters)
{
<li class="ml-3">
<span class="list-item time-filter-item @( ActiveTimeFilterDuration == filter.Duration ? " active" : "" )"
@onclick="@( () => OnTimeFilterClick(filter.Duration) )">
@filter.Name
</span>
</li>
}
</ul>
<div class="col-12 col-sm-4 col-md-6 col-lg-4 text-center text-sm-right mt-3 mt-sm-0">
<TelerikDropDownList Data="@IntervalFilters" TextField="Name" ValueField="Duration"
TItem="IntervalFilter" TValue="long" Value="SelectedFilterInterval"
ValueChanged="@( (long v) => OnIntervalChange(v) )"
Width="140px" PopupWidth="135px" PopupHeight="auto" Class="dropdown-list-selection interval ddl-no-bg">
<ValueTemplate>
<span class="service-category"> Interval: @( (context as IntervalFilter).Name)</span>
</ValueTemplate>
</TelerikDropDownList>
<TelerikDropDownList @bind-Value="@MainChartType" Data="@AvailableChartTypes" Width="140px" PopupWidth="140px" PopupHeight="auto" Class="ddl-no-bg">
<ValueTemplate>
@{
ChartTypeList dataItem = context as ChartTypeList;
<span class="chart-category selected d-flex align-items-center">
<img src="@( $" images/{@dataItem.Text.ToLowerInvariant()}.png" )" />
@dataItem.Text
</span>
}
</ValueTemplate>
<ItemTemplate>
@{
ChartTypeList dataItem = context as ChartTypeList;
<span class="chart-category d-flex align-items-center">
<img src="@( $" images/{@dataItem.Text.ToLowerInvariant()}.png" )" />
@dataItem.Text
</span>
}
</ItemTemplate>
</TelerikDropDownList>
</div>
</div>
<div class="row">
<div class="col">
@if (CurrentChartData != null)
{
<MainChart ChartData="@CurrentChartData" IntervalFilter="@SelectedInterval" MainChartType="@MainChartType" />
}
else
{
<div class="alert alert-info">
Select a symbol from the list to see its details. If there are no symbols, add a new one.
</div>
}
</div>
</div>

Просмотреть файл

@ -0,0 +1,142 @@
using Microsoft.AspNetCore.Components;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using BlazorFinancePortfolio.Models;
using BlazorFinancePortfolio.Helpers;
using BlazorFinancePortfolio.Services;
using Telerik.Blazor;
namespace BlazorFinancePortfolio.Client.Components.StocksChart
{
public partial class TopAreaContainer
{
[Inject] internal StocksListService stocksService { get; set; }
[Parameter] public Stock SelectedStock { get; set; }
public DateTime MinDate { get; set; } = DateTime.Now.AddMonths(-2).Date;
public DateTime MaxDate { get; set; } = DateTime.Now.Date;
//parameters
List<StockIntervalDetails> CurrentChartData { get; set; }
DateTime Start { get; set; }
DateTime End { get; set; } = DateTime.Now;
public IntervalFilter SelectedInterval { get; set; }
ChartSeriesType MainChartType { get; set; }
long? ActiveTimeFilterDuration { get; set; }
long SelectedFilterInterval { get; set; }
//data sources
List<ChartTypeList> AvailableChartTypes { get; set; } = ChartTypeList.GetAvailableSeriesTypes();
List<TimeFilter> TimeFilters { get; set; } = TimeFilter.GetFilters();
public List<IntervalFilter> IntervalFilters { get; set; } = IntervalFilter.GetIntervalFilters();
IDictionary<long, long> TimeFilterDefaultIntervalsMapping { get; set; } =
new Dictionary<long, long>
{
{Constants.MS_1_HOUR, Constants.MS_5_MINUTES },
{Constants.MS_4_HOURS, Constants.MS_15_MINUTES },
{Constants.MS_12_HOURS, Constants.MS_30_MINUTES },
{Constants.MS_1_DAY, Constants.MS_30_MINUTES },
{Constants.MS_4_DAYS, Constants.MS_1_HOUR },
{Constants.MS_1_WEEK, Constants.MS_4_HOURS },
};
protected override void OnInitialized()
{
SelectedInterval = IntervalFilter.GetIntervalFilters()[3];
SelectedFilterInterval = IntervalFilter.GetIntervalFilters()[3].Duration;
ActiveTimeFilterDuration = TimeFilter.GetFilters()[3].Duration;
Start = End.AddDays(-4);
FilterIntervals(ActiveTimeFilterDuration.Value);
TimeFilters.Add(new TimeFilter() { Name = "MAX", Duration = (long)(MaxDate - MinDate).TotalMilliseconds });
base.OnInitialized();
}
protected override void OnParametersSet()
{
FilterCurrentChartData(SelectedFilterInterval);
base.OnParametersSet();
}
void DatesChanged(Tuple<DateTime, DateTime> dates)
{
Start = dates.Item1;
End = dates.Item2;
var dateRangeIntervalInMs = (long)(End - Start).TotalMilliseconds;
ActiveTimeFilterDuration = null;
FilterIntervals(dateRangeIntervalInMs);
SetDefaultInterval(dateRangeIntervalInMs);
FilterCurrentChartData(SelectedFilterInterval);
StateHasChanged();
}
void OnTimeFilterClick(long FilterDuration)
{
if (this.ActiveTimeFilterDuration == FilterDuration)
{
return;
}
ActiveTimeFilterDuration = FilterDuration;
End = MaxDate;
Start = End.AddMilliseconds(-ActiveTimeFilterDuration.Value);
FilterIntervals(FilterDuration);
SetDefaultInterval(FilterDuration);
FilterCurrentChartData(SelectedFilterInterval);
StateHasChanged();
}
void FilterCurrentChartData(long intervalInMilliseconds)
{
var intervalInMinutes = intervalInMilliseconds / (1000 * 60);
if (SelectedStock != null)
{
CurrentChartData = stocksService.GenerateStockIntervals(SelectedStock, (int)intervalInMinutes, Start, End);
}
else
{
CurrentChartData = null;
}
}
void OnIntervalChange(long IntervalDuration)
{
SelectedInterval = IntervalFilter.GetIntervalFilters().Where(i => i.Duration == IntervalDuration).FirstOrDefault();
SelectedFilterInterval = IntervalDuration;
FilterCurrentChartData(IntervalDuration);
}
void FilterIntervals(long filterDuration)
{
IntervalFilters = IntervalFilter.GetIntervalFilters().Where(i => i.Duration < filterDuration && filterDuration / i.Duration < 200).ToList();
}
void SetDefaultInterval(long filterDuration)
{
if (!IntervalFilters.Any(i => i.Duration == SelectedFilterInterval))
{
SelectedFilterInterval = TimeFilterDefaultIntervalsMapping.ContainsKey(filterDuration)
? TimeFilterDefaultIntervalsMapping[filterDuration]
: Constants.MS_1_DAY;
SelectedInterval = IntervalFilters.Where(i => i.Duration == SelectedFilterInterval).FirstOrDefault();
}
}
}
}

Просмотреть файл

@ -0,0 +1,25 @@
<TelerikButton Icon="@IconName.Delete" Class="text-danger" OnClick="@( _ => WindowVisible = true )" Enabled="@Enabled">
Remove
</TelerikButton>
<TelerikWindow @bind-Visible="@WindowVisible" Modal="true">
<WindowTitle>
<strong>Confirm deleting</strong>
</WindowTitle>
<WindowContent>
Are you sure you want to delete this?
<TelerikButton OnClick="@(() => WindowVisible = false)">Cancel</TelerikButton>
<TelerikButton OnClick="@RaiseConfirm" Primary="true">Confirm</TelerikButton>
</WindowContent>
</TelerikWindow>
@code {
[Parameter] public bool Enabled { get; set; }
[Parameter] public EventCallback OnConfirm { get; set; }
bool WindowVisible { get; set; }
async void RaiseConfirm()
{
WindowVisible = false;
await OnConfirm.InvokeAsync(null);
}
}

Просмотреть файл

@ -0,0 +1,28 @@
@using BlazorFinancePortfolio.Models
<TelerikChart Width="100%" Height="60px" Transitions="false" RenderAs="@RenderingMode.SVG">
<ChartSeriesItems>
<ChartSeries Type="ChartSeriesType.Area"
Data="@(StockData.IntraDayChart.Cast<object>())"
Color="@(StockData.DayChange >= 0 ? "#5CB85C" : "#D9534F")" />
<ChartLegend Visible="false" />
<ChartValueAxes>
<ChartValueAxis Visible="false">
<ChartValueAxisLabels Visible="false" />
<ChartValueAxisMajorGridLines Visible="false" />
<ChartValueAxisMinorGridLines Visible="false" />
</ChartValueAxis>
</ChartValueAxes>
<ChartCategoryAxes>
<ChartCategoryAxis Visible="false">
<ChartCategoryAxisLabels Visible="false" />
<ChartCategoryAxisMajorGridLines Visible="false" />
<ChartCategoryAxisMinorGridLines Visible="false" />
</ChartCategoryAxis>
</ChartCategoryAxes>
</ChartSeriesItems>
</TelerikChart>
@code {
[Parameter] public Stock StockData { get; set; }
}

Просмотреть файл

@ -0,0 +1,107 @@
@using BlazorFinancePortfolio.Models
@using BlazorFinancePortfolio.Helpers
<div class="row py-4 d-flex justify-content-between stocks-grid-buttons">
<div class="col d-flex justify-content-start px-0">
@if (UncategorizedStocks != null)
{
<TelerikDropDownList Data="@UncategorizedStocks"
Value="@SelectedValue"
ValueChanged="@((string v) =>OnAdd(v))"
TextField="Name"
ValueField="Symbol"
DefaultItem="@DefailtUncategorizedStock"
PopupWidth="270px"
Width="165px"
Class="ddl-no-bg">
</TelerikDropDownList>
}
<ConfirmButton Enabled="@( SelectedStock != null )" OnConfirm="@OnRemoveConfirm" />
</div>
<div class="col text-center mt-4 mt-md-0">
<GridSwitcher />
</div>
<div class="col d-none d-md-block"></div>
</div>
<div class="row stocks-grid">
<TelerikGrid Data="@Data" Sortable="true" SelectionMode="GridSelectionMode.Single" SelectedItems="@SelectedStocks" SelectedItemsChanged="@((IEnumerable<Stock> stocks) => OnSelect(stocks))">
<GridColumns>
<GridCheckboxColumn SelectAll="false" Width="40px" />
<GridColumn Field="Symbol" Title="Symbol" />
<GridColumn Field="Name" Title="Name" />
<GridColumn Field="Price" Title="Price">
<Template>
@{
var stock = context as Stock;
<strong>@(SelectedCurrency?.Sign + stock.Price)</strong>
}
</Template>
</GridColumn>
@if (LargerThanPhone)
{
<GridColumn Field="DayChange" Title="Day Change">
<Template>
@{
var stock = context as Stock;
<span class="@NumbersHelper.GetChangeNumberClass(stock.DayChange)">@NumbersHelper.FormatDecimal(stock.DayChange * SelectedCurrency.Multiplier)</span>
}
</Template>
</GridColumn>
@*<GridColumn Field="ChangePercentage" Title="Change Percentage">
<Template>
@{
var stock = context as Stock;
<span class="@NumbersHelper.GetChangeNumberClass(stock.ChangePercentage)">@(stock.ChangePercentage)%</span>
}
</Template>
</GridColumn>*@
<GridColumn Field="Volume" Title="Volume">
<Template>
@{
var stock = context as Stock;
<span>@NumbersHelper.FormatDecimal(stock.Volume * SelectedCurrency.Multiplier)</span>
}
</Template>
</GridColumn>
@*<GridColumn Field="VolumeAvg" Title="Avg. Volume">
<Template>
@{
var stock = context as Stock;
<span>@NumbersHelper.FormatDecimal(stock.VolumeAvg * SelectedCurrency.Multiplier)</span>
}
</Template>
</GridColumn>*@
@if (LargerThanTablet)
{
<GridColumn Field="MarketCap" Title="Market Cap">
<Template>
@{
var stock = context as Stock;
<span>@NumbersHelper.FormatDecimal(stock.MarketCap * SelectedCurrency.Multiplier)</span>
}
</Template>
</GridColumn>
<GridColumn Field="PricePerEarningRatio" Title="PE Ratio">
<Template>
@{
var stock = context as Stock;
<span>
@(stock.PricePerEarningRatio != null
? NumbersHelper.FormatDecimal(stock.PricePerEarningRatio.Value * SelectedCurrency.Multiplier)
: null)
</span>
}
</Template>
</GridColumn>
<GridColumn Title="Last Day Chart">
<Template>
<SparklineChart StockData="@( context as Stock )" />
</Template>
</GridColumn>
}
}
</GridColumns>
</TelerikGrid>
</div>

Просмотреть файл

@ -0,0 +1,123 @@
using Microsoft.AspNetCore.Components;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using BlazorFinancePortfolio.Models;
using BlazorFinancePortfolio.Helpers;
using BlazorFinancePortfolio.Services;
using Microsoft.JSInterop;
namespace BlazorFinancePortfolio.Client.Components.StocksGrid
{
public partial class StocksGrid : IDisposable
{
[Inject] StocksListService StocksListService { get; set; }
[Inject] IJSRuntime JSRuntime { get; set; }
[Parameter] public Stock SelectedStock { get; set; }
IEnumerable<Stock> SelectedStocks
{
get
{
if (SelectedStock != null)
{
return new List<Stock>() { SelectedStock };
}
return Enumerable.Empty<Stock>();
}
}
[Parameter] public EventCallback<Stock> SelectedStockChanged { get; set; }
[Parameter] public List<Stock> Data { get; set; }
[Parameter] public List<Stock> UncategorizedStocks { get; set; }
[CascadingParameter] public Currency SelectedCurrency { get; set; }
string SelectedValue { get; set; }
Stock DefailtUncategorizedStock { get; set; } = new Stock() { Name = "+ Add new stock", Symbol = null };
bool LargerThanPhone { get; set; }
bool LargerThanTablet { get; set; }
async Task OnSelect(IEnumerable<Stock> selectedStocks)
{
SelectedStock = selectedStocks.FirstOrDefault();
await SelectedStockChanged.InvokeAsync(SelectedStock);
}
async Task OnAdd(string symbol)
{
if (!string.IsNullOrWhiteSpace(symbol))
{
var stock = UncategorizedStocks.FirstOrDefault(s => s.Symbol == symbol);
if (stock != null && Data.All(s => s.Symbol != stock.Symbol))
{
Stock addedStock = await StocksListService.AddStock(stock);
Data = await StocksListService.GetStocks(true);
UncategorizedStocks.Remove(stock);
var newUncategorizedStocks = new List<Stock>(UncategorizedStocks);
UncategorizedStocks = newUncategorizedStocks;
SelectedValue = null;
}
}
}
async Task OnRemoveConfirm()
{
if (SelectedStock == null)
{
return;
}
var stockForRemove = Data.FirstOrDefault(c => c.Symbol == SelectedStock.Symbol);
await StocksListService.RemoveStock(stockForRemove);
Data = await StocksListService.GetStocks(true);
UncategorizedStocks = await StocksListService.GetStocks(false);
SelectedStock = Data.FirstOrDefault();
await SelectedStockChanged.InvokeAsync(SelectedStock);
}
protected override async Task OnInitializedAsync()
{
await ToggleColumns();
Data = await StocksListService.GetStocks(true);
UncategorizedStocks = await StocksListService.GetStocks(false);
SelectedStock = Data.FirstOrDefault();
await SelectedStockChanged.InvokeAsync(SelectedStock);
await base.OnInitializedAsync();
}
protected override async Task OnAfterRenderAsync(bool firstRender)
{
if (firstRender)
{
WindowResizeDispatcher.WindowResize += ToggleColumns;
}
await base.OnAfterRenderAsync(firstRender);
}
async Task ToggleColumns()
{
if (WindowResizeDispatcher.WindowWidth == null)
{
WindowResizeDispatcher.WindowWidth = await JSRuntime.InvokeAsync<int>("getWindowWidth");
}
LargerThanPhone = WindowResizeDispatcher.WindowWidth > 768;
LargerThanTablet = WindowResizeDispatcher.WindowWidth > 992;
StateHasChanged();
}
public void Dispose()
{
WindowResizeDispatcher.WindowResize -= ToggleColumns;
}
}
}

58
Client/Pages/Index.razor Normal file
Просмотреть файл

@ -0,0 +1,58 @@
@page "/"
@using BlazorFinancePortfolio.Models
@using BlazorFinancePortfolio.Services
@using BlazorFinancePortfolio.Client.Components.StocksChart
@using BlazorFinancePortfolio.Client.Components.StocksGrid
@inject StocksListService StocksListService
<div class="container">
@if (ShowChart)
{
<TopAreaContainer SelectedStock="@SelectedStock" />
}
<div class="text-primary text-right mt-2">
<span class="collapse-expand-button" @onclick="@( () => ShowChart = !ShowChart )">
@( ShowChart ? "Hide Chart" : "Show Chart" )
<TelerikIcon Icon="@( ShowChart ? IconName.ArrowChevronUp : IconName.ArrowChevronDown )" />
</span>
</div>
</div>
<div class="d-flex justify-content-center mx-0">
<div class="row">
@if (SelectedStock != null)
{
<div class="selected-stock-badge d-flex align-items-center badge-@( SelectedStock.ChangePercentage > 0 ? "positive" : "negative" )-value">
<span>@SelectedStock.Symbol&nbsp;</span><span>@SelectedStock.Price</span>
</div>
}
</div>
</div>
<div class="container">
<StocksGrid @bind-SelectedStock="@SelectedStock"></StocksGrid>
</div>
@code {
Stock SelectedStock { get; set; }
bool ShowChart { get; set; } = true;
[CascadingParameter] public bool ShowNavigation { get; set; }
protected override void OnAfterRender(bool firstRender)
{
if (firstRender)
{
ShowNavigation = true;
StateHasChanged();
}
base.OnAfterRender(firstRender);
}
}

Просмотреть файл

@ -0,0 +1,73 @@
@page "/real-time"
@using BlazorFinancePortfolio.Models
@using BlazorFinancePortfolio.Client.Components
<div class="container" style="padding-bottom: 100px;">
<div class="d-flex justify-content-center py-5 align-items-center">
<GridSwitcher />
</div>
<div class="row d-flex justify-content-center real-time-data">
<TelerikGrid Data=@GridData
ScrollMode="@GridScrollMode.Virtual"
Height="600px" RowHeight="60" PageSize="48" Width="100%">
<GridColumns>
<GridColumn Field="@nameof(RealTimeData.Symbol)" Width="80px" Title="Symbol" />
<GridColumn Field="@nameof(RealTimeData.Name)" Width="130px" Title="Name" />
<GridColumn Field="@nameof(RealTimeData.Currency)" Width="80px" Title="Currency">
<Template>
@SelectedCurrency.Symbol
</Template>
</GridColumn>
<GridColumn Field="@nameof(RealTimeData.Price)" Width="100px" Title="Price">
<Template>
@{
RealTimeData dataItem = context as RealTimeData;
<span class="grid-price-cell p-0 @( GetPriceChangeClass(dataItem.Change) )">
@string.Format(SelectedCurrency.Sign + "{0:0.00}", dataItem.Price)
</span>
}
</Template>
</GridColumn>
<GridColumn Field="@nameof(RealTimeData.Change)" Width="100px" Title="Change">
<Template>
@{
RealTimeData dataItem = context as RealTimeData;
string sign = dataItem.Change > 0 ? "+" : "";
<span class="grid-price-cell p-0 @( GetPriceChangeClass(dataItem.Change) )">
@string.Format(sign + "{0:0.00}%", dataItem.Change)
</span>
}
</Template>
</GridColumn>
<GridColumn Field="@nameof(RealTimeData.StockExchangeLong)" Width="230px" Title="Stock Exchange" />
<GridColumn Field="@nameof(RealTimeData.StockExchangeShort)" Width="100px" Title="SE Short" />
<GridColumn Field="@nameof(RealTimeData.TimeZone)" Width="100px" Title="Time Zone" />
<GridColumn Field="@nameof(RealTimeData.TimeZoneName)" Width="180px" Title="Time Zone Name" />
@if (ShowAllColumns)
{
<GridColumn Field="@nameof(RealTimeData.YearLow)" Width="80px" Title="Year Low">
<Template>
@string.Format("{0:0.00}", (context as RealTimeData).YearLow)
</Template>
</GridColumn>
<GridColumn Field="@nameof(RealTimeData.YearHigh)" Width="90px" Title="Year High">
<Template>
@string.Format("{0:0.00}", (context as RealTimeData).YearHigh)
</Template>
</GridColumn>
<GridColumn Field="@nameof(RealTimeData.Volume)" Width="130px" Title="Volume">
<Template>
@string.Format("{0:0}", (context as RealTimeData).Volume)
</Template>
</GridColumn>
<GridColumn Field="@nameof(RealTimeData.MarketCap)" Width="150px" Title="Market Cap">
<Template>
@string.Format("{0:0}", (context as RealTimeData).MarketCap)
</Template>
</GridColumn>
}
</GridColumns>
</TelerikGrid>
</div>
</div>

Просмотреть файл

@ -0,0 +1,93 @@
using Microsoft.AspNetCore.Components;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using System.Threading;
using BlazorFinancePortfolio.Models;
using BlazorFinancePortfolio.Services;
using BlazorFinancePortfolio.Helpers;
using Microsoft.JSInterop;
namespace BlazorFinancePortfolio.Client.Pages
{
public partial class RealTime : IDisposable
{
[Inject] RealTimeDataService RealTimeDataService { get; set; }
[Inject] IJSRuntime JSRuntime { get; set; }
[CascadingParameter] public Currency SelectedCurrency { get; set; }
bool ShowAllColumns { get; set; }
int LoadDataInterval { get; set; } = 1500;
List<RealTimeData> GridData { get; set; }
CancellationTokenSource CancelToken;
Random rnd = new Random();
protected override async Task OnInitializedAsync()
{
await ToggleColumns();
WindowResizeDispatcher.WindowResize += ToggleColumns;
CancelToken = new CancellationTokenSource();
GridData = await RealTimeDataService.GetInitialData(SelectedCurrency.Symbol);
await IntervalDataUpdate();
}
async Task IntervalDataUpdate()
{
while (CancelToken.Token != null)
{
await Task.Delay(LoadDataInterval, CancelToken.Token);
await RefreshData();
StateHasChanged();
}
}
void StopTimer()
{
if (CancelToken != null)
{
CancelToken.Cancel();
}
}
protected async Task ToggleColumns()
{
if (WindowResizeDispatcher.WindowWidth == null)
{
WindowResizeDispatcher.WindowWidth = await JSRuntime.InvokeAsync<int>("getWindowWidth");
}
if (WindowResizeDispatcher.WindowWidth < 992)
{
ShowAllColumns = false;
}
else
{
ShowAllColumns = true;
}
StateHasChanged();
}
public void Dispose()
{
WindowResizeDispatcher.WindowResize -= ToggleColumns;
StopTimer();
}
async Task RefreshData()
{
foreach (RealTimeData item in GridData)
{
decimal change = RealTimeDataService.GetRandomChange();
item.Change = change;
item.Price = item.Price + change;
}
}
string GetPriceChangeClass(decimal change)
{
if (change > 0) return "price-up";
if (change < 0) return "price-down";
return "";
}
}
}

Просмотреть файл

@ -0,0 +1,79 @@
@page "/profile"
@using BlazorFinancePortfolio.Models
@using BlazorFinancePortfolio.Helpers
<TelerikWindow Visible="@IsProfileVisible" State="WindowState.Maximized">
<WindowContent>
<div class="container py-5">
<div class="col d-flex justify-content-between align-items-center py-5">
<h1>My Portfolio</h1>
<span @onclick="@CloseProfile" class="k-icon icon-close"></span>
</div>
<div class=" row d-flex justify-content-between pt-5">
<div class="col col-3">
<div class="user-profile-wrapper d-flex flex-column align-self-end">
<div class="profile-image"></div>
<h3 class="pt-4 pb-5">Collin Johnson</h3>
</div>
<table>
<tr>
<td>CURRENT VALUE:</td>
<td class="current-value">@(SelectedCurrency.Sign)@NumbersHelper.FormatDecimal(1694.88m * SelectedCurrency.Multiplier) </td>
</tr>
<tr>
<td>24H CHANGE:</td>
<td class="green">@(SelectedCurrency.Sign)@NumbersHelper.FormatDecimal(20m * SelectedCurrency.Multiplier)</td>
</tr>
<tr>
<td>% CHANGE:</td>
<td class="green">+1.2%</td>
</tr>
<tr>
<td>TOTAL COST:</td>
<td>@(SelectedCurrency.Sign)@NumbersHelper.FormatDecimal(9.185m * SelectedCurrency.Multiplier) </td>
</tr>
<tr>
<td>TOTAL PROFIT:</td>
<td class="red">@(SelectedCurrency.Sign)@NumbersHelper.FormatDecimal(-2.638m * SelectedCurrency.Multiplier) </td>
</tr>
</table>
</div>
<div class="col user-data">
<TelerikGrid Data="@Stocks">
<GridColumns>
<GridColumn Field="Symbol" Title="Symbol" Width="100px">
<Template>
<span class="symbol-col">@( (context as Stock).Symbol )</span>
</Template>
</GridColumn>
<GridColumn Field="Name" Title="Name" />
<GridColumn Field="Price" Title="Price" Width="100px">
<Template>
@{
var stock = context as Stock;
<strong>@(SelectedCurrency?.Sign + stock.Price)</strong>
}
</Template>
</GridColumn>
</GridColumns>
</TelerikGrid>
</div>
<div class="col col-5 pb-5">
<TelerikChart Width="100%" Height="100%" @ref="@ChartRef">
<ChartSeriesItems>
<ChartSeries Type="ChartSeriesType.Pie" Data="@Stocks"
Field="@nameof(Stock.Price)" CategoryField="@nameof(Stock.Symbol)">
<ChartSeriesLabels Template="@( $"#=category#\n{SelectedCurrency.Sign}#=value#" )" Visible="true" />
</ChartSeries>
</ChartSeriesItems>
<ChartLegend Position="ChartLegendPosition.Bottom" Visible="@ChartLegendVisible">
</ChartLegend>
</TelerikChart>
</div>
</div>
</div>
</WindowContent>
</TelerikWindow>

Просмотреть файл

@ -0,0 +1,90 @@
using Microsoft.AspNetCore.Components;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using BlazorFinancePortfolio.Models;
using BlazorFinancePortfolio.Helpers;
using BlazorFinancePortfolio.Services;
using Telerik.Blazor.Components;
using Microsoft.JSInterop;
namespace BlazorFinancePortfolio.Client.Pages
{
public partial class UserProfile : IDisposable
{
[Inject] NavigationManager NavManager { get; set; }
[Inject] StocksListService StocksListService {get;set;}
[Inject] IJSRuntime JSRuntime { get; set; }
[CascadingParameter] public Currency SelectedCurrency { get; set; }
[Parameter] public bool IsProfileVisible { get; set; }
[Parameter] public EventCallback<bool> IsProfileVisibleChanged { get; set; }
public List<Stock> Stocks { get; set; }
TelerikChart ChartRef { get; set; }
bool ChartLegendVisible { get; set; } = true;
protected override async Task OnInitializedAsync()
{
if (IsCurrentPageProfile())
{
IsProfileVisible = true;
}
Stocks = await StocksListService.GetStocks(true);
await ResizeChart();
WindowResizeDispatcher.WindowResize += ResizeChart;
await base.OnInitializedAsync();
}
async void CloseProfile()
{
if (IsCurrentPageProfile())
{
NavManager.NavigateTo("");
}
else
{
IsProfileVisible = false;
await IsProfileVisibleChanged.InvokeAsync(IsProfileVisible);
}
}
bool IsCurrentPageProfile()
{
string currPage = NavManager.Uri.Substring(Math.Min(NavManager.Uri.Length, NavManager.BaseUri.Length)).ToLowerInvariant();
return currPage.StartsWith("profile");
}
async Task ResizeChart()
{
if (WindowResizeDispatcher.WindowWidth == null)
{
WindowResizeDispatcher.WindowWidth = await JSRuntime.InvokeAsync<int>("getWindowWidth");
}
if (WindowResizeDispatcher.WindowWidth <= 992)
{
ChartLegendVisible = false;
}
else
{
ChartLegendVisible = true;
}
StateHasChanged();
if (ChartRef != null)
{
ChartRef.Refresh();
}
}
public void Dispose()
{
WindowResizeDispatcher.WindowResize -= ResizeChart;
}
}
}

16
Client/Program.cs Normal file
Просмотреть файл

@ -0,0 +1,16 @@
using Microsoft.AspNetCore.Blazor.Hosting;
namespace BlazorFinancePortfolio.Client
{
public class Program
{
public static void Main(string[] args)
{
CreateHostBuilder(args).Build().Run();
}
public static IWebAssemblyHostBuilder CreateHostBuilder(string[] args) =>
BlazorWebAssemblyHost.CreateDefaultBuilder()
.UseBlazorStartup<Startup>();
}
}

Просмотреть файл

@ -0,0 +1,27 @@
{
"iisSettings": {
"windowsAuthentication": false,
"anonymousAuthentication": true,
"iisExpress": {
"applicationUrl": "http://localhost:65065/blazor-stocks-portfolio/",
"sslPort": 44340
}
},
"profiles": {
"IIS Express": {
"commandName": "IISExpress",
"launchBrowser": true,
"environmentVariables": {
"ASPNETCORE_ENVIRONMENT": "Development"
}
},
"BlazorFinancePortfolio.Client": {
"commandName": "Project",
"launchBrowser": true,
"environmentVariables": {
"ASPNETCORE_ENVIRONMENT": "Development"
},
"applicationUrl": "https://localhost:5001/blazor-stocks-portfolio/;http://localhost:5000/blazor-stocks-portfolio/"
}
}
}

Просмотреть файл

@ -0,0 +1,126 @@
@inherits LayoutComponentBase
@using BlazorFinancePortfolio.Services
@using BlazorFinancePortfolio.Models
@using BlazorFinancePortfolio.Client.Pages
@inject CurrenciesService currenciesService
<TelerikRootComponent>
<nav class="navbar navbar-expand-lg py-3 header">
<div class="container d-flex justify-content-between">
<div class="title-wrap">
<h1 class="header-title">My Stocks Portfolio</h1>
<div class="d-flex flex-wrap justify-content-between">
<TelerikDropDownList Data="@Currencies"
Value="@SelectedCurrencyName"
TValue="string"
TItem="Currency"
ValueField="Symbol"
TextField="Name"
PopupWidth="200px"
PopupHeight="auto"
Width="200px"
ValueChanged="@OnCurrencySelect"
Class="ddl-no-bg">
</TelerikDropDownList>
<a href="https://github.com/telerik/blazor-stocks" class="source-code-link text-white-50 d-flex align-items-center ">
<svg class="navbar-nav-svg mr-1" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512" focusable="false"><title>GitHub</title><path d="M256 0C114.64 0 0 114.61 0 256c0 113.09 73.34 209 175.08 242.9 12.8 2.35 17.47-5.56 17.47-12.34 0-6.08-.22-22.18-.35-43.54-71.2 15.49-86.2-34.34-86.2-34.34-11.64-29.57-28.42-37.45-28.42-37.45-23.27-15.84 1.73-15.55 1.73-15.55 25.69 1.81 39.21 26.38 39.21 26.38 22.84 39.12 59.92 27.82 74.5 21.27 2.33-16.54 8.94-27.82 16.25-34.22-56.84-6.43-116.6-28.43-116.6-126.49 0-27.95 10-50.8 26.35-68.69-2.63-6.48-11.42-32.5 2.51-67.75 0 0 21.49-6.88 70.4 26.24a242.65 242.65 0 0 1 128.18 0c48.87-33.13 70.33-26.24 70.33-26.24 14 35.25 5.18 61.27 2.55 67.75 16.41 17.9 26.31 40.75 26.31 68.69 0 98.35-59.85 120-116.88 126.32 9.19 7.9 17.38 23.53 17.38 47.41 0 34.22-.31 61.83-.31 70.23 0 6.85 4.61 14.81 17.6 12.31C438.72 464.97 512 369.08 512 256.02 512 114.62 397.37 0 256 0z" fill="currentColor" fill-rule="evenodd"></path></svg>
Source Code
</a>
</div>
</div>
<div @onclick="@( () => ProfileVisible = true )" class="profile-wrapper d-flex flex-column align-self-center"
tabindex="0">
<div class="profile-image">
</div>
<div class="text-white-50">Collin Johnson</div>
</div>
</div>
</nav>
<main class="container-fluid px-0">
<div class="content px-4">
@if (Installable)
{
<div class="fixed-bottom d-flex align-items-center justify-content-between install-prompt">
<span>Install this app?</span>
<span>
<button class="btn btn-light btn-sm mx-2" @onclick="@InstallClicked">Install</button>
<button class="btn btn-outline-light btn-sm" @onclick="@(()=>Installable=false)">Cancel </button>
</span>
</div>
}
<CascadingValue Value="@SelectedCurrency">
@if (!ProfileVisible)
{
@Body
}
else
{
<UserProfile @bind-IsProfileVisible="@ProfileVisible" />
}
</CascadingValue>
</div>
</main>
<footer class="container-fluid footer text-center d-flex align-items-center">
<div class="w-100">
<span class="footer-copyright text-center">
Copyright ©2019-@DateTime.Now.Year Progress Software
Corporation and/or its subsidiaries or affiliates.
</span><span class="progress-logo d-inline-flex"></span>
</div>
</footer>
</TelerikRootComponent>
@code{
public Currency SelectedCurrency { get; set; }
bool ProfileVisible { get; set; }
List<Currency> Currencies { get; set; }
public string SelectedCurrencyName { get; set; } = "USD";
void OnCurrencySelect(string selectedCurrencySymbol)
{
SelectedCurrency = Currencies.FirstOrDefault(c => c.Symbol == selectedCurrencySymbol);
SelectedCurrencyName = selectedCurrencySymbol;
StateHasChanged();
}
protected override async Task OnInitializedAsync()
{
ml = () => InvokeAsync(StateHasChanged);// PWA infrastructure
Currencies = await currenciesService.GetCurrenciesAsync();
SelectedCurrency = Currencies.FirstOrDefault();
}
// PWA infrastructure
[Inject] IJSRuntime JSRuntime { get; set; } // needs to be here not as a @inject directive
static bool Installable = false;
static Action ml;
[JSInvokable("InstallPwaPrompt")]
//a named method so that it is easy to call from JS Interop, used in the config in the .csproj file
public static Task InstallPwaPrompt()
{
Installable = true;
ml.Invoke();
return Task.CompletedTask;
}
async void InstallClicked(MouseEventArgs args)
{
Installable = false;
await JSRuntime.InvokeAsync<object>("BlazorPWA.installPWA");//this name comes from the nuget package that generates the pwa code, don't change it unless you change the generated code
}
}

22
Client/Startup.cs Normal file
Просмотреть файл

@ -0,0 +1,22 @@
using BlazorFinancePortfolio.Services;
using Microsoft.AspNetCore.Components.Builder;
using Microsoft.Extensions.DependencyInjection;
namespace BlazorFinancePortfolio.Client
{
public class Startup
{
public void ConfigureServices(IServiceCollection services)
{
services.AddTelerikBlazor();
services.AddScoped<CurrenciesService>();
services.AddScoped<StocksListService>();
services.AddScoped<RealTimeDataService>();
}
public void Configure(IComponentsApplicationBuilder app)
{
app.AddComponent<App>("app");
}
}
}

384
Client/Styles/styles.css Normal file

Различия файлов скрыты, потому что одна или несколько строк слишком длинны

1
Client/Styles/styles.min.css поставляемый Normal file

Различия файлов скрыты, потому что одна или несколько строк слишком длинны

657
Client/Styles/styles.scss Normal file
Просмотреть файл

@ -0,0 +1,657 @@
// typography
$roboto: 'Roboto', Helvetica, Arial, sans-serif;
$ubunto: 'Ubuntu', sans-serif;
$font-weight-200: 200;
// colors
$white: #fff;
$black: #000;
$white-50: rgba(255, 255, 255, .5);
$text-base: #373A3C;
$border-base: #ddd;
$active-state-base: #1C7CD5;
$hover-bg: #e6e6e6;
$negative-value: #D9534F;
$positive-value: #5CB85C;
$body-base-bg: rgba(236, 238, 239, 0.5);
// margins
$margin-sm: 4px;
$margin-m: 2 * $margin-sm;
// fonts
$font-size-default: 16px;
// header
$header-height: 140px;
$header-title-padding: .5rem;
$profile-image-width: 58px;
$profile-image-height: 58px;
$profile-image-margin-top: 2rem;
// footer
$footer-padding: 10px;
$footer-height: 43px;
$footer-font-size: $font-size-default;
$progress-logo-height: 20px;
$progress-logo-width: 100px;
// dropdownlist
$dropdownlist-font-size: $font-size-default;
$dropdownlist-item-selected-bg: #E9ECEF;
$dropdownlist-item-selected-text: $active-state-base;
$dropownlist-add-new-padding: 8px;
$dropownlist-add-new-width: 125px;
$dropownlist-add-new-height: 40px;
// splitter
$splitter-bar-boxshadow: 5px 5px 5px rgba(#000, 0.2);
// grid
$grid-header-subtitle: 13px;
$grid-row-selection-bg: #007BFF;
$grid-cell-positive-color: $positive-value;
$grid-cell-negative-color: $negative-value;
$grid-sorting-icon-margin: $margin-sm;
$grid-sorting-icon-right-position: 20px;
// buttons
$nav-button-width: 150px;
$nav-button-active-bg: $active-state-base;
// badge
$badge-padding: 10px;
$badge-height: 22px;
// heatmap
$heatmap-base-bg: $body-base-bg;
$heatmap-size-container-mb: -35px;
$heatmap-scale-wrap-width: 385px;
$heatmap-scape-block-width: 35px;
$heatmap-scale-block-height: 18px;
// tooltip
$tooltip-content-padding-x: 6px;
$tooltip-content-padding-y: 2 * $tooltip-content-padding-x;
// user profile
$profile-font-size: 1.8em;
$profile-current-value-font-size: 1em;
$profile-table-padding: 8px 12px 8px 0;
$profile-table-cell-width: 180px;
$profile-icon-size: 38px;
$profile-user-wrapper: 210px;
// charts
$chart-navigation-drag-handle-bg: #ECEEEF;
$chart-navigation-drag-handle-border: #818A91;
// dateinputs
$dateinput-width: 140px;
/* Blazor app loading sign */
.loading-section {
text-align: center;
height: 80vh;
display: flex;
flex-direction: column;
justify-content: center;
}
.loading-section h2 {
color: $active-state-base;
}
.loader-dot {
height: 20px;
width: 20px;
border-radius: 50%;
background-color: $active-state-base;
display: inline-block;
-webkit-animation: grow 2.1s infinite ease-in-out both;
animation: grow 2.1s infinite ease-in-out both;
}
.loader-dot.dot1 {
-webkit-animation-delay: -0.96s;
animation-delay: -0.96s;
}
.loader-dot.dot2 {
-webkit-animation-delay: -0.48s;
animation-delay: -0.48s;
}
@-webkit-keyframes grow {
0%, 80%, 100% {
-webkit-transform: scale(0)
}
40% {
-webkit-transform: scale(1.0)
}
}
/* End Blazor app loading sign */
/* Symbol badge */
.selected-stock-badge {
height: $badge-height;
color: $white;
padding: $badge-padding;
border-radius: 2 * $badge-padding;
span:first-child {
margin-right: 2 * $margin-sm;
}
&.badge-positive-value {
background: $positive-value;
}
&.badge-negative-value {
background: $negative-value;
}
}
/* End symbol badge */
/* Header */
.header {
height: $header-height;
background: url('../images/header-bg.svg');
background-position: bottom;
background-repeat: no-repeat;
h1 {
padding-top: $header-title-padding;
font-family: $ubunto;
font-weight: $font-weight-200;
color: $white;
}
.profile-wrapper {
outline: none;
cursor: pointer;
}
.profile-image {
align-self: center;
width: $profile-image-width;
height: $profile-image-height;
border-radius: 50%;
background-image: url('../images/user.jpg');
background-size: cover;
background-position: center;
}
.title-wrap {
max-width: 80%;
}
.source-code-link {
padding: .375rem .75rem;
&:hover {
text-decoration: none;
}
.navbar-nav-svg {
width: 20px;
}
}
}
.k-dropdown {
.k-dropdown-wrap .k-input {
font-size: $dropdownlist-font-size;
padding: 0;
}
}
.k-popup {
padding: 0;
.k-list {
.k-item.k-state-selected {
background: $dropdownlist-item-selected-bg;
color: $dropdownlist-item-selected-text;
}
}
}
/* Stock list */
.dropdownlist-add-new {
padding-left: 0;
width: auto;
.k-dropdown-wrap {
border: 1px solid $border-base;
background: $white;
color: $text-base;
padding: $dropownlist-add-new-padding;
width: $dropownlist-add-new-width;
height: $dropownlist-add-new-height;
align-items: center;
.k-select {
.k-icon {
color: $text-base;
&.k-i-arrow-s {
display: none;
}
}
}
.k-input {
.k-icon {
&.k-i-plus {
margin-right: 2 * $margin-sm;
}
}
}
&:hover {
border-color: $hover-bg;
background-color: $hover-bg;
}
}
}
.btn-remove-stock.k-button.k-flat {
color: $negative-value;
&:focus:after {
box-shadow: none;
}
}
.k-dialog {
.k-header {
&, .k-icon {
font-weight: bold;
}
.k-icon {
font-size: 1.25rem;
}
}
.k-content.k-dialog-content {
display: flex;
align-items: center;
}
.k-dialog-buttongroup {
.k-button {
&:focus {
box-shadow: none;
}
}
}
}
.dropdown-list-selection {
align-items: center;
.k-dropdown-wrap .service-category {
font-weight: bold;
margin-left: $margin-sm;
}
}
.k-grid {
margin-bottom: 100px;
border-width: 0;
td:first-child {
text-overflow: unset;
}
.k-grid-header .k-header {
vertical-align: middle;
.grid-header-subtitle {
display: block;
margin: 0;
font-weight: normal;
font-size: $grid-header-subtitle;
}
.k-link > .k-icon.k-i-sort-asc-sm,
.k-link > .k-icon.k-i-sort-desc-sm {
margin-left: $grid-sorting-icon-margin;
}
.grid-header-subtitle + .k-icon.k-i-sort-asc-sm,
.grid-header-subtitle + .k-i-sort-desc-sm {
position: absolute;
top: 50%;
margin-top: - #{$grid-sorting-icon-right-position / 2};
right: $grid-sorting-icon-right-position;
}
}
.k-grid-content tr td {
vertical-align: top;
}
tr.k-state-selected > td {
color: inherit;
}
tr.k-state-selected > td.symbol-col {
color: $grid-row-selection-bg;
}
.symbol-col {
color: $grid-row-selection-bg;
}
th,
.symbol-col,
.price-col {
font-weight: bold;
}
.grid-cell-positive {
color: $grid-cell-positive-color;
}
.grid-cell-negative {
color: $grid-cell-negative-color;
}
}
/* Stock chart */
kendo-dateinput {
width: $dateinput-width;
margin-right: $margin-m;
}
.list-item {
&.time-filter-item {
cursor: pointer;
&.active {
font-weight: bold;
color: black;
border-bottom: 3px solid $active-state-base;
}
}
}
.chart-category {
&.selected {
font-weight: bold;
}
img {
margin-right: $margin-m;
}
}
.dropdownlist-service-selector.interval {
margin-right: $margin-m;
}
/* Footer */
.footer {
position: fixed;
bottom: 0;
padding: $footer-padding;
background: url('../images/footer-bg.svg');
background-repeat: no-repeat;
background-size: cover;
background-color: blue;
z-index: 999;
font-size: $footer-font-size;
color: $white;
.progress-logo {
width: $progress-logo-width;
height: $progress-logo-height;
vertical-align: middle;
background-image: url('../images/progress-logo.svg');
background-position: center;
background-repeat: no-repeat;
}
}
/* PWA Install Prompt */
.install-prompt {
width: 360px;
height: 60px;
padding: 15px 20px;
box-sizing: border-box;
bottom: 63px;
left: auto;
right: 20px;
border: 0;
border-radius: 2px;
font-family: $roboto;
font-size: 14px;
font-weight: lighter;
color: #FFFFFF;
background-color: #818A91;
z-index: 11000;
}
/* Profile */
h1 {
font-family: $ubunto;
}
td {
padding: $profile-table-padding;
}
.user-data {
.k-grid tbody td {
line-height: 20px;
padding: .4rem;
}
}
td:last-of-type {
font-weight: bold;
}
.green {
color: $positive-value;
}
.red {
color: $negative-value;
}
.current-value {
font-size: $profile-current-value-font-size;
}
.symbol-col {
color: $grid-row-selection-bg;
}
.k-icon {
cursor: pointer;
outline: none;
&.icon-close {
background: url('../images/cross-out.svg') center no-repeat;
width: $profile-icon-size;
height: $profile-icon-size;
}
}
.profile-stocks-grid {
border-width: 1px;
}
.user-profile-wrapper {
max-width: $profile-user-wrapper;
.profile-image {
align-self: center;
width: 2 * $profile-image-width;
height: 2 * $profile-image-height;
border-radius: 50%;
background-image: url('../images/user.jpg');
background-size: cover;
background-position: center;
}
h3 {
font-size: $profile-font-size;
}
}
/*end profile*/
/*error ui*/
#blazor-error-ui {
display: none;
position: fixed;
width: 30vw;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
z-index: 999999;
}
/*end error ui*/
/* collapse and exand button for the chart area */
.collapse-expand-button {
font-size: .875em;
font-weight: bold;
display: inline-flex;
align-items: center;
cursor: pointer;
}
/* end collapse-expand buttons */
/* real-time data */
.real-time-data {
.k-grid {
max-width: 100%;
tbody td {
white-space: nowrap;
line-height: 20px;
padding: 0.4rem 0.4rem;
}
.grid-price-cell {
padding: 4px 12px;
}
.price-up {
color: $positive-value;
}
.price-down {
color: $negative-value;
}
}
.price-down,
.price-up {
animation-duration: 700ms;
animation-name: blink;
animation-iteration-count: 1;
animation-direction: alternate;
animation-timing-function: ease-in-out;
}
@keyframes blink {
from {
opacity: 1;
}
to {
opacity: .4;
}
}
.back-icon {
font-size: 2em;
cursor: pointer;
outline: none;
}
}
/* end real-time data */
/* drpdowns background */
.ddl-no-bg.k-dropdown .k-dropdown-wrap {
background-color: transparent;
border-color: transparent;
}
.header .ddl-no-bg.k-dropdown .k-dropdown-wrap {
color: rgba($white, .5);
}
.stocks-grid .k-grid td:last-child {
padding-left: 0;
padding-right: 0;
}
.stocks-grid .k-grid td, .stocks-grid .k-grid th {
text-align: center;
}
stocks-grid .k-grid .k-grid-header th.k-header {
overflow: visible;
white-space: normal;
vertical-align: middle;
}
.stocks-grid-buttons .k-widget.k-dropdown .k-dropdown-wrap {
.k-select {
visibility: hidden;
}
}
/* responsive layouts */
@media screen and (max-width: 992px) {
.daterange-input-wrap, .scale-blocks, .scale-wrap, .user-data {
display: none !important
}
app-navigation {
-webkit-box-flex: 0 !important;
flex-grow: 0 !important
}
}
@media screen and (max-width: 480px) {
.k-button-group, .profile-wrapper {
display: none
}
.header-title {
font-size: 1.8rem
}
}
@media screen and (max-width: 769px) {
.dropdownlist-add-new {
padding-left: 16px !important
}
.k-button-group {
margin-top: 10px
}
.profile-wrapper div:last-child {
display: none
}
}

9
Client/_Imports.razor Normal file
Просмотреть файл

@ -0,0 +1,9 @@
@using System.Net.Http
@using Microsoft.AspNetCore.Components.Forms
@using Microsoft.AspNetCore.Components.Routing
@using Microsoft.AspNetCore.Components.Web
@using Microsoft.JSInterop
@using BlazorFinancePortfolio.Client
@using BlazorFinancePortfolio.Client.Shared
@using Telerik.Blazor
@using Telerik.Blazor.Components

Просмотреть файл

@ -0,0 +1,13 @@
[
{
"outputFile": "Styles/styles.css",
"inputFile": "Styles/styles.scss",
"minify": {
"enabled": true
},
"includeInProject": true,
"options": {
"sourceMap": true
}
}
]

13
Client/libman.json Normal file
Просмотреть файл

@ -0,0 +1,13 @@
{
"version": "1.0",
"defaultProvider": "unpkg",
"libraries": [
{
"library": "@progress/kendo-theme-bootstrap@latest",
"destination": "wwwroot/telerik-themes/bootstrap",
"files": [
"dist/all.css"
]
}
]
}

Просмотреть файл

@ -0,0 +1,136 @@
const baseURL = '/blazor-stocks-portfolio/';
const indexURL = '/blazor-stocks-portfolio/index.html';
const networkFetchEvent = 'fetch';
const swInstallEvent = 'install';
const swInstalledEvent = 'installed';
const swActivateEvent = 'activate';
const staticCachePrefix = 'blazor-cache-v';
const staticCacheName = 'blazor-cache-v21';
const requiredFiles = [
"/blazor-stocks-portfolio/_framework/blazor.boot.json",
"/blazor-stocks-portfolio/_framework/blazor.webassembly.js",
"/blazor-stocks-portfolio/_framework/wasm/mono.js",
"/blazor-stocks-portfolio/_framework/wasm/mono.wasm",
"/blazor-stocks-portfolio/_framework/_bin/BlazorFinancePortfolio.Client.dll",
"/blazor-stocks-portfolio/_framework/_bin/BlazorFinancePortfolio.Client.pdb",
"/blazor-stocks-portfolio/_framework/_bin/BlazorFinancePortfolio.Shared.dll",
"/blazor-stocks-portfolio/_framework/_bin/BlazorFinancePortfolio.Shared.pdb",
"/blazor-stocks-portfolio/_framework/_bin/Microsoft.AspNetCore.Blazor.dll",
"/blazor-stocks-portfolio/_framework/_bin/Microsoft.AspNetCore.Components.dll",
"/blazor-stocks-portfolio/_framework/_bin/Microsoft.AspNetCore.Components.Forms.dll",
"/blazor-stocks-portfolio/_framework/_bin/Microsoft.AspNetCore.Components.Web.dll",
"/blazor-stocks-portfolio/_framework/_bin/Microsoft.Bcl.AsyncInterfaces.dll",
"/blazor-stocks-portfolio/_framework/_bin/Microsoft.CSharp.dll",
"/blazor-stocks-portfolio/_framework/_bin/Microsoft.Extensions.DependencyInjection.Abstractions.dll",
"/blazor-stocks-portfolio/_framework/_bin/Microsoft.Extensions.DependencyInjection.dll",
"/blazor-stocks-portfolio/_framework/_bin/Microsoft.Extensions.Logging.Abstractions.dll",
"/blazor-stocks-portfolio/_framework/_bin/Microsoft.Extensions.Options.dll",
"/blazor-stocks-portfolio/_framework/_bin/Microsoft.Extensions.Primitives.dll",
"/blazor-stocks-portfolio/_framework/_bin/Microsoft.JSInterop.dll",
"/blazor-stocks-portfolio/_framework/_bin/Mono.Security.dll",
"/blazor-stocks-portfolio/_framework/_bin/Mono.WebAssembly.Interop.dll",
"/blazor-stocks-portfolio/_framework/_bin/mscorlib.dll",
"/blazor-stocks-portfolio/_framework/_bin/netstandard.dll",
"/blazor-stocks-portfolio/_framework/_bin/Newtonsoft.Json.dll",
"/blazor-stocks-portfolio/_framework/_bin/System.Buffers.dll",
"/blazor-stocks-portfolio/_framework/_bin/System.ComponentModel.Annotations.dll",
"/blazor-stocks-portfolio/_framework/_bin/System.ComponentModel.Composition.dll",
"/blazor-stocks-portfolio/_framework/_bin/System.ComponentModel.DataAnnotations.dll",
"/blazor-stocks-portfolio/_framework/_bin/System.Core.dll",
"/blazor-stocks-portfolio/_framework/_bin/System.Data.DataSetExtensions.dll",
"/blazor-stocks-portfolio/_framework/_bin/System.Data.dll",
"/blazor-stocks-portfolio/_framework/_bin/System.dll",
"/blazor-stocks-portfolio/_framework/_bin/System.Drawing.Common.dll",
"/blazor-stocks-portfolio/_framework/_bin/System.IO.Compression.dll",
"/blazor-stocks-portfolio/_framework/_bin/System.IO.Compression.FileSystem.dll",
"/blazor-stocks-portfolio/_framework/_bin/System.Memory.dll",
"/blazor-stocks-portfolio/_framework/_bin/System.Net.Http.dll",
"/blazor-stocks-portfolio/_framework/_bin/System.Numerics.dll",
"/blazor-stocks-portfolio/_framework/_bin/System.Numerics.Vectors.dll",
"/blazor-stocks-portfolio/_framework/_bin/System.Runtime.CompilerServices.Unsafe.dll",
"/blazor-stocks-portfolio/_framework/_bin/System.Runtime.Loader.dll",
"/blazor-stocks-portfolio/_framework/_bin/System.Runtime.Serialization.dll",
"/blazor-stocks-portfolio/_framework/_bin/System.ServiceModel.Internals.dll",
"/blazor-stocks-portfolio/_framework/_bin/System.Text.Encodings.Web.dll",
"/blazor-stocks-portfolio/_framework/_bin/System.Text.Json.dll",
"/blazor-stocks-portfolio/_framework/_bin/System.Threading.Tasks.Extensions.dll",
"/blazor-stocks-portfolio/_framework/_bin/System.Transactions.dll",
"/blazor-stocks-portfolio/_framework/_bin/System.Xml.dll",
"/blazor-stocks-portfolio/_framework/_bin/System.Xml.Linq.dll",
"/blazor-stocks-portfolio/_framework/_bin/Telerik.Blazor.dll",
"/blazor-stocks-portfolio/_framework/_bin/Telerik.DataSource.dll",
"/blazor-stocks-portfolio/_framework/_bin/WebAssembly.Bindings.dll",
"/blazor-stocks-portfolio/_framework/_bin/WebAssembly.Net.Http.dll",
"/blazor-stocks-portfolio/css/bootstrap/bootstrap.min.css",
"/blazor-stocks-portfolio/css/bootstrap/bootstrap.min.css.map",
"/blazor-stocks-portfolio/css/styles.min.css",
"/blazor-stocks-portfolio/default-icon-192x192.png",
"/blazor-stocks-portfolio/default-icon-512x512.png",
"/blazor-stocks-portfolio/favicon.ico",
"/blazor-stocks-portfolio/images/area.png",
"/blazor-stocks-portfolio/images/candle.png",
"/blazor-stocks-portfolio/images/cross-out.svg",
"/blazor-stocks-portfolio/images/footer-bg.svg",
"/blazor-stocks-portfolio/images/header-bg.svg",
"/blazor-stocks-portfolio/images/line.png",
"/blazor-stocks-portfolio/images/progress-logo.svg",
"/blazor-stocks-portfolio/images/user.jpg",
"/blazor-stocks-portfolio/index.html",
"/blazor-stocks-portfolio/telerik-themes/bootstrap/dist/all.css",
"/blazor-stocks-portfolio/windowResizeHandler.js",
"/blazor-stocks-portfolio/ServiceWorkerRegister.js",
"/blazor-stocks-portfolio/manifest.json"
];
// * listen for the install event and pre-cache anything in filesToCache * //
self.addEventListener(swInstallEvent, event => {
self.skipWaiting();
event.waitUntil(
caches.open(staticCacheName)
.then(cache => {
return cache.addAll(requiredFiles);
})
);
});
self.addEventListener(swActivateEvent, function (event) {
event.waitUntil(
caches.keys().then(function (cacheNames) {
return Promise.all(
cacheNames.map(function (cacheName) {
if (staticCacheName !== cacheName && cacheName.startsWith(staticCachePrefix)) {
return caches.delete(cacheName);
}
})
);
})
);
});
self.addEventListener(networkFetchEvent, event => {
const requestUrl = new URL(event.request.url);
if (requestUrl.origin === location.origin) {
if (requestUrl.pathname === baseURL) {
event.respondWith(caches.match(indexURL));
return;
}
}
event.respondWith(
caches.match(event.request)
.then(response => {
if (response) {
return response;
}
return fetch(event.request)
.then(response => {
if (response.ok) {
if (requestUrl.origin === location.origin) {
caches.open(staticCacheName).then(cache => {
cache.put(event.request.url, response);
});
}
}
return response.clone();
});
}).catch(error => {
console.error(error);
})
);
});

Просмотреть файл

@ -0,0 +1,68 @@
const serviceWorkerFileName = '/blazor-stocks-portfolio/ServiceWorker.js';
const swInstalledEvent = 'installed';
const staticCachePrefix = 'blazor-cache-v';
const updateAlertMessage = 'Update available. Reload the page when convenient.';
const blazorAssembly = 'BlazorFinancePortfolio.Client';
const blazorInstallMethod = 'InstallPwaPrompt';
window.updateAvailable = new Promise(function (resolve, reject) {
var { hostname } = window.location;
if (typeof ignoreHosts !== 'undefined') {
if (ignoreHosts.includes(hostname)) {
return;
}
}
if ('serviceWorker' in navigator) {
navigator.serviceWorker.register(serviceWorkerFileName)
.then(function (registration) {
console.log('Registration successful, scope is:', registration.scope);
registration.onupdatefound = () => {
const installingWorker = registration.installing;
installingWorker.onstatechange = () => {
switch (installingWorker.state) {
case swInstalledEvent:
if (navigator.serviceWorker.controller) {
resolve(true);
} else {
resolve(false);
}
break;
default:
}
};
};
})
.catch(error =>
console.log('Service worker registration failed, error:', error));
}
});
window['updateAvailable']
.then(isAvailable => {
if (isAvailable) {
alert(updateAlertMessage);
}
});
function showAddToHomeScreen() {
DotNet.invokeMethodAsync(blazorAssembly, blazorInstallMethod)
.then(function () { }, function (er) { setTimeout(showAddToHomeScreen, 1000); });
}
window.BlazorPWA = {
installPWA: function () {
if (window.PWADeferredPrompt) {
window.PWADeferredPrompt.prompt();
window.PWADeferredPrompt.userChoice
.then(function (choiceResult) {
window.PWADeferredPrompt = null;
});
}
}
};
window.addEventListener('beforeinstallprompt', function (e) {
// Prevent Chrome 67 and earlier from automatically showing the prompt
e.preventDefault();
// Stash the event so it can be triggered later.
window.PWADeferredPrompt = e;
showAddToHomeScreen();
});

7
Client/wwwroot/css/bootstrap/bootstrap.min.css поставляемый Normal file

Различия файлов скрыты, потому что одна или несколько строк слишком длинны

Различия файлов скрыты, потому что одна или несколько строк слишком длинны

1
Client/wwwroot/css/styles.min.css поставляемый Normal file

Различия файлов скрыты, потому что одна или несколько строк слишком длинны

Двоичные данные
Client/wwwroot/default-icon-192x192.png Normal file

Двоичный файл не отображается.

После

Ширина:  |  Высота:  |  Размер: 8.8 KiB

Двоичные данные
Client/wwwroot/default-icon-512x512.png Normal file

Двоичный файл не отображается.

После

Ширина:  |  Высота:  |  Размер: 22 KiB

Двоичные данные
Client/wwwroot/favicon.ico Normal file

Двоичный файл не отображается.

После

Ширина:  |  Высота:  |  Размер: 3.2 KiB

Двоичные данные
Client/wwwroot/images/area.png Normal file

Двоичный файл не отображается.

После

Ширина:  |  Высота:  |  Размер: 410 B

Двоичные данные
Client/wwwroot/images/candle.png Normal file

Двоичный файл не отображается.

После

Ширина:  |  Высота:  |  Размер: 391 B

Просмотреть файл

@ -0,0 +1,39 @@
<?xml version="1.0" encoding="iso-8859-1"?>
<!-- Generator: Adobe Illustrator 18.1.1, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
<svg version="1.1" id="Capa_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
viewBox="0 0 224.512 224.512" style="enable-background:new 0 0 224.512 224.512;" xml:space="preserve">
<g>
<polygon style="fill:#010002;" points="224.507,6.997 217.521,0 112.256,105.258 6.998,0 0.005,6.997 105.263,112.254
0.005,217.512 6.998,224.512 112.256,119.24 217.521,224.512 224.507,217.512 119.249,112.254 "/>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
</svg>

После

Ширина:  |  Высота:  |  Размер: 735 B

Просмотреть файл

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="1920" height="43" viewBox="0 0 1920 43"><defs><clipPath id="a"><rect width="1920" height="43" fill="#8d8d8d" style="mix-blend-mode:multiply;isolation:isolate"/></clipPath><linearGradient id="b" x1="0.546" y1="-0.009" x2="0.5" y2="0.968" gradientUnits="objectBoundingBox"><stop offset="0" stop-color="#1d2aa3"/><stop offset="1" stop-color="#425ad2"/></linearGradient><linearGradient id="c" x1="0.509" y1="-0.491" x2="0.5" y2="1" gradientUnits="objectBoundingBox"><stop offset="0" stop-color="#00b5dc" stop-opacity="0.58"/><stop offset="1" stop-color="#00b5dc" stop-opacity="0.141"/></linearGradient></defs><g clip-path="url(#a)"><g transform="translate(-52.239 -161.611)"><rect width="2108.063" height="132.002" transform="translate(43.918 116)" fill="url(#b)"/><g transform="translate(-169 -106.778)"><rect width="2415.489" height="875.609" fill="none"/><g transform="translate(22.014 99.518)"><path d="M122.273,222.71l331.388,86.9c36.969,9.695,78.433.593,101.458-22.269L722.827,120.821c26.134-25.949,75.317-33.715,114.275-18.045l223.589,89.936c22.977,9.242,50.537,10.609,75.076,3.725l252.566-70.857a120.42,120.42,0,0,1,66.847.81l564.346,174.317c31.328,9.677,67.65,5.491,93.153-10.738L2543.386,15.891c41.681-26.524,106.891-18.885,134.59,15.765l22.671,28.361c17.01,21.277,15.194,47.692-4.649,67.67L2191.2,635.935c-25.036,25.208-71.962,33.637-110.657,19.88l-225.292-80.1a119.822,119.822,0,0,0-74.595-1.192L1474.93,671.3a120.014,120.014,0,0,1-72.622-.513L935.132,514.945a120.236,120.236,0,0,0-60.237-3.664L630.451,559.063c-21.893,4.279-45.251,2.327-65.177-5.45L45.8,350.891C11.575,337.534-6.26,309.626,2,282.353l4.219-13.926c11.3-37.289,66-58.84,116.051-45.717" transform="translate(0 0)" fill="rgba(0,181,220,0.08)"/><path d="M2166.294,65.676a110.173,110.173,0,0,0-28.3,8.1L1800.385,223.556c-24.439,10.843-54.744,12.437-81.133,4.267l-239.419-74.118c-27.124-8.4-58.326-6.466-83.036,5.135L1107.168,294.832c-27.136,12.742-61.874,13.734-90.341,2.58L553.5,115.886c-22.877-8.963-50.135-10.185-74.395-3.335L57.045,231.718C26.832,240.249,5.66,260.017,2,283.119l-1.427,9C-4.8,325.941,28.411,356.68,75.49,361.468l476.135,48.419a108.679,108.679,0,0,1,41.18,12.4L868.417,573.966c26.433,14.547,62.09,17.125,92.053,6.658l366.846-128.152c25.7-8.979,55.9-8.439,80.936,1.447l306.671,121.066c39.473,15.583,89.018,7.245,114.658-19.3l230.014-238.108c18.5-19.151,50.266-29.415,82.565-26.68l433.87,36.75c48.844,4.138,92.915-21.243,97.733-56.284L2701.578,69.07c5.809-42.251-47.331-76.4-105.381-67.711Z" transform="translate(49.835 51.733)" fill="url(#c)"/></g></g></g></g></svg>

После

Ширина:  |  Высота:  |  Размер: 2.6 KiB

Просмотреть файл

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="1920" height="160" viewBox="0 0 1920 160"><defs><style>.a{fill:#1159a7;}.b{clip-path:url(#a);}.c{fill:url(#b);}.d{fill:none;}.e{fill:rgba(0,181,220,0.08);}.f{fill:url(#c);}</style><clipPath id="a"><rect class="a" width="1920" height="160"/></clipPath><linearGradient id="b" x1="0.454" y1="-0.009" x2="0.5" y2="0.968" gradientUnits="objectBoundingBox"><stop offset="0" stop-color="#121b95"/><stop offset="1" stop-color="#425ad2"/></linearGradient><linearGradient id="c" x1="0.509" y1="-0.491" x2="0.5" y2="1" gradientUnits="objectBoundingBox"><stop offset="0" stop-color="#00b5dc" stop-opacity="0.58"/><stop offset="1" stop-color="#00b5dc" stop-opacity="0.141"/></linearGradient></defs><g class="b"><rect class="c" width="2108.063" height="1168.002" transform="translate(-179.742 -161.611)"/><g transform="translate(-443.25 -140.389)"><rect class="d" width="2415.489" height="875.609"/><g transform="translate(-167.986 49.518)"><path class="e" d="M122.273,222.71l331.388,86.9c36.969,9.695,78.433.593,101.458-22.269L722.827,120.821c26.134-25.949,75.317-33.715,114.275-18.045l223.589,89.936c22.977,9.242,50.537,10.609,75.076,3.725l252.566-70.857a120.42,120.42,0,0,1,66.847.81l564.346,174.317c31.328,9.677,67.65,5.491,93.153-10.738L2543.386,15.891c41.681-26.524,106.891-18.885,134.59,15.765l22.671,28.361c17.01,21.277,15.194,47.692-4.649,67.67L2191.2,635.935c-25.036,25.208-71.962,33.637-110.657,19.88l-225.292-80.1a119.822,119.822,0,0,0-74.595-1.192L1474.93,671.3a120.014,120.014,0,0,1-72.622-.513L935.132,514.945a120.236,120.236,0,0,0-60.237-3.664L630.451,559.063c-21.893,4.279-45.251,2.327-65.177-5.45L45.8,350.891C11.575,337.534-6.26,309.626,2,282.353l4.219-13.926c11.3-37.289,66-58.84,116.051-45.717" transform="translate(0 0)"/><path class="f" d="M2166.294,65.676a110.173,110.173,0,0,0-28.3,8.1L1800.385,223.556c-24.439,10.843-54.744,12.437-81.133,4.267l-239.419-74.118c-27.124-8.4-58.326-6.466-83.036,5.135L1107.168,294.832c-27.136,12.742-61.874,13.734-90.341,2.58L553.5,115.886c-22.877-8.963-50.135-10.185-74.395-3.335L57.045,231.718C26.832,240.249,5.66,260.017,2,283.119l-1.427,9C-4.8,325.941,28.411,356.68,75.49,361.468l476.135,48.419a108.679,108.679,0,0,1,41.18,12.4L868.417,573.966c26.433,14.547,62.09,17.125,92.053,6.658l366.846-128.152c25.7-8.979,55.9-8.439,80.936,1.447l306.671,121.066c39.473,15.583,89.018,7.245,114.658-19.3l230.014-238.108c18.5-19.151,50.266-29.415,82.565-26.68l433.87,36.75c48.844,4.138,92.915-21.243,97.733-56.284L2701.578,69.07c5.809-42.251-47.331-76.4-105.381-67.711Z" transform="translate(49.835 51.733)"/></g></g></g></svg>

После

Ширина:  |  Высота:  |  Размер: 2.6 KiB

Двоичные данные
Client/wwwroot/images/line.png Normal file

Двоичный файл не отображается.

После

Ширина:  |  Высота:  |  Размер: 522 B

Просмотреть файл

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="89.433" height="20.85" viewBox="0 0 89.433 20.85"><defs><style>.a{fill:#5ce500;}.b{fill:#fff;}</style></defs><path class="a" d="M16.722,13.973a.677.677,0,0,1-.312.536L12.075,17V7.378L3.715,2.563,8.051.068a.685.685,0,0,1,.624,0L16.722,4.7ZM10.217,8.45l-5.725-3.3a.706.706,0,0,0-.624,0L0,7.378l6.037,3.477V17.81L9.905,15.58a.7.7,0,0,0,.312-.539ZM0,14.333l4.176,2.406V11.923Z" transform="translate(0 0.007)"/><g transform="translate(21.164 4.675)"><path class="b" d="M66.218,13.62H61.66V25.958h2.021V21.117h2.551c2.759,0,4.28-1.348,4.28-3.8,0-1.113-.416-3.7-4.294-3.7m2.242,3.733c0,1.39-.721,1.958-2.492,1.958H63.684V15.443h2.551c1.5,0,2.225.624,2.225,1.91m8.072-.381.152.073-.343,1.823-.236-.073a2,2,0,0,0-.586-.09c-1.889,0-2.041,1.5-2.041,3.324v3.93H71.607V16.9h1.757V17.99a2.415,2.415,0,0,1,1.816-1.206,2.518,2.518,0,0,1,1.352.187m4.6-.256a4,4,0,0,0-3.414,1.622,5.452,5.452,0,0,0-.915,3.105c0,2.863,1.7,4.713,4.329,4.713,3.2,0,4.329-2.575,4.329-4.779a5.074,5.074,0,0,0-1.057-3.241,4.088,4.088,0,0,0-3.272-1.421m0,7.77c-1.466,0-2.374-1.165-2.374-3.04,0-1.9.911-3.074,2.374-3.074s2.391,1.192,2.391,3.04c0,1.9-.915,3.074-2.391,3.074M101.2,16.971l.152.073-.343,1.823-.236-.073a2,2,0,0,0-.582-.09c-1.889,0-2.041,1.5-2.041,3.324v3.93H96.276V16.9h1.757V17.99a2.415,2.415,0,0,1,1.816-1.206,2.51,2.51,0,0,1,1.348.187m6.724,6.082-.035.1a1.993,1.993,0,0,1-2.041,1.348,2.345,2.345,0,0,1-2.416-2.468h6.4l.024-.177a5.531,5.531,0,0,0,.021-.721c0-.059,0-.111,0-.163a4.057,4.057,0,0,0-4.114-4.239,3.907,3.907,0,0,0-3.279,1.573,5.391,5.391,0,0,0-.97,3.157c0,2.8,1.74,4.682,4.332,4.682a3.72,3.72,0,0,0,3.889-2.866l.059-.222h-1.872Zm-4.429-2.644a2.176,2.176,0,0,1,2.239-2.052,2.132,2.132,0,0,1,2.194,2.052Zm-10.63-2.752a3.2,3.2,0,0,0-2.409-.943c-2.748,0-4,2.416-4,4.661,0,2.3,1.237,4.63,4,4.63a3.2,3.2,0,0,0,2.336-.95c-.007.4-.017.78-.028.943a2.023,2.023,0,0,1-2.26,2.19c-.783,0-1.681-.277-1.854-1.057l-.038-.173H86.766l.031.246c.194,1.6,1.594,2.589,3.66,2.589a3.8,3.8,0,0,0,3.726-1.972,6.371,6.371,0,0,0,.454-2.693V16.9H92.866v.759Zm-2.291,6.679c-.655,0-2.177-.3-2.177-3.04,0-1.8.853-2.925,2.225-2.925,1.061,0,2.194.769,2.194,2.925,0,1.9-.839,3.04-2.242,3.04m27.955-1.106c0,1.442-1.2,2.894-3.868,2.894-2.364,0-3.771-1.022-3.958-2.88l-.021-.191h1.844l.028.139c.225,1.116,1.289,1.352,2.142,1.352.936,0,1.934-.3,1.934-1.147,0-.426-.277-.731-.828-.9-.326-.1-.724-.208-1.151-.329-.728-.2-1.483-.416-1.948-.575a2.382,2.382,0,0,1-1.809-2.2c0-1.826,1.778-2.644,3.539-2.644,2.357,0,3.577.887,3.729,2.714l.014.187h-1.816l-.024-.142c-.184-1.05-1.227-1.206-1.833-1.206-.523,0-1.743.094-1.743.963,0,.4.336.686,1.033.887.208.059.516.139.87.229.738.191,1.66.43,2.125.589a2.276,2.276,0,0,1,1.743,2.263m8.647,0c0,1.442-1.2,2.894-3.868,2.894-2.364,0-3.771-1.022-3.958-2.88l-.021-.191h1.844l.028.139c.225,1.116,1.289,1.352,2.142,1.352.936,0,1.934-.3,1.934-1.147,0-.426-.277-.731-.828-.9-.326-.1-.728-.208-1.151-.329-.728-.2-1.483-.416-1.944-.575a2.382,2.382,0,0,1-1.809-2.2c0-1.826,1.778-2.644,3.539-2.644,2.357,0,3.577.887,3.729,2.714l.017.187h-1.816l-.024-.142c-.184-1.05-1.227-1.206-1.833-1.206-.523,0-1.743.094-1.743.963,0,.4.336.686,1.033.887.208.059.516.139.873.229.738.191,1.66.43,2.125.589a2.278,2.278,0,0,1,1.733,2.263" transform="translate(-61.66 -13.62)"/><path class="b" d="M252.555,21.019a1.289,1.289,0,1,1,1.275-1.289,1.266,1.266,0,0,1-1.275,1.289m0-2.36a1.071,1.071,0,1,0,1.043,1.071,1.034,1.034,0,0,0-1.043-1.071m-.149,1.31v.43h-.381V19.043h.634a.452.452,0,0,1,.5.471.4.4,0,0,1-.26.409l.308.471h-.423l-.26-.43h-.118Zm.211-.607h-.211v.3h.211c.114,0,.177-.055.177-.149s-.062-.152-.177-.152" transform="translate(-185.562 -16.769)"/></g></svg>

После

Ширина:  |  Высота:  |  Размер: 3.6 KiB

Двоичные данные
Client/wwwroot/images/user.jpg Normal file

Двоичный файл не отображается.

После

Ширина:  |  Высота:  |  Размер: 134 KiB

43
Client/wwwroot/index.html Normal file
Просмотреть файл

@ -0,0 +1,43 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<title>Telerik Blazor Stocks Portfolio</title>
<base href="/blazor-stocks-portfolio/" />
<link href="favicon.ico" rel="icon" type="image/x-icon" />
<link href="css/bootstrap/bootstrap.min.css" rel="stylesheet" />
<link href="css/styles.min.css" rel="stylesheet" />
<link href="telerik-themes/bootstrap/dist/all.css" rel="stylesheet" />
<script src="_content/telerik.ui.for.blazor.trial/js/telerik-blazor.js" defer></script>
<script src="ServiceWorkerRegister.js"></script>
<link href="manifest.json" rel="manifest" />
<link href="https://fonts.googleapis.com/css?family=Ubuntu&display=swap" rel="stylesheet">
</head>
<body>
<app>
<div class="loading-section">
<h2>Telerik Blazor Stocks Portfolio</h2>
<div class="short-description">You can review and manage your stocks and their performance. Please wait while the app initializes.</div>
<div class="loader mt-5">
<div class="loader-dot dot1"></div>
<div class="loader-dot dot2"></div>
<div class="loader-dot dot3"></div>
</div>
</div>
</app>
<div id="blazor-error-ui" class="alert alert-warning text-center py-3">
An error has occurred. This application may no longer respond until reloaded.
<div class="pt-3">
<a href class="reload btn-warning p-2"><span class="oi oi-reload"></span> Reload</a>
</div>
</div>
<script src="_framework/blazor.webassembly.js"></script>
<script src="windowResizeHandler.js" defer></script>
</body>
</html>

Просмотреть файл

@ -0,0 +1,18 @@
{
"short_name": "My Stocks",
"name": "My Blazor Stocks PWA",
"start_url": "/blazor-stocks-portfolio/",
"display": "standalone",
"icons": [
{
"src":"/blazor-stocks-portfolio/default-icon-192x192.png",
"type":"image/png",
"sizes":"192x192"
},
{
"src":"/blazor-stocks-portfolio/default-icon-512x512.png",
"type":"image/png",
"sizes":"512x512"
}
]
}

1
Client/wwwroot/telerik-themes/bootstrap/dist/all.css поставляемый Normal file

Различия файлов скрыты, потому что одна или несколько строк слишком длинны

Просмотреть файл

@ -0,0 +1,19 @@
function raiseResizeEvent() {
var namespace = 'BlazorFinancePortfolio.Shared';
var method = 'RaiseWindowResizeEvent';
DotNet.invokeMethodAsync(namespace, method, Math.floor(window.innerWidth), Math.floor(window.innerHeight))
.then(function () { },
function (er) { /* .net dispatches is probably not initialized yet, or the namespace/method name are wrong */ });
}
//throttle resize event, taken from https://stackoverflow.com/a/668185/812369
var timeout = false;
window.addEventListener("resize", function () {
if (timeout !== false)
clearTimeout(timeout);
timeout = setTimeout(raiseResizeEvent, 200);
});
function getWindowWidth() {
return window.innerWidth;
}

Просмотреть файл

@ -0,0 +1,17 @@
<Project Sdk="Microsoft.NET.Sdk.Web">
<PropertyGroup>
<TargetFramework>netcoreapp3.1</TargetFramework>
<LangVersion>7.3</LangVersion>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.AspNetCore.Blazor.Server" Version="3.1.0-preview4.19579.2" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\Client\BlazorFinancePortfolio.Client.csproj" />
<ProjectReference Include="..\Shared\BlazorFinancePortfolio.Shared.csproj" />
</ItemGroup>
</Project>

22
Server/Program.cs Normal file
Просмотреть файл

@ -0,0 +1,22 @@
using Microsoft.AspNetCore;
using Microsoft.AspNetCore.Hosting;
using Microsoft.Extensions.Configuration;
namespace BlazorFinancePortfolio.Server
{
public class Program
{
public static void Main(string[] args)
{
BuildWebHost(args).Run();
}
public static IWebHost BuildWebHost(string[] args) =>
WebHost.CreateDefaultBuilder(args)
.UseConfiguration(new ConfigurationBuilder()
.AddCommandLine(args)
.Build())
.UseStartup<Startup>()
.Build();
}
}

Просмотреть файл

@ -0,0 +1,27 @@
{
"iisSettings": {
"windowsAuthentication": false,
"anonymousAuthentication": true,
"iisExpress": {
"applicationUrl": "http://localhost:65063/blazor-stocks-portfolio/",
"sslPort": 44359
}
},
"profiles": {
"IIS Express": {
"commandName": "IISExpress",
"launchBrowser": true,
"environmentVariables": {
"ASPNETCORE_ENVIRONMENT": "Development"
}
},
"BlazorFinancePortfolio.Server": {
"commandName": "Project",
"launchBrowser": true,
"environmentVariables": {
"ASPNETCORE_ENVIRONMENT": "Development"
},
"applicationUrl": "https://localhost:5001/blazor-stocks-portfolio/;http://localhost:5000/blazor-stocks-portfolio/"
}
}
}

47
Server/Startup.cs Normal file
Просмотреть файл

@ -0,0 +1,47 @@
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.ResponseCompression;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using System.Linq;
namespace BlazorFinancePortfolio.Server
{
public class Startup
{
// This method gets called by the runtime. Use this method to add services to the container.
// For more information on how to configure your application, visit https://go.microsoft.com/fwlink/?LinkID=398940
public void ConfigureServices(IServiceCollection services)
{
services.AddMvc();
services.AddResponseCompression(opts =>
{
opts.MimeTypes = ResponseCompressionDefaults.MimeTypes.Concat(
new[] { "application/octet-stream" });
});
}
// This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
app.UseResponseCompression();
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
app.UseBlazorDebugging();
}
app.UseStaticFiles();
app.UseClientSideBlazorFiles<Client.Startup>();
app.UseRouting();
app.UseEndpoints(endpoints =>
{
endpoints.MapDefaultControllerRoute();
endpoints.MapFallbackToClientSideBlazor<Client.Startup>("index.html");
});
}
}
}

Просмотреть файл

@ -0,0 +1,11 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>netstandard2.1</TargetFramework>
<LangVersion>7.3</LangVersion>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Telerik.UI.for.Blazor.Trial" Version="2.6.1" />
</ItemGroup>
</Project>

Просмотреть файл

@ -0,0 +1,18 @@
namespace BlazorFinancePortfolio.Helpers
{
public static class Constants
{
public const int MS_1_DAY = 86400000;
public const int MS_5_MINUTES = MS_1_DAY / 24 / 12;
public const int MS_15_MINUTES = MS_1_DAY / 24 / 4;
public const int MS_30_MINUTES = MS_1_DAY / 24 / 2;
public const int MS_1_HOUR = MS_1_DAY / 24;
public const int MS_4_HOURS = MS_1_DAY / 6;
public const int MS_12_HOURS = MS_1_DAY / 2;
public const int MS_4_DAYS = MS_1_DAY * 4;
public const int MS_1_WEEK = MS_1_DAY * 7;
public const int MaxLabelStepsInStocksChart = 15;
}
}

Просмотреть файл

@ -0,0 +1,41 @@
using System;
namespace BlazorFinancePortfolio.Helpers
{
public class NumbersHelper
{
public static string FormatDecimal(decimal num)
{
// Ensure number has max 3 significant digits (no rounding up can happen)
long i = (long)Math.Pow(10, (int)Math.Max(0, Math.Log10(Convert.ToDouble(num)) - 2));
if (i != 0)
{
num = num / i * i;
}
if (num >= 1000000000)
return (num / 1000000000).ToString("0.##") + "B";
if (num >= 1000000)
return (num / 1000000).ToString("0.##") + "M";
if (num >= 1000)
return (num / 1000).ToString("0.##") + "K";
return num.ToString("0.##");
}
public static string GetChangeNumberClass(decimal value)
{
if (value > 0)
{
return "grid-cell-positive";
}
else if (value < 0)
{
return "grid-cell-negative";
}
return string.Empty;
}
}
}

Просмотреть файл

@ -0,0 +1,30 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.JSInterop;
namespace BlazorFinancePortfolio.Helpers
{
public static class WindowResizeDispatcher
{
public static event Func<Task> WindowResize;
public static int? WindowWidth { get; set; }
public static int? WindowHeight { get; set; }
[JSInvokable]
public static async Task RaiseWindowResizeEvent(int width, int height)
{
WindowWidth = width;
WindowHeight = height;
try
{
await WindowResize?.Invoke();
}
catch (Exception ex)
{
//something went wrong with the handlers
}
}
}
}

Просмотреть файл

@ -0,0 +1,23 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Telerik.Blazor;
namespace BlazorFinancePortfolio.Models
{
public class ChartTypeList
{
public ChartSeriesType Value { get; set; }
public string Text { get { return Value.ToString(); } }
public static List<ChartTypeList> GetAvailableSeriesTypes()
{
return new List<ChartTypeList>()
{
new ChartTypeList { Value = ChartSeriesType.Area },
new ChartTypeList { Value = ChartSeriesType.Line }
};
}
}
}

13
Shared/Models/Currency.cs Normal file
Просмотреть файл

@ -0,0 +1,13 @@
namespace BlazorFinancePortfolio.Models
{
public class Currency
{
public string Symbol { get; set; }
public string Name { get; set; }
public decimal Multiplier { get; set; }
public string Sign { get; set; }
}
}

11
Shared/Models/Interval.cs Normal file
Просмотреть файл

@ -0,0 +1,11 @@
using Telerik.Blazor;
namespace BlazorFinancePortfolio.Models
{
public class Interval
{
public ChartCategoryAxisBaseUnit Unit { get; set; }
public int Step { get; set; }
}
}

Просмотреть файл

@ -0,0 +1,30 @@
using BlazorFinancePortfolio.Helpers;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Telerik.Blazor;
namespace BlazorFinancePortfolio.Models
{
public class IntervalFilter
{
public string Name { get; set; }
public Interval Interval { get; set; }
public long Duration { get; set; }
public static List<IntervalFilter> GetIntervalFilters()
{
return new List<IntervalFilter>()
{
new IntervalFilter { Name = "5M", Interval = new Interval { Unit = ChartCategoryAxisBaseUnit.Minutes, Step = 5 }, Duration = Constants.MS_5_MINUTES },
new IntervalFilter { Name = "15M", Interval = new Interval { Unit = ChartCategoryAxisBaseUnit.Minutes, Step = 15 }, Duration = Constants.MS_15_MINUTES },
new IntervalFilter { Name = "30M", Interval = new Interval { Unit = ChartCategoryAxisBaseUnit.Minutes, Step = 30 }, Duration = Constants.MS_30_MINUTES },
new IntervalFilter { Name = "1H", Interval = new Interval { Unit = ChartCategoryAxisBaseUnit.Hours, Step = 1 }, Duration = Constants.MS_1_HOUR },
new IntervalFilter { Name = "4H", Interval = new Interval { Unit = ChartCategoryAxisBaseUnit.Hours, Step = 4 }, Duration = Constants.MS_4_HOURS },
new IntervalFilter { Name = "1D", Interval = new Interval { Unit = ChartCategoryAxisBaseUnit.Days, Step = 1 }, Duration = Constants.MS_1_DAY },
new IntervalFilter { Name = "1W", Interval = new Interval { Unit = ChartCategoryAxisBaseUnit.Weeks, Step = 1 }, Duration = Constants.MS_1_WEEK }
};
}
}
}

Просмотреть файл

@ -0,0 +1,26 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
namespace BlazorFinancePortfolio.Models
{
public class RealTimeData
{
public int Id { get; set; }
public string Symbol { get; set; }
public string Name { get; set; }
public string Currency { get; set; }
public decimal Price { get; set; }
public decimal Change { get; set; }
public string StockExchangeLong { get; set; }
public string StockExchangeShort { get; set; }
public string TimeZone { get; set; }
public string TimeZoneName { get; set; }
public decimal YearHigh { get; set; }
public decimal YearLow { get; set; }
public decimal Volume { get; set; }
public decimal MarketCap { get; set; }
}
}

30
Shared/Models/Stock.cs Normal file
Просмотреть файл

@ -0,0 +1,30 @@
using System;
using System.Collections.Generic;
namespace BlazorFinancePortfolio.Models
{
public class Stock
{
public string Symbol { get; set; }
public string Name { get; set; }
public decimal Price { get; set; }
public decimal DayChange { get; set; }
public decimal ChangePercentage { get; set; }
public decimal Volume { get; set; }
public decimal VolumeAvg { get; set; }
public decimal MarketCap { get; set; }
public decimal? PricePerEarningRatio { get; set; }
public bool IsCategorized { get; set; }
public IEnumerable<decimal> IntraDayChart { get; set; }
}
}

Просмотреть файл

@ -0,0 +1,35 @@
using System;
namespace BlazorFinancePortfolio.Models
{
public class StockIntervalDetails
{
public DateTime Date { get; set; }
public decimal Open { get; set; }
public decimal Close { get; set; }
public decimal High { get; set; }
public decimal Low { get; set; }
public decimal Volume { get; set; }
public string ColumnColor { get; set; } // return currentLargerThenPrev ? '#5CB85C' : '#FF6358';
public StockIntervalDetails Clone()
{
return new StockIntervalDetails
{
Date = new DateTime(this.Date.Ticks),
Close = this.Close,
ColumnColor = this.ColumnColor,
High = this.High,
Low = this.Low,
Open = this.Open,
Volume = this.Volume
};
}
}
}

Просмотреть файл

@ -0,0 +1,28 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
namespace BlazorFinancePortfolio.Models
{
public class TimeFilter
{
public string Name { get; set; }
public long Duration { get; set; }
private static int MS_PER_DAY = 86400000;
public static List<TimeFilter> GetFilters()
{
return new List<TimeFilter>()
{
new TimeFilter { Name = "1H", Duration = MS_PER_DAY / 24 },
new TimeFilter { Name = "4H", Duration = MS_PER_DAY / 6 },
new TimeFilter { Name = "12H",Duration = MS_PER_DAY / 2 },
new TimeFilter { Name = "1D", Duration = MS_PER_DAY },
new TimeFilter { Name = "4D", Duration = MS_PER_DAY * 4 },
new TimeFilter { Name = "1W", Duration = MS_PER_DAY * 7 }
};
}
}
}

Просмотреть файл

@ -0,0 +1,19 @@
using BlazorFinancePortfolio.Models;
using System.Collections.Generic;
using System.Threading.Tasks;
namespace BlazorFinancePortfolio.Services
{
public class CurrenciesService
{
public Task<List<Currency>> GetCurrenciesAsync()
{
var data = new List<Currency>();
data.Add(new Currency { Multiplier = 1, Name = "United States Dollar", Symbol = "USD", Sign = "$" });
data.Add(new Currency { Multiplier = 0.9m, Name = "Euro", Symbol = "EUR", Sign = "€" });
data.Add(new Currency { Multiplier = 0.76m, Name = "Pound sterling", Symbol = "GBP", Sign = "£" });
return Task.FromResult(data);
}
}
}

Просмотреть файл

@ -0,0 +1,62 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using BlazorFinancePortfolio.Models;
namespace BlazorFinancePortfolio.Services
{
public class RealTimeDataService
{
private static Random Rnd { get; set; } = new Random();
public decimal GetRandomChange()
{
double currRandom = Rnd.NextDouble();
double change = currRandom > 0.5 ? currRandom > 0.75 ? -Rnd.NextDouble() * 2 : Rnd.NextDouble() * 2 : 0;
return (decimal)change;
}
private string GetSymbol()
{
string letters = "ABCDEFGHIJKLMNOPQRSTUVWXYZ";
string symbol = string.Empty;
for (int i = 0; i < 4; i++)
{
symbol += letters[(int)Math.Floor(Rnd.NextDouble() * 26)];
}
return symbol;
}
public Task<List<RealTimeData>> GetInitialData(string currency)
{
List<RealTimeData> data = new List<RealTimeData>();
decimal price = (decimal)(Rnd.NextDouble() * 100 + 10);
for (int i = 1; i < 10001; i++)
{
string symbol = GetSymbol();
data.Add(new RealTimeData
{
Id = i,
Symbol = symbol,
Name = symbol + " Inc.",
Currency = currency,
Price = price,
Change = GetRandomChange(),
StockExchangeLong = "New York Stock Exchange",
StockExchangeShort = "NYSE",
TimeZone = "EDT",
TimeZoneName = "America/New_York",
YearHigh = price + price / 3,
YearLow = price - price / 3,
Volume = (decimal)(21774241 * Rnd.NextDouble() * 50),
MarketCap = (decimal)(229956956 * Rnd.NextDouble() * 50)
});
}
return Task.FromResult(data);
}
}
}

Просмотреть файл

@ -0,0 +1,665 @@
using BlazorFinancePortfolio.Models;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
namespace BlazorFinancePortfolio.Services
{
public class StocksListService
{
private List<Stock> UserStocks { get; set; }
public Task<List<Stock>>GetStocks(bool isCategorized)
{
if (UserStocks == null)
{
UserStocks = GetInitialStocks();
}
var categorizedStocks = UserStocks.Where(s => s.IsCategorized == isCategorized).ToList();
return Task.FromResult(categorizedStocks);
}
public List<StockIntervalDetails> GenerateStockIntervals(Stock stock, int intervalInMinutes, DateTime start, DateTime end)
{
var stocks = new List<StockIntervalDetails>
{
new StockIntervalDetails
{
Open = stock.Price - 0.1m,
Close = stock.Price,
ColumnColor = "#5CB85C",
Date = start,
High = stock.Price + 1.1m,
Low = stock.Price - 2.2m,
Volume = stock.Price * 10000
}
};
var startIterator = start.AddMinutes(intervalInMinutes);
var random = new Random();
var counter = 1;
while (startIterator < end)
{
var prevInterval = stocks[counter - 1];
// get random volatility from -3% to 3% for each N(intervalInMinutes) minutes
var randomVolatility = random.Next(-30, 30) * 0.001m;
var randomVolume = random.Next(100, 10000);
var randomHighPercentage = random.Next(100, 130) * 0.01m;
var randomLowPercentage = random.Next(70, 100) * 0.01m;
var change = prevInterval.Close * randomVolatility;
var newPrice = prevInterval.Close + change;
var high = newPrice * randomHighPercentage;
var low = newPrice * randomLowPercentage;
var stockToAdd = new StockIntervalDetails
{
Close = newPrice,
Date = startIterator,
Open = prevInterval.Close,
High = high,
Low = low,
Volume = newPrice * randomVolume
};
stockToAdd.ColumnColor = stockToAdd.Volume >= prevInterval.Volume ? "#5CB85C" : "#FF6358";
stocks.Add(stockToAdd);
counter++;
startIterator = startIterator.AddMinutes(intervalInMinutes);
}
return stocks;
}
public async Task<Stock> RemoveStock(Stock stockToRemove)
{
var matchingItem = UserStocks.FirstOrDefault(s => s.Symbol == stockToRemove.Symbol);
if (matchingItem != null)
{
matchingItem.IsCategorized = false;
return await Task.FromResult(matchingItem);
}
return await Task.FromResult(matchingItem);
}
public async Task<Stock> AddStock(Stock stockToAdd)
{
var matchingItem = UserStocks.FirstOrDefault(s => s.Symbol == stockToAdd.Symbol);
if (matchingItem != null)
{
matchingItem.IsCategorized = true;
}
return await Task.FromResult(matchingItem);
}
private List<Stock> GetInitialStocks()
{
var stockList = new List<Stock>();
stockList.Add(new Stock
{
Symbol = "AAN",
Name = "Aaron's, Inc.",
Price = 76.61m,
DayChange = -1.18m,
ChangePercentage = -1.52m,
Volume = 710442,
VolumeAvg = 837114,
MarketCap = 5174814208,
IsCategorized = true,
PricePerEarningRatio = 25.94m,
IntraDayChart = new List<decimal> { 77.77m, 77.48m, 77.47m, 77.22m, 77.29m, 76.9m, 76.69m, 76.65m, 76.69m, 76.82m, 76.75m, 76.81m, 76.87m, 76.84m, 76.7m, 76.65m, 76.44m, 76.29m, 76.42m, 76.32m, 76.2m, 75.94m, 75.87m, 75.8m, 75.99m, 76.09m, 75.8m, 75.82m, 75.91m, 75.84m, 75.85m, 76.01m, 75.99m, 75.92m, 75.99m, 76.06m, 76.06m, 76.11m, 76.17m, 76.18m, 76.17m, 76.13m, 76.04m, 75.88m, 75.72m, 75.69m, 75.92m, 75.99m, 76.04m, 76.07m, 76.03m, 75.95m, 75.75m, 75.58m, 75.8m, 75.87m, 75.93m, 76.08m, 75.95m, 76.01m, 76.05m, 76.07m, 76.16m, 76.21m, 76.2m, 76.38m, 76.41m, 76.43m, 76.38m, 76.51m, 76.7m, 76.65m, 76.71m, 76.68m, 76.65m, 76.46m, 76.53m, 76.59m }
});
stockList.Add(new Stock
{
Symbol = "AAPL",
Name = "Apple Inc.",
Price = 246.58m,
DayChange = 2.49m,
ChangePercentage = 1.02m,
Volume = 15827692,
VolumeAvg = 20028962,
MarketCap = 1114344259584,
IsCategorized = true,
PricePerEarningRatio = 20.94m,
IntraDayChart = new List<decimal> { 243.75m, 243.54m, 243.32m, 243.47m, 243.74m, 243.43m, 243.34m, 243.39m, 243.47m, 243.66m, 243.68m, 244.43m, 244.53m, 244.25m, 244.16m, 243.93m, 244.41m, 244.69m, 244.52m, 244.52m, 244.62m, 244.88m, 245.07m, 245.7m, 245.31m, 245.34m, 245.48m, 245.54m, 245.28m, 245.43m, 245.41m, 245.2m, 245.33m, 245.31m, 245.34m, 245.56m, 245.59m, 245.47m, 245.1m, 245.18m, 245.29m, 245.24m, 245.35m, 245.26m, 245.16m, 245.38m, 245.31m, 245.3m, 245.3m, 245.25m, 245.39m, 245.45m, 245.38m, 245.37m, 245.25m, 244.81m, 245.05m, 245.07m, 245.1m, 245.2m, 245.18m, 245.13m, 245.18m, 245.35m, 245.34m, 245.31m, 245.39m, 245.46m, 245.57m, 245.65m, 245.67m, 245.76m, 245.68m, 245.77m, 245.79m, 245.86m, 245.9m, 246.24m }
});
stockList.Add(new Stock
{
Symbol = "ACN",
Name = "Accenture plc",
Price = 183.07m,
DayChange = -0.77m,
ChangePercentage = -0.42m,
Volume = 1369124,
VolumeAvg = 1892150,
MarketCap = 116597284864,
IsCategorized = true,
PricePerEarningRatio = 24.87m,
IntraDayChart = new List<decimal> { 184.08m, 184.36m, 183.49m, 183.77m, 183.74m, 183.57m, 183.62m, 183.76m, 184.13m, 183.95m, 183.99m, 184.16m, 184.05m, 183.85m, 183.81m, 183.84m, 184.4m, 184.34m, 184.4m, 184.32m, 184.24m, 184.45m, 184.5m, 184.54m, 184.52m, 184.55m, 184.58m, 184.71m, 184.63m, 184.74m, 184.58m, 184.29m, 184.13m, 184.12m, 184.1m, 184.11m, 184.21m, 184.21m, 184.11m, 184.05m, 184.06m, 184m, 183.94m, 183.9m, 183.89m, 183.9m, 183.94m, 183.83m, 183.9m, 183.75m, 183.73m, 183.78m, 183.78m, 183.92m, 183.9m, 183.75m, 183.87m, 183.87m, 183.83m, 183.81m, 183.7m, 183.4m, 183.42m, 183.53m, 183.53m, 183.6m, 183.64m, 183.5m, 183.46m, 183.51m, 183.54m, 183.62m, 183.59m, 183.66m, 183.54m, 183.4m, 183.24m, 183.31m }
});
stockList.Add(new Stock
{
Symbol = "ADBE",
Name = "Adobe Inc.",
Price = 270.98m,
DayChange = 2.93m,
ChangePercentage = 1.09m,
Volume = 1511852,
VolumeAvg = 3342325,
MarketCap = 131175735296,
IsCategorized = true,
PricePerEarningRatio = 48.22m,
IntraDayChart = new List<decimal> { 267.66m, 267.8m, 268.63m, 269.04m, 269.44m, 269.13m, 269.3m, 269.95m, 270.13m, 269.81m, 269.9m, 270.2m, 270.35m, 270.2m, 270.14m, 270.33m, 271.13m, 270.73m, 270.55m, 270.31m, 270.46m, 271.06m, 271.54m, 271.31m, 270.86m, 270.97m, 271.18m, 271.33m, 271.16m, 271.29m, 271.11m, 270.73m, 270.61m, 270.82m, 271.02m, 270.95m, 271.18m, 271.07m, 270.96m, 271.03m, 271.02m, 270.95m, 270.91m, 270.93m, 270.88m, 271.06m, 271.03m, 270.89m, 271.02m, 270.95m, 271.04m, 270.74m, 270.86m, 270.57m, 270.85m, 270.68m, 270.63m, 270.59m, 270.59m, 270.7m, 270.82m, 270.35m, 270.53m, 270.73m, 270.76m, 271m, 271.06m, 270.64m, 270.65m, 270.79m, 270.88m, 270.88m, 270.9m, 271.02m, 270.77m, 270.72m, 270.68m, 270.59m }
});
stockList.Add(new Stock
{
Symbol = "AGM",
Name = "Federal Agricultural Mortgage Corporation",
Price = 84.57m,
DayChange = 0.17m,
ChangePercentage = 0.2m,
Volume = 22444,
VolumeAvg = 22114,
MarketCap = 890445952,
IsCategorized = true,
PricePerEarningRatio = 9.46m,
IntraDayChart = new List<decimal> { 84.42m, 84.87m, 84.87m, 84.02m, 84.02m, 84.18m, 84.11m, 83.5m, 82.7m, 82.7m, 82.97m, 82.97m, 82.97m, 83.08m, 83.08m, 83.29m, 83.29m, 83.01m, 83.01m, 83.21m, 83.21m, 83.18m, 83.38m, 83.08m, 83.08m, 83.34m, 83.34m, 83.39m, 83.39m, 83.35m, 83.35m, 83.32m, 83.32m, 83.28m, 83.28m, 83.28m, 83.58m, 83.58m, 83.58m, 83.26m, 83.26m, 83.49m, 83.49m, 84.52m, 84.52m, 84.1m, 84.1m, 84.1m, 83.41m, 84.77m, 83.53m, 83.53m, 83.92m, 83.92m, 83.76m, 83.76m, 84.44m, 84.44m, 84.44m, 84.33m, 84.72m, 84.59m }
});
stockList.Add(new Stock
{
Symbol = "AMZN",
Name = "Amazon.com, Inc.",
Price = 1779.99m,
DayChange = 17.78m,
ChangePercentage = 1.01m,
Volume = 2173743,
VolumeAvg = 3771314,
MarketCap = 882513674240,
IsCategorized = true,
PricePerEarningRatio = 78.87m,
IntraDayChart = new List<decimal> { 1762.22m, 1762m, 1763.11m, 1768.61m, 1768.02m, 1766.44m, 1764.64m, 1766.46m, 1767.14m, 1768.17m, 1767.75m, 1769.02m, 1768.68m, 1771.99m, 1771.46m, 1774.6m, 1778.5m, 1778.76m, 1776.4m, 1773.73m, 1774.49m, 1771.65m, 1772.55m, 1773.22m, 1773.5m, 1770.94m, 1769.19m, 1770.69m, 1771.39m, 1772.2m, 1770.41m, 1771.65m, 1769.62m, 1769.5m, 1769.54m, 1768.72m, 1768.31m, 1767.99m, 1767.33m, 1766.14m, 1765.66m, 1765.45m, 1765.79m, 1765.99m, 1767.82m, 1767.14m, 1768.4m, 1768.29m, 1767.83m, 1767.51m, 1769.12m, 1767.93m, 1768.07m, 1768.5m, 1769.81m, 1769.46m, 1775.4m, 1774.97m, 1772.7m, 1771m, 1768.94m, 1769.56m, 1774.53m, 1775.34m, 1778.57m, 1779.69m, 1780.34m, 1779.24m, 1778.32m, 1780m, 1781m, 1779.16m, 1778.02m, 1777.22m, 1777.04m, 1778.57m, 1780.24m, 1780m }
});
stockList.Add(new Stock
{
Symbol = "ASML",
Name = "ASML Holding N.V.",
Price = 263.99m,
DayChange = 1.26m,
ChangePercentage = 0.48m,
Volume = 549797,
VolumeAvg = 1164687,
MarketCap = 110834614272,
IsCategorized = true,
PricePerEarningRatio = 37.94m,
IntraDayChart = new List<decimal> { 262.8m, 262.63m, 262.18m, 261.98m, 262.34m, 262.23m, 262.23m, 262.21m, 262.78m, 262.62m, 262.75m, 262.74m, 262.88m, 262.85m, 263.11m, 263m, 263.38m, 263.45m, 263.63m, 263.23m, 263.45m, 263.16m, 263.29m, 263.48m, 263.52m, 263.89m, 264.08m, 264.5m, 264.54m, 264.38m, 263.95m, 263.82m, 263.78m, 263.93m, 264.13m, 264.16m, 263.97m, 263.94m, 264m, 263.84m, 263.98m, 263.95m, 264.11m, 264.15m, 264.26m, 264.06m, 264.49m, 264.02m, 264.08m, 264.6m, 264.65m, 264.27m, 264.36m, 263.92m, 263.75m, 264.29m, 264.09m, 264.24m, 264.33m, 263.92m, 263.77m, 263.74m, 264.02m, 263.88m, 263.82m, 263.79m, 263.6m, 263.43m, 263.58m, 263.57m, 263.61m, 263.55m, 263.57m, 263.41m, 264.06m }
});
stockList.Add(new Stock
{
Symbol = "AVGO",
Name = "Broadcom Inc.",
Price = 289.82m,
DayChange = 5.87m,
ChangePercentage = 2.07m,
Volume = 1987976,
VolumeAvg = 1691400,
MarketCap = 114963193856,
IsCategorized = true,
PricePerEarningRatio = 40.79m,
IntraDayChart = new List<decimal> { 283.88m, 284.32m, 284.93m, 284.65m, 285.68m, 285.99m, 286.07m, 285.96m, 286.29m, 286.21m, 286.29m, 286.64m, 286.62m, 286.22m, 286.17m, 285.92m, 286.8m, 286.83m, 287.17m, 287.14m, 287.66m, 287.87m, 287.92m, 288.42m, 288.4m, 288.42m, 288.8m, 288.9m, 289.24m, 289.27m, 288.96m, 288.16m, 288.27m, 288.4m, 288.8m, 288.8m, 289.16m, 288.92m, 288.97m, 289.09m, 289.16m, 288.75m, 288.88m, 289.1m, 289.4m, 290.08m, 290.01m, 290.26m, 290.67m, 290.22m, 290.47m, 290.91m, 290.79m, 291.15m, 291.27m, 290.16m, 290.1m, 289.79m, 290.02m, 290.05m, 290.03m, 289.9m, 289.92m, 290.29m, 290.22m, 290.28m, 290.24m, 290.46m, 290.64m, 290.62m, 290.67m, 290.61m, 290.63m, 290.19m, 290.01m, 290.2m, 289.89m, 289.75m }
});
stockList.Add(new Stock
{
Symbol = "BNPQY",
Name = "BNP Paribas SA",
Price = 26.43m,
DayChange = 0.43m,
ChangePercentage = 1.65m,
Volume = 103645,
VolumeAvg = 193571,
MarketCap = 66021871616,
IsCategorized = true,
PricePerEarningRatio = null,
IntraDayChart = new List<decimal> { 26m, 25.97m, 25.97m, 25.98m, 25.95m, 25.95m, 25.89m, 25.91m, 25.89m, 25.85m, 25.91m, 25.93m, 25.89m, 25.94m, 25.94m, 25.94m, 25.94m, 25.91m, 25.9m, 25.89m, 25.92m, 25.97m, 25.97m, 25.94m, 25.94m, 25.94m, 25.94m, 25.99m, 26.07m, 26.07m, 26.01m, 26.01m, 26.04m, 26.03m, 26.03m, 26.03m, 25.99m, 25.99m, 26.08m, 26.08m, 26.06m, 26.05m, 26.05m, 26.05m, 25.99m, 25.99m, 25.99m, 26.06m, 25.99m, 26.02m, 26.05m, 26.05m, 26m, 26m, 26m, 25.98m, 26m, 26m, 26.03m, 26.11m, 26.22m, 26.26m, 26.36m, 26.41m, 26.41m, 26.29m, 26.41m, 26.4m, 26.35m, 26.35m, 26.42m, 26.43m, 26.43m }
});
stockList.Add(new Stock
{
Symbol = "CACC",
Name = "Credit Acceptance Corporation",
Price = 439.2m,
DayChange = -0.69m,
ChangePercentage = -0.16m,
Volume = 57324,
VolumeAvg = 84857,
MarketCap = 8255554560,
IsCategorized = true,
PricePerEarningRatio = 13.4m,
IntraDayChart = new List<decimal> { 439.4m, 438.08m, 438.08m, 438.57m, 438.57m, 439.86m, 439.86m, 440.89m, 440.89m, 439.95m, 439.95m, 440m, 440.8m, 440.58m, 439.52m, 439.03m, 438.46m, 437.69m, 437.29m, 438.59m, 437.05m, 437.25m, 437.34m, 438.89m, 438.89m, 438.89m, 440m, 438.22m, 437.41m, 438.13m, 438.14m, 437.57m, 437.14m, 436.77m, 436.77m, 437.89m, 437.31m, 437.31m, 437.09m, 437.09m, 436.91m, 436.91m, 437.43m, 437.43m, 437.22m, 437.22m, 436.83m, 436.47m, 436.47m, 436.82m, 436.82m, 438.84m, 438.84m, 437.4m, 437.4m, 438.35m, 438.35m, 438.2m, 439.57m, 440.49m, 438.99m, 438.67m, 440.14m, 439.22m, 439.77m, 439.77m, 439.25m, 439.43m, 438.9m, 439.24m, 438.69m, 438.71m }
});
stockList.Add(new Stock
{
Symbol = "CAI",
Name = "CAI International, Inc.",
Price = 23.77m,
DayChange = -1.23m,
ChangePercentage = -4.92m,
Volume = 184691,
VolumeAvg = 114114,
MarketCap = 414063904,
PricePerEarningRatio = 12.99m,
IsCategorized = false,
IntraDayChart = new List<decimal> { 25m, 25.5m, 24.95m, 24.99m, 25.19m, 25m, 24.69m, 24.55m, 24.31m, 24.32m, 24.22m, 24.15m, 23.97m, 23.77m, 24.13m, 24m, 24m, 24.07m, 23.91m, 24.06m, 23.91m, 23.71m, 23.59m, 23.57m, 23.72m, 23.56m, 23.47m, 23.32m, 23.68m, 23.68m, 23.65m, 23.65m, 23.65m, 23.63m, 23.67m, 23.67m, 23.62m, 23.77m, 23.67m, 23.56m, 23.44m, 23.69m, 23.69m, 23.85m, 23.84m, 23.79m, 23.79m, 23.58m, 23.58m, 23.58m, 23.78m, 23.84m, 23.66m, 23.67m, 24.15m, 23.98m, 23.98m, 23.81m, 23.8m, 23.8m, 23.77m, 23.91m, 23.91m, 23.83m, 23.98m, 23.95m, 23.88m, 23.91m, 23.84m, 23.78m, 23.92m, 23.7m, 23.93m, 23.76m, 23.7m, 23.76m, 23.76m, 23.77m }
});
stockList.Add(new Stock
{
Symbol = "CBTX",
Name = "CBTX, Inc.",
Price = 28.71m,
DayChange = 0.04m,
ChangePercentage = 0.14m,
Volume = 43506,
VolumeAvg = 26514,
MarketCap = 746933696,
IsCategorized = false,
PricePerEarningRatio = 13.87m,
IntraDayChart = new List<decimal> { 28.58m, 28.58m, 28.55m, 28.55m, 28.55m, 28.43m, 28.43m, 28.33m, 28.33m, 28.43m, 28.43m, 28.43m, 28.45m, 28.45m, 28.46m, 28.46m, 28.75m, 28.64m, 28.7m, 28.7m, 28.55m, 28.55m, 28.55m, 28.55m, 28.55m, 28.49m, 28.49m, 28.46m, 28.46m, 28.47m, 28.43m, 28.43m, 28.41m, 28.39m, 28.39m, 28.5m, 28.51m, 28.48m, 28.23m, 28.23m, 28.41m, 28.41m, 28.45m, 28.45m, 28.55m, 28.5m, 28.55m, 28.55m, 28.75m, 28.75m, 28.74m, 28.74m, 28.9m, 28.9m, 28.82m, 28.82m, 28.79m, 28.85m, 28.62m }
});
stockList.Add(new Stock
{
Symbol = "CMA",
Name = "Comerica Incorporated",
Price = 66.52m,
DayChange = 0.28m,
ChangePercentage = 0.42m,
Volume = 1407582,
VolumeAvg = 1229371,
MarketCap = 9587926016,
IsCategorized = false,
PricePerEarningRatio = 8.42m,
IntraDayChart = new List<decimal> { 66.25m, 66.08m, 66.16m, 66.01m, 65.99m, 65.83m, 65.75m, 65.89m, 65.83m, 65.75m, 65.76m, 65.8m, 65.67m, 65.61m, 65.69m, 65.9m, 66.02m, 65.95m, 65.96m, 65.91m, 65.93m, 65.99m, 65.9m, 65.88m, 65.79m, 65.9m, 65.86m, 65.92m, 65.91m, 65.95m, 65.97m, 66.1m, 66.06m, 65.92m, 65.96m, 65.98m, 66.11m, 66.15m, 66.2m, 66.15m, 66.24m, 66.31m, 66.35m, 66.3m, 66.25m, 66.26m, 66.23m, 66.18m, 66.22m, 66.22m, 66.22m, 66.13m, 66.2m, 66.25m, 66.49m, 66.36m, 66.53m, 66.59m, 66.56m, 66.63m, 66.63m, 66.92m, 67.06m, 67.06m, 67.1m, 67.23m, 67.23m, 66.94m, 66.89m, 66.81m, 66.9m, 66.84m, 66.78m, 66.71m, 66.63m, 66.47m, 66.63m, 66.53m }
});
stockList.Add(new Stock
{
Symbol = "CRM",
Name = "salesforce.com, inc.",
Price = 150.49m,
DayChange = 3.21m,
ChangePercentage = 2.18m,
Volume = 4814264,
VolumeAvg = 5236950,
MarketCap = 131979730944,
IsCategorized = false,
PricePerEarningRatio = 124.99m,
IntraDayChart = new List<decimal> { 147.19m, 147.16m, 147.29m, 147.91m, 147.87m, 147.83m, 147.8m, 147.78m, 148.08m, 148.2m, 148.04m, 148.39m, 148.23m, 148.01m, 147.88m, 148.18m, 148.77m, 148.51m, 148.4m, 148.32m, 148.33m, 148.69m, 148.79m, 148.83m, 148.79m, 148.89m, 148.89m, 148.89m, 148.95m, 149.04m, 148.96m, 148.76m, 148.61m, 148.61m, 148.69m, 148.63m, 148.73m, 148.79m, 148.69m, 148.72m, 148.71m, 148.79m, 148.75m, 148.79m, 148.79m, 149.03m, 149.08m, 149.15m, 149.2m, 149.11m, 149.34m, 149.55m, 149.63m, 149.58m, 149.63m, 149.64m, 149.78m, 149.89m, 149.93m, 149.95m, 149.97m, 149.6m, 149.74m, 149.73m, 149.94m, 149.99m, 149.96m, 149.98m, 150.05m, 150.1m, 150.17m, 150.23m, 150.24m, 150.41m, 150.32m, 150.4m, 150.23m, 150.4m }
});
stockList.Add(new Stock
{
Symbol = "CSCO",
Name = "Cisco Systems, Inc.",
Price = 46.9m,
DayChange = 0.31m,
ChangePercentage = 0.67m,
Volume = 12554651,
VolumeAvg = 16144737,
MarketCap = 196328095744,
IsCategorized = false,
PricePerEarningRatio = 17.97m,
IntraDayChart = new List<decimal> { 46.59m, 46.67m, 46.74m, 46.77m, 46.79m, 46.76m, 46.79m, 46.78m, 46.78m, 46.82m, 46.77m, 46.79m, 46.82m, 46.79m, 46.74m, 46.67m, 46.76m, 46.77m, 46.74m, 46.74m, 46.74m, 46.8m, 46.88m, 46.96m, 46.96m, 46.94m, 46.95m, 46.96m, 46.94m, 46.95m, 46.91m, 46.86m, 46.88m, 46.87m, 46.88m, 46.86m, 46.9m, 46.94m, 46.92m, 46.93m, 46.94m, 46.92m, 46.92m, 46.89m, 46.87m, 46.91m, 46.89m, 46.88m, 46.88m, 46.85m, 46.89m, 46.9m, 46.85m, 46.87m, 46.83m, 46.86m, 46.86m, 46.78m, 46.8m, 46.81m, 46.78m, 46.72m, 46.72m, 46.78m, 46.8m, 46.83m, 46.86m, 46.87m, 46.88m, 46.86m, 46.88m, 46.92m, 46.96m, 46.94m, 46.94m, 46.94m, 46.88m, 46.9m }
});
stockList.Add(new Stock
{
Symbol = "ECPG",
Name = "Encore Capital Group, Inc.",
Price = 33.79m,
DayChange = -0.76m,
ChangePercentage = -2.2m,
Volume = 233014,
VolumeAvg = 189114,
MarketCap = 1049429568,
IsCategorized = false,
PricePerEarningRatio = 6.82m,
IntraDayChart = new List<decimal> { 34.56m, 34.45m, 34.45m, 34.53m, 34.38m, 34.26m, 34.17m, 34.17m, 34.13m, 34.19m, 34.21m, 34.27m, 34.27m, 34.17m, 34.17m, 34.17m, 34.12m, 34.17m, 34.18m, 34.18m, 34.14m, 34.1m, 34.12m, 34.09m, 34.09m, 34.03m, 33.99m, 33.97m, 33.98m, 33.97m, 34.02m, 33.99m, 33.99m, 34m, 33.96m, 34.01m, 34.01m, 34.03m, 34.05m, 34.1m, 34.04m, 34.06m, 34.01m, 34.01m, 34.01m, 33.9m, 33.88m, 33.88m, 33.85m, 33.85m, 33.86m, 33.88m, 33.88m, 33.89m, 33.92m, 33.96m, 33.94m, 33.94m, 33.91m, 33.88m, 33.89m, 33.87m, 33.9m, 33.9m, 33.87m, 33.87m, 33.89m, 33.86m, 33.85m, 33.87m, 33.76m, 33.85m, 33.79m, 33.81m, 33.9m, 33.86m, 33.8m }
});
stockList.Add(new Stock
{
Symbol = "EPRT",
Name = "Essential Properties Realty Trust, Inc.",
Price = 25.58m,
DayChange = 0.04m,
ChangePercentage = 0.16m,
Volume = 894983,
VolumeAvg = 909457,
MarketCap = 1952475392,
IsCategorized = false,
PricePerEarningRatio = 49.57m,
IntraDayChart = new List<decimal> { 25.54m, 25.43m, 25.4m, 25.29m, 25.21m, 25.24m, 25.26m, 25.32m, 25.3m, 25.23m, 25.33m, 25.39m, 25.41m, 25.3m, 25.37m, 25.36m, 25.36m, 25.43m, 25.45m, 25.43m, 25.37m, 25.46m, 25.48m, 25.49m, 25.48m, 25.5m, 25.51m, 25.55m, 25.53m, 25.54m, 25.56m, 25.6m, 25.53m, 25.51m, 25.5m, 25.53m, 25.51m, 25.54m, 25.56m, 25.55m, 25.55m, 25.5m, 25.47m, 25.47m, 25.45m, 25.47m, 25.5m, 25.53m, 25.48m, 25.49m, 25.43m, 25.38m, 25.45m, 25.46m, 25.4m, 25.42m, 25.34m, 25.37m, 25.36m, 25.34m, 25.35m, 25.33m, 25.42m, 25.41m, 25.48m, 25.44m, 25.46m, 25.48m, 25.5m, 25.49m, 25.49m, 25.45m, 25.52m, 25.56m, 25.57m, 25.58m, 25.59m, 25.58m }
});
stockList.Add(new Stock
{
Symbol = "FB",
Name = "Facebook, Inc.",
Price = 187.89m,
DayChange = 2.09m,
ChangePercentage = 1.12m,
Volume = 6979273,
VolumeAvg = 12925912,
MarketCap = 536040767488,
IsCategorized = false,
PricePerEarningRatio = 31.78m,
IntraDayChart = new List<decimal> { 185.81m, 185.25m, 185.73m, 185.74m, 185.93m, 186.33m, 186.08m, 186.03m, 186.39m, 186.33m, 186.44m, 186.87m, 187.51m, 187.11m, 187.29m, 187.43m, 187.83m, 187.96m, 188.27m, 188.29m, 188.25m, 188.5m, 188.76m, 188.93m, 188.54m, 188.44m, 188.52m, 188.55m, 188.33m, 188.35m, 188.21m, 187.93m, 188.05m, 188.29m, 188.36m, 188.5m, 188.57m, 188.47m, 188.43m, 188.63m, 188.66m, 188.66m, 188.57m, 188.57m, 188.43m, 188.57m, 188.52m, 188.4m, 188.29m, 188.26m, 188.44m, 188.24m, 188.28m, 188.36m, 188.24m, 187.91m, 188.05m, 188.12m, 187.77m, 187.85m, 187.83m, 187.62m, 187.74m, 187.93m, 187.96m, 187.96m, 188.04m, 188.17m, 188.18m, 188.18m, 188.05m, 188.19m, 188.1m, 188.19m, 188.07m, 188.02m, 187.93m, 187.9m }
});
stockList.Add(new Stock
{
Symbol = "FOR",
Name = "Forestar Group Inc.",
Price = 18.5m,
DayChange = -0.32m,
ChangePercentage = -1.7m,
Volume = 77837,
VolumeAvg = 88428,
MarketCap = 887951872,
IsCategorized = false,
PricePerEarningRatio = 6.72m,
IntraDayChart = new List<decimal> { 18.8m, 18.8m, 18.43m, 18.43m, 18.48m, 18.48m, 18.38m, 18.38m, 18.43m, 18.43m, 18.35m, 18.4m, 18.32m, 18.32m, 18.33m, 18.33m, 18.3m, 18.26m, 18.26m, 18.21m, 18.21m, 18.12m, 18.13m, 18.13m, 18.12m, 18.13m, 18.13m, 18.19m, 18.17m, 18.16m, 18.16m, 18.17m, 18.2m, 18.17m, 18.16m, 18.16m, 18.14m, 18.16m, 18.19m, 18.19m, 18.16m, 18.16m, 18.16m, 18.17m, 18.17m, 18.19m, 18.19m, 18.17m, 18.17m, 18.17m, 18.18m, 18.18m, 18.16m, 18.18m, 18.17m, 18.17m, 18.17m, 18.17m, 18.16m, 18.16m, 18.2m, 18.29m, 18.29m, 18.35m, 18.35m, 18.35m, 18.39m, 18.36m, 18.5m, 18.49m, 18.45m, 18.47m, 18.5m, 18.48m }
});
stockList.Add(new Stock
{
Symbol = "GATX",
Name = "GATX Corporation",
Price = 80.2m,
DayChange = -1.39m,
ChangePercentage = -1.7m,
Volume = 144502,
VolumeAvg = 283171,
MarketCap = 2815019776,
IsCategorized = false,
PricePerEarningRatio = 14.51m,
IntraDayChart = new List<decimal> { 81.6m, 81.53m, 81.64m, 81.64m, 81.26m, 81.26m, 80.63m, 80.89m, 80.91m, 80.98m, 81.06m, 81.09m, 80.93m, 80.89m, 80.8m, 80.85m, 80.65m, 80.68m, 80.58m, 80.46m, 80.6m, 80.38m, 80.24m, 80.01m, 79.79m, 80.02m, 80.01m, 80.08m, 80.3m, 80.4m, 80.46m, 80.6m, 80.6m, 80.32m, 80.37m, 80.44m, 80.61m, 80.49m, 80.49m, 80.58m, 80.58m, 80.59m, 80.64m, 80.52m, 80.49m, 80.3m, 80.45m, 80.36m, 80.29m, 80.35m, 80.45m, 80.36m, 80.41m, 80.4m, 79.86m, 79.41m, 79.56m, 79.64m, 80.03m, 80.16m, 80.17m, 80.22m, 80.18m, 80.25m, 80.46m, 80.56m, 80.55m, 80.59m, 80.47m, 80.42m, 80.57m, 80.41m, 80.39m, 80.24m, 80.23m, 80.09m, 80.26m, 80.21m }
});
stockList.Add(new Stock
{
Symbol = "GOOGL",
Name = "Alphabet Inc.",
Price = 1264.3m,
DayChange = 12.77m,
ChangePercentage = 1.02m,
Volume = 1243991,
VolumeAvg = 1160087,
MarketCap = 877319290880,
IsCategorized = false,
PricePerEarningRatio = 25.52m,
IntraDayChart = new List<decimal> { 1250.64m, 1251.31m, 1254.55m, 1256.5m, 1255.41m, 1254.68m, 1252.91m, 1254.09m, 1256.22m, 1256.61m, 1257.85m, 1260.42m, 1260.15m, 1259.65m, 1261.1m, 1261.26m, 1263.52m, 1264m, 1264.86m, 1264.52m, 1264.04m, 1264.98m, 1266.85m, 1267.93m, 1265.79m, 1266.83m, 1266.93m, 1267.49m, 1266.84m, 1266.75m, 1265.67m, 1264.12m, 1263.36m, 1264.1m, 1263.71m, 1264.6m, 1265m, 1265m, 1264.28m, 1264.64m, 1265.52m, 1265.67m, 1265.46m, 1265.36m, 1265.49m, 1265.6m, 1265.38m, 1265.37m, 1264.68m, 1263.98m, 1264.75m, 1265.1m, 1266m, 1266.34m, 1266.22m, 1264.06m, 1263.03m, 1263.04m, 1261.88m, 1262.14m, 1261.77m, 1261m, 1260.89m, 1260.72m, 1263.04m, 1263.1m, 1262.93m, 1262.81m, 1263.68m, 1263.31m, 1262.4m, 1263.43m, 1263.6m, 1264.01m, 1264.16m, 1263.89m, 1263.45m, 1263.41m }
});
stockList.Add(new Stock
{
Symbol = "IBM",
Name = "International Business Machines Corporation",
Price = 135.44m,
DayChange = 0.91m,
ChangePercentage = 0.68m,
Volume = 2543592,
VolumeAvg = 5996562,
MarketCap = 119982915584,
IsCategorized = false,
PricePerEarningRatio = 14.23m,
IntraDayChart = new List<decimal> { 134.52m, 134.76m, 134.94m, 134.59m, 134.44m, 134.35m, 134.45m, 134.42m, 134.39m, 134.52m, 134.84m, 135.26m, 134.93m, 134.77m, 134.82m, 134.88m, 135.34m, 135.29m, 135.45m, 135.43m, 135.36m, 135.7m, 135.72m, 135.89m, 135.9m, 135.75m, 135.72m, 135.82m, 135.74m, 135.76m, 135.65m, 135.5m, 135.46m, 135.55m, 135.64m, 135.59m, 135.55m, 135.59m, 135.51m, 135.4m, 135.46m, 135.44m, 135.46m, 135.49m, 135.48m, 135.49m, 135.48m, 135.37m, 135.37m, 135.3m, 135.3m, 135.32m, 135.31m, 135.35m, 135.36m, 135.18m, 135.25m, 135.43m, 135.55m, 135.5m, 135.43m, 135.32m, 135.29m, 135.28m, 135.28m, 135.4m, 135.4m, 135.34m, 135.38m, 135.37m, 135.35m, 135.39m, 135.44m, 135.43m, 135.48m, 135.43m, 135.41m, 135.43m }
});
stockList.Add(new Stock
{
Symbol = "INTC",
Name = "Intel Corporation",
Price = 56.46m,
DayChange = 1.99m,
ChangePercentage = 3.64m,
Volume = 56704514,
VolumeAvg = 16469900,
MarketCap = 252583968768,
IsCategorized = false,
PricePerEarningRatio = 13.11m,
IntraDayChart = new List<decimal> { 54.61m, 55.48m, 55.38m, 55.4m, 55.68m, 55.71m, 55.87m, 55.93m, 55.85m, 55.9m, 55.96m, 55.94m, 55.94m, 55.94m, 55.85m, 55.58m, 55.92m, 55.9m, 55.99m, 55.98m, 55.93m, 56.11m, 56.15m, 56.4m, 56.33m, 56.2m, 55.97m, 56.17m, 56.34m, 56.17m, 56.21m, 56.18m, 56.14m, 56.14m, 56.22m, 56.19m, 56.25m, 56.07m, 56.1m, 56.19m, 56.14m, 56.09m, 56.03m, 56.08m, 56.13m, 56.22m, 56.24m, 56.22m, 56.23m, 56.15m, 56.26m, 56.37m, 56.31m, 56.31m, 56.23m, 56.19m, 56.22m, 56.29m, 56.34m, 56.33m, 56.35m, 56.25m, 56.22m, 56.18m, 56.19m, 56.22m, 56.19m, 56.25m, 56.28m, 56.39m, 56.41m, 56.46m, 56.53m, 56.45m, 56.43m, 56.37m, 56.4m, 56.6m }
});
stockList.Add(new Stock
{
Symbol = "INTU",
Name = "Intuit Inc.",
Price = 257.67m,
DayChange = -1.99m,
ChangePercentage = -0.77m,
Volume = 1038001,
VolumeAvg = 1170162,
MarketCap = 67013271552,
IsCategorized = false,
PricePerEarningRatio = 43.75m,
IntraDayChart = new List<decimal> { 259.8m, 258.72m, 258.6m, 259.23m, 259.13m, 258.44m, 258.75m, 259.33m, 259.71m, 259.47m, 259.68m, 259.95m, 259.79m, 259.05m, 259.1m, 258.85m, 259.25m, 258.8m, 258.9m, 258.83m, 258.99m, 259.42m, 259.41m, 259.01m, 258.94m, 258.98m, 259.07m, 258.93m, 258.87m, 258.76m, 258.64m, 258.04m, 257.73m, 257.94m, 258.08m, 257.92m, 258.15m, 258.01m, 257.96m, 258.01m, 258.18m, 258.07m, 258.07m, 257.97m, 257.9m, 258.01m, 258.01m, 257.83m, 257.77m, 257.46m, 257.64m, 257.78m, 257.74m, 257.58m, 257.5m, 257.19m, 257.26m, 257.2m, 257.13m, 257.06m, 257.22m, 256.83m, 256.94m, 257.04m, 256.81m, 257.05m, 257.08m, 256.97m, 256.86m, 257.14m, 257.15m, 257.2m, 257.29m, 257.47m, 257.39m, 257.56m, 257.4m, 257.81m }
});
stockList.Add(new Stock
{
Symbol = "JPM",
Name = "JPMorgan Chase & Co.",
Price = 125.73m,
DayChange = -0.11m,
ChangePercentage = -0.09m,
Volume = 7792212,
VolumeAvg = 10052585,
MarketCap = 394352164864,
IsCategorized = false,
PricePerEarningRatio = 12.41m,
IntraDayChart = new List<decimal> { 125.86m, 125.75m, 126.07m, 125.8m, 125.74m, 125.78m, 125.52m, 125.68m, 125.78m, 125.78m, 125.69m, 125.68m, 125.47m, 125.29m, 125.35m, 125.44m, 125.52m, 125.57m, 125.6m, 125.57m, 125.56m, 125.52m, 125.29m, 125.31m, 125.22m, 125.34m, 125.24m, 125.05m, 124.98m, 125.07m, 125.08m, 125.11m, 125.07m, 125.14m, 125.26m, 125.36m, 125.45m, 125.35m, 125.45m, 125.44m, 125.44m, 125.54m, 125.64m, 125.51m, 125.43m, 125.4m, 125.49m, 125.46m, 125.48m, 125.49m, 125.44m, 125.39m, 125.49m, 125.45m, 125.57m, 125.28m, 125.44m, 125.46m, 125.59m, 125.62m, 125.65m, 125.74m, 126m, 126.05m, 126.08m, 126.03m, 125.96m, 125.79m, 125.75m, 125.69m, 125.93m, 125.82m, 125.82m, 125.73m, 125.67m, 125.51m, 125.72m, 125.75m }
});
stockList.Add(new Stock
{
Symbol = "MSFT",
Name = "Microsoft Corporation",
Price = 140.73m,
DayChange = 1.04m,
ChangePercentage = 0.75m,
Volume = 20464765,
VolumeAvg = 26070562,
MarketCap = 1094723174400,
IsCategorized = false,
PricePerEarningRatio = 26.55m,
IntraDayChart = new List<decimal> { 139.48m, 139.56m, 139.57m, 139.89m, 140.3m, 140.12m, 140.35m, 140.58m, 140.98m, 140.71m, 140.77m, 140.77m, 140.69m, 140.61m, 140.65m, 140.62m, 140.82m, 140.82m, 140.82m, 140.84m, 140.62m, 140.92m, 141.1m, 141.08m, 140.78m, 140.83m, 141.07m, 141.01m, 140.97m, 141.02m, 140.84m, 140.72m, 140.69m, 140.7m, 140.77m, 140.71m, 140.73m, 140.62m, 140.55m, 140.65m, 140.69m, 140.66m, 140.68m, 140.66m, 140.68m, 140.79m, 140.84m, 140.71m, 140.57m, 140.51m, 140.58m, 140.65m, 140.66m, 140.68m, 140.59m, 140.51m, 140.45m, 140.48m, 140.46m, 140.44m, 140.39m, 140.26m, 140.29m, 140.26m, 140.25m, 140.36m, 140.41m, 140.35m, 140.41m, 140.38m, 140.47m, 140.54m, 140.49m, 140.52m, 140.43m, 140.42m, 140.38m, 140.52m }
});
stockList.Add(new Stock
{
Symbol = "NVDA",
Name = "NVIDIA Corporation",
Price = 204.54m,
DayChange = 3,
ChangePercentage = 1.49m,
Volume = 10357677,
VolumeAvg = 8645212,
MarketCap = 122178895872,
IsCategorized = false,
PricePerEarningRatio = 46.12m,
IntraDayChart = new List<decimal> { 201.49m, 202.51m, 202.97m, 201.91m, 202.82m, 202.28m, 201.87m, 201.42m, 202.09m, 202.28m, 201.57m, 202.62m, 202.68m, 202.38m, 202.45m, 202.53m, 203.34m, 203.21m, 203.85m, 204.02m, 204.29m, 204.48m, 204.57m, 204.88m, 204.4m, 204.41m, 204.4m, 204.63m, 204.89m, 205.22m, 204.87m, 204.64m, 204.23m, 204.36m, 204.67m, 204.61m, 204.59m, 204.07m, 204.16m, 204.2m, 204.29m, 203.85m, 203.76m, 203.75m, 204m, 204.4m, 204.45m, 204.57m, 204.75m, 204.55m, 204.51m, 204.9m, 204.9m, 204.62m, 204.52m, 203.82m, 203.72m, 203.82m, 203.87m, 203.81m, 203.77m, 203.35m, 203.47m, 203.75m, 203.87m, 203.93m, 203.73m, 203.69m, 203.86m, 203.84m, 204.38m, 204.71m, 204.8m, 204.66m, 204.52m, 204.57m, 204.33m, 204.49m }
});
stockList.Add(new Stock
{
Symbol = "ORCL",
Name = "Oracle Corporation",
Price = 54.17m,
DayChange = 0.09m,
ChangePercentage = 0.17m,
Volume = 5741404,
VolumeAvg = 9189612,
MarketCap = 177814110208,
IsCategorized = false,
PricePerEarningRatio = 17.73m,
IntraDayChart = new List<decimal> { 54.04m, 54.27m, 54.25m, 54.19m, 54.23m, 54.24m, 54.24m, 54.29m, 54.35m, 54.34m, 54.35m, 54.36m, 54.28m, 54.24m, 54.18m, 54.13m, 54.22m, 54.21m, 54.21m, 54.21m, 54.18m, 54.28m, 54.34m, 54.38m, 54.38m, 54.36m, 54.38m, 54.38m, 54.38m, 54.42m, 54.39m, 54.28m, 54.3m, 54.28m, 54.27m, 54.26m, 54.27m, 54.23m, 54.22m, 54.23m, 54.25m, 54.2m, 54.2m, 54.26m, 54.27m, 54.28m, 54.26m, 54.26m, 54.24m, 54.19m, 54.22m, 54.22m, 54.22m, 54.2m, 54.2m, 54.13m, 54.16m, 54.15m, 54.14m, 54.1m, 54.08m, 54.01m, 54.04m, 54.05m, 54.04m, 54.1m, 54.12m, 54.1m, 54.12m, 54.12m, 54.13m, 54.16m, 54.18m, 54.19m, 54.15m, 54.15m, 54.13m, 54.13m }
});
stockList.Add(new Stock
{
Symbol = "PRGS",
Name = "Progress Software Corporation",
Price = 40.31m,
DayChange = 0.43m,
ChangePercentage = 1.08m,
Volume = 119189,
VolumeAvg = 166171,
MarketCap = 1805307648,
IsCategorized = false,
PricePerEarningRatio = 33.76m,
IntraDayChart = new List<decimal> { 39.86m, 39.69m, 39.69m, 39.72m, 39.74m, 39.68m, 39.5m, 39.45m, 39.55m, 39.55m, 39.65m, 39.65m, 39.63m, 39.76m, 39.79m, 39.89m, 39.87m, 39.86m, 39.85m, 39.85m, 39.85m, 39.85m, 39.85m, 39.86m, 39.87m, 39.92m, 39.88m, 39.86m, 39.83m, 39.82m, 39.83m, 39.83m, 39.95m, 39.94m, 39.9m, 39.94m, 39.94m, 39.94m, 39.95m, 39.97m, 39.96m, 39.94m, 39.93m, 39.91m, 39.89m, 39.92m, 39.93m, 39.96m, 39.95m, 39.93m, 39.92m, 39.93m, 39.9m, 39.96m, 39.88m, 39.9m, 39.88m, 39.94m, 39.98m, 39.95m, 39.91m, 39.9m, 39.93m, 39.97m, 40.18m, 40.18m, 40.19m, 40.19m, 40.19m, 40.24m, 40.35m, 40.28m, 40.3m, 40.27m, 40.33m, 40.3m, 40.33m, 40.29m }
});
stockList.Add(new Stock
{
Symbol = "QCOM",
Name = "QUALCOMM Incorporated",
Price = 80.17m,
DayChange = 0.94m,
ChangePercentage = 1.19m,
Volume = 5950509,
VolumeAvg = 5573175,
MarketCap = 97459462144,
IsCategorized = false,
PricePerEarningRatio = 29.34m,
IntraDayChart = new List<decimal> { 79.2m, 79.12m, 79.34m, 79.07m, 79.09m, 79.15m, 79.1m, 79.08m, 79.15m, 79.21m, 79.22m, 79.32m, 79.35m, 79.28m, 79.24m, 79.13m, 79.31m, 79.33m, 79.39m, 79.43m, 79.55m, 79.55m, 79.6m, 79.82m, 79.83m, 79.89m, 79.91m, 79.96m, 80m, 80.08m, 80.06m, 80.02m, 79.98m, 80m, 80.03m, 79.98m, 80.01m, 79.87m, 79.95m, 79.97m, 79.99m, 79.91m, 79.94m, 79.9m, 79.92m, 79.98m, 79.99m, 79.98m, 79.99m, 79.9m, 79.91m, 79.98m, 79.92m, 79.92m, 79.95m, 79.85m, 80.01m, 80.1m, 80.15m, 80.11m, 80.07m, 79.93m, 79.84m, 79.84m, 79.82m, 79.95m, 79.93m, 79.83m, 79.82m, 79.95m, 79.98m, 80.14m, 80.16m, 80.1m, 80.1m, 80.12m, 80.14m, 80.14m }
});
stockList.Add(new Stock
{
Symbol = "SAP",
Name = "SAP SE",
Price = 131.87m,
DayChange = 0.34m,
ChangePercentage = 0.26m,
Volume = 558142,
VolumeAvg = 1046450,
MarketCap = 162669559808,
IsCategorized = false,
PricePerEarningRatio = 31.86m,
IntraDayChart = new List<decimal> { 131.43m, 131.37m, 131.06m, 131.03m, 131.23m, 131.25m, 131.32m, 131.33m, 131.59m, 131.67m, 131.72m, 131.82m, 131.79m, 131.81m, 131.76m, 131.78m, 131.85m, 131.84m, 131.95m, 131.79m, 131.77m, 131.83m, 131.92m, 132.02m, 131.95m, 132.08m, 131.99m, 132.05m, 131.96m, 131.94m, 131.75m, 131.73m, 131.73m, 131.72m, 131.71m, 131.78m, 131.73m, 131.67m, 131.73m, 131.74m, 131.82m, 131.79m, 131.83m, 131.83m, 131.82m, 131.79m, 131.77m, 131.75m, 131.7m, 131.72m, 131.77m, 131.74m, 131.73m, 131.67m, 131.65m, 131.61m, 131.62m, 131.65m, 131.62m, 131.63m, 131.55m, 131.56m, 131.6m, 131.58m, 131.69m, 131.69m, 131.65m, 131.61m, 131.65m, 131.66m, 131.7m, 131.7m, 131.71m, 131.68m, 131.66m, 131.67m, 131.77m }
});
stockList.Add(new Stock
{
Symbol = "SNAP",
Name = "Snap Inc.",
Price = 13.52m,
DayChange = 0.29m,
ChangePercentage = 2.19m,
Volume = 54048642,
VolumeAvg = 44871025,
MarketCap = 18647865344,
IsCategorized = false,
PricePerEarningRatio = null,
IntraDayChart = new List<decimal> { 13.22m, 12.93m, 12.94m, 12.94m, 13.03m, 13.06m, 13.06m, 13.02m, 13.06m, 13.08m, 13.07m, 13.09m, 13.1m, 13.15m, 13.16m, 13.27m, 13.2m, 13.33m, 13.44m, 13.52m, 13.58m, 13.6m, 13.48m, 13.54m, 13.53m, 13.6m, 13.55m, 13.56m, 13.58m, 13.65m, 13.58m, 13.6m, 13.58m, 13.51m, 13.54m, 13.58m, 13.54m, 13.54m, 13.55m, 13.54m, 13.56m, 13.61m, 13.61m, 13.55m, 13.55m, 13.52m, 13.48m, 13.47m, 13.47m, 13.38m, 13.42m, 13.44m, 13.43m, 13.39m, 13.42m, 13.41m, 13.4m, 13.41m, 13.38m, 13.4m, 13.39m, 13.41m, 13.44m, 13.43m, 13.49m, 13.51m, 13.51m, 13.54m, 13.56m, 13.59m, 13.58m, 13.58m, 13.57m, 13.53m, 13.51m, 13.49m, 13.48m, 13.51m }
});
stockList.Add(new Stock
{
Symbol = "SNE",
Name = "Sony Corporation",
Price = 58.51m,
DayChange = 0.47m,
ChangePercentage = 0.81m,
Volume = 670753,
VolumeAvg = 757475,
MarketCap = 71632035840,
IsCategorized = false,
PricePerEarningRatio = 12.3m,
IntraDayChart = new List<decimal> { 58.11m, 58.09m, 58.15m, 58.11m, 58.19m, 58.17m, 58.16m, 58.2m, 58.27m, 58.24m, 58.24m, 58.25m, 58.29m, 58.25m, 58.28m, 58.34m, 58.41m, 58.38m, 58.4m, 58.35m, 58.35m, 58.38m, 58.47m, 58.48m, 58.47m, 58.52m, 58.53m, 58.59m, 58.58m, 58.57m, 58.52m, 58.5m, 58.48m, 58.48m, 58.48m, 58.45m, 58.47m, 58.39m, 58.38m, 58.39m, 58.42m, 58.43m, 58.43m, 58.39m, 58.42m, 58.4m, 58.39m, 58.43m, 58.46m, 58.42m, 58.47m, 58.45m, 58.43m, 58.46m, 58.47m, 58.4m, 58.45m, 58.46m, 58.5m, 58.5m, 58.47m, 58.44m, 58.46m, 58.48m, 58.5m, 58.49m, 58.5m, 58.52m, 58.52m, 58.55m, 58.53m, 58.54m, 58.54m, 58.58m, 58.57m, 58.58m, 58.56m, 58.58m }
});
stockList.Add(new Stock
{
Symbol = "TSM",
Name = "Taiwan Semiconductor Manufacturing Company Limited",
Price = 51.13m,
DayChange = 0.36m,
ChangePercentage = 0.71m,
Volume = 5965678,
VolumeAvg = 9484987,
MarketCap = 250063552512,
IsCategorized = false,
PricePerEarningRatio = 22.93m,
IntraDayChart = new List<decimal> { 50.79m, 50.85m, 50.92m, 50.9m, 50.92m, 50.91m, 50.86m, 50.78m, 50.73m, 50.82m, 50.85m, 50.87m, 50.85m, 50.88m, 50.97m, 50.95m, 51.05m, 51.06m, 50.99m, 51.01m, 51.03m, 50.93m, 50.92m, 51m, 51m, 50.92m, 50.97m, 50.98m, 50.97m, 50.94m, 50.93m, 50.9m, 50.92m, 50.92m, 50.96m, 50.99m, 51m, 50.97m, 51.04m, 51.03m, 51.01m, 51.05m, 51.05m, 51.06m, 51.06m, 51.06m, 51.05m, 51.03m, 51.03m, 51.01m, 50.99m, 51.03m, 51.04m, 51.04m, 51.04m, 50.97m, 50.96m, 50.96m, 50.99m, 51m, 51m, 50.97m, 50.99m, 51.01m, 51.03m, 51.06m, 51.04m, 51.03m, 51.07m, 51.03m, 51.04m, 51.12m, 51.14m, 51.1m, 51.11m, 51.09m, 51.1m, 51.15m }
});
stockList.Add(new Stock
{
Symbol = "TWTR",
Name = "Twitter, Inc.",
Price = 30.75m,
DayChange = -0.64m,
ChangePercentage = -2.07m,
Volume = 105075360,
VolumeAvg = 10571275,
MarketCap = 23770302464,
IsCategorized = false,
PricePerEarningRatio = 10.16m,
IntraDayChart = new List<decimal> { 30.95m, 30.53m, 30.56m, 30.64m, 30.53m, 30.39m, 30.26m, 30.13m, 30.03m, 29.99m, 30.11m, 30.16m, 30.14m, 30.02m, 30.01m, 30m, 30.11m, 30.16m, 30.22m, 30.17m, 30.15m, 30.2m, 30.23m, 30.33m, 30.35m, 30.3m, 30.39m, 30.44m, 30.48m, 30.55m, 30.34m, 30.47m, 30.37m, 30.42m, 30.44m, 30.47m, 30.51m, 30.56m, 30.48m, 30.52m, 30.53m, 30.63m, 30.58m, 30.51m, 30.45m, 30.52m, 30.52m, 30.49m, 30.33m, 30.34m, 30.29m, 30.3m, 30.31m, 30.26m, 30.31m, 30.2m, 30.25m, 30.15m, 30.1m, 30.07m, 30.08m, 30.06m, 30.07m, 30.14m, 30.17m, 30.13m, 30.14m, 30.13m, 30.15m, 30.18m, 30.2m, 30.24m, 30.21m, 30.32m, 30.31m, 30.33m, 30.33m, 30.31m }
});
stockList.Add(new Stock
{
Symbol = "TXN",
Name = "Texas Instruments Incorporated",
Price = 120.51m,
DayChange = 1.21m,
ChangePercentage = 1.01m,
Volume = 4106890,
VolumeAvg = 6018987,
MarketCap = 112698302464,
IsCategorized = false,
PricePerEarningRatio = 22.37m,
IntraDayChart = new List<decimal> { 119.32m, 119.08m, 119.2m, 118.7m, 118.93m, 119.07m, 118.95m, 118.92m, 118.99m, 118.98m, 119.1m, 118.98m, 118.92m, 118.8m, 118.84m, 118.81m, 119.13m, 119.21m, 119.36m, 119.32m, 119.32m, 119.34m, 119.31m, 119.61m, 119.58m, 119.79m, 119.85m, 119.97m, 120m, 119.92m, 119.78m, 119.71m, 119.78m, 119.97m, 120.08m, 120.07m, 120.22m, 120.14m, 120.18m, 120.25m, 120.25m, 120.13m, 120.28m, 120.32m, 120.38m, 120.46m, 120.55m, 120.52m, 120.59m, 120.37m, 120.39m, 120.56m, 120.43m, 120.51m, 120.43m, 120.24m, 120.29m, 120.23m, 120.21m, 120.22m, 120.24m, 120.03m, 120.12m, 120.17m, 120.24m, 120.28m, 120.32m, 120.33m, 120.25m, 120.29m, 120.35m, 120.5m, 120.5m, 120.5m, 120.39m, 120.46m, 120.37m, 120.53m }
});
stockList.Add(new Stock
{
Symbol = "XOM",
Name = "Exxon Mobil Corporation",
Price = 67.72m,
DayChange = -0.54m,
ChangePercentage = -0.79m,
Volume = 11995831,
VolumeAvg = 10012842,
MarketCap = 286530764800,
IsCategorized = false,
PricePerEarningRatio = 16.32m,
IntraDayChart = new List<decimal> { 68.28m, 68.18m, 68.1m, 68.04m, 68.04m, 68.03m, 68m, 68.1m, 68.11m, 68.02m, 68.07m, 68.01m, 68m, 68.07m, 67.97m, 68m, 67.98m, 67.99m, 67.93m, 67.88m, 67.85m, 67.82m, 67.81m, 67.82m, 67.77m, 67.81m, 67.77m, 67.72m, 67.71m, 67.64m, 67.71m, 67.78m, 67.71m, 67.71m, 67.68m, 67.67m, 67.7m, 67.61m, 67.62m, 67.59m, 67.62m, 67.61m, 67.7m, 67.71m, 67.7m, 67.62m, 67.6m, 67.63m, 67.6m, 67.57m, 67.57m, 67.5m, 67.47m, 67.5m, 67.46m, 67.36m, 67.41m, 67.4m, 67.43m, 67.51m, 67.39m, 67.31m, 67.3m, 67.36m, 67.43m, 67.5m, 67.55m, 67.54m, 67.54m, 67.53m, 67.54m, 67.56m, 67.53m, 67.5m, 67.51m, 67.49m, 67.64m, 67.74m }
});
return stockList;
}
}
}

11
readme.md Normal file
Просмотреть файл

@ -0,0 +1,11 @@
# Blazor PWA App - Stocks Portfolio
This sample application demonstrates one way to implement a PWA ([Progressive Web Application](https://developers.google.com/web/progressive-web-apps)) functionality in a Blazor WASM app.
To generate the needed PWA assets, the [Blazor.PWA.MSBuild by SQL-MisterMagoo](https://github.com/SQL-MisterMagoo/Blazor.PWA.MSBuild) is used in this example. You can write them manually, use a different tool, or even see if Microsoft will prepare a ready-made PWA project template for Blazor.
The SASS styles are built to CSS through the [WebCopmiler package by madskristensen ](https://github.com/madskristensen/WebCompiler), then a build task in the `csproj` file copies the output to the `wwwroot` folder. This requires that you build through Visual Studio, a command-line build may throw an exception like `Access to the path '7z.dll' is denied`, depending on the machine setup and permissions.
The Telerik theme is fetched as a [local dependency](https://docs.telerik.com/blazor-ui/themes/overview#optional-dependency-management) to allow for easier caching for offline use of the PWA.
Data is generated in services for simplicity in this sample. They do not provide full offline capabilities (such as [offline detection](https://stackoverflow.com/questions/44756154/progressive-web-app-how-to-detect-and-handle-when-connection-is-up-again) and caching changes that can be synced later) as this is beyond the scope of this example.