Compare commits

...

13 Commits

Author SHA1 Message Date
bbe9183fb0 Release 1.140.0 (#852) 2022-04-22 21:29:08 +02:00
1b03ddc586 Feature/add symbol profile overrides model (#851)
* Add symbol profile overrides model

* Update changelog
2022-04-22 21:27:55 +02:00
beb12637ce Bugfix/fix total calculation for sell and dividend (#850)
* Fix calculation for sell and dividend activities

* Update changelog
2022-04-22 19:29:18 +02:00
20358d9105 Feature/persist savings rate (#849)
* Persist savings rate

* Update changelog
2022-04-21 23:07:19 +02:00
0e4c39d145 Feature/reuse value component in ghostfolio in numbers section (#846)
* Reuse value component

* Update changelog
2022-04-19 17:06:12 +02:00
83ebacbb06 Add Buy me a coffee link (#848) 2022-04-18 21:32:54 +02:00
7c58c5fb7f Setup funding.yml (#847) 2022-04-18 21:20:30 +02:00
f3271ab1ff Feature/upgrade yahoo finance2 to version 2.3.1 (#844)
* Upgrade yahoo-finance2 to version 2.3.1

* Update changelog
2022-04-18 17:10:00 +02:00
9f597cbff1 Release 1.139.0 (#843) 2022-04-18 11:59:16 +02:00
90efc2ac51 Feature/beautify etf names in asset profile (#842)
* Beautify ETF names

* Update changelog
2022-04-18 11:57:57 +02:00
056b318d86 Bugfix/fix end date in ics files (#841)
* Fix end date

* Update changelog
2022-04-18 11:31:16 +02:00
82ede2fe32 Bugfix/fix fear and greed data source (#840)
* Fix data source of Fear & Greed Index

* Update changelog
2022-04-18 10:49:02 +02:00
8ae041faa0 Bugfix/fix issue in fire calculator after changing investment horizon (#839)
* Properly update chart datasets and improve tooltip

* Update changelog
2022-04-18 10:31:16 +02:00
24 changed files with 344 additions and 125 deletions

1
.github/FUNDING.yml vendored Normal file
View File

@ -0,0 +1 @@
custom: ['https://www.buymeacoffee.com/ghostfolio']

View File

@ -5,6 +5,43 @@ All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
## 1.140.0 - 22.04.2022
### Added
- Added support for sub-labels in the value component
- Added a symbol profile overrides model for manual adjustments
### Changed
- Reused the value component in the _Ghostfolio in Numbers_ section of the about page
- Persisted the savings rate in the _FIRE_ calculator
- Upgraded `yahoo-finance2` from version `2.3.0` to `2.3.1`
### Fixed
- Fixed the calculation of the total value for sell and dividend activities in the create or edit transaction dialog
### Todo
- Apply data migration (`yarn database:migrate`)
## 1.139.0 - 18.04.2022
### Added
- Added the total amount to the tooltip in the chart of the _FIRE_ calculator
### Changed
- Beautified the ETF names in the symbol profile
### Fixed
- Fixed an issue with changing the investment horizon in the chart of the _FIRE_ calculator
- Fixed an issue with the end dates in the `.ics` file of the future activities (drafts) export
- Fixed the data source of the _Fear & Greed Index_ (market mood)
## 1.138.0 - 16.04.2022
### Added
@ -14,7 +51,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
### Changed
- Separated the deposit and savings in the chart of the the _FIRE_ calculator
- Separated the deposit and savings in the chart of the _FIRE_ calculator
## 1.137.0 - 15.04.2022

View File

@ -246,6 +246,8 @@ Ghostfolio is **100% free** and **open source**. We encourage and support an act
Not sure what to work on? We have got some ideas. Please join the Ghostfolio [Slack channel](https://join.slack.com/t/ghostfolio/shared_invite/zt-vsaan64h-F_I0fEo5M0P88lP9ibCxFg), tweet to [@ghostfolio\_](https://twitter.com/ghostfolio_) or send an e-mail to hi@ghostfol.io. We would love to hear from you.
If you like to support this project, get **[Ghostfolio Premium](https://ghostfol.io/pricing)** or **[Buy me a coffee](https://www.buymeacoffee.com/ghostfolio)**.
## License
© 2022 [Ghostfolio](https://ghostfol.io)

View File

@ -52,9 +52,15 @@ export class InfoService {
}
if (this.configurationService.get('ENABLE_FEATURE_FEAR_AND_GREED_INDEX')) {
info.fearAndGreedDataSource = encodeDataSource(
ghostfolioFearAndGreedIndexDataSource
);
if (
this.configurationService.get('ENABLE_FEATURE_SUBSCRIPTION') === true
) {
info.fearAndGreedDataSource = encodeDataSource(
ghostfolioFearAndGreedIndexDataSource
);
} else {
info.fearAndGreedDataSource = ghostfolioFearAndGreedIndexDataSource;
}
}
if (this.configurationService.get('ENABLE_FEATURE_IMPORT')) {

View File

@ -12,4 +12,8 @@ export class UpdateUserSettingDto {
@IsString()
@IsOptional()
locale?: string;
@IsNumber()
@IsOptional()
savingsRate?: number;
}

View File

@ -20,7 +20,10 @@ import Big from 'big.js';
import { countries } from 'countries-list';
import { addDays, format, isSameDay } from 'date-fns';
import yahooFinance from 'yahoo-finance2';
import type { Price } from 'yahoo-finance2/dist/esm/src/modules/quoteSummary-iface';
import type {
Price,
QuoteSummaryResult
} from 'yahoo-finance2/dist/esm/src/modules/quoteSummary-iface';
@Injectable()
export class YahooFinanceService implements DataProviderInterface {
@ -89,8 +92,7 @@ export class YahooFinanceService implements DataProviderInterface {
response.assetSubClass = assetSubClass;
response.currency = assetProfile.price.currency;
response.dataSource = this.getName();
response.name =
assetProfile.price.longName || assetProfile.price.shortName || symbol;
response.name = this.formatName(assetProfile);
response.symbol = aSymbol;
if (
@ -296,6 +298,25 @@ export class YahooFinanceService implements DataProviderInterface {
return { items };
}
private formatName(aAssetProfile: QuoteSummaryResult) {
let name = aAssetProfile.price.longName;
if (name) {
name = name.replace('iShares ETF (CH) - ', '');
name = name.replace('iShares III Public Limited Company - ', '');
name = name.replace('iShares VI Public Limited Company - ', '');
name = name.replace('iShares VII PLC - ', '');
name = name.replace('Multi Units Luxembourg - ', '');
name = name.replace('VanEck ETFs N.V. - ', '');
name = name.replace('Vaneck Vectors Ucits Etfs Plc - ', '');
name = name.replace('Vanguard Funds Public Limited Company - ', '');
name = name.replace('Vanguard Index Funds - ', '');
name = name.replace('Xtrackers (IE) Plc - ', '');
}
return name || aAssetProfile.price.shortName || aAssetProfile.price.symbol;
}
private parseAssetClass(aPrice: Price): {
assetClass: AssetClass;
assetSubClass: AssetSubClass;

View File

@ -4,7 +4,12 @@ import { UNKNOWN_KEY } from '@ghostfolio/common/config';
import { Country } from '@ghostfolio/common/interfaces/country.interface';
import { Sector } from '@ghostfolio/common/interfaces/sector.interface';
import { Injectable } from '@nestjs/common';
import { DataSource, Prisma, SymbolProfile } from '@prisma/client';
import {
DataSource,
Prisma,
SymbolProfile,
SymbolProfileOverrides
} from '@prisma/client';
import { continents, countries } from 'countries-list';
import { ScraperConfiguration } from './data-provider/ghostfolio-scraper-api/interfaces/scraper-configuration.interface';
@ -36,6 +41,7 @@ export class SymbolProfileService {
): Promise<EnhancedSymbolProfile[]> {
return this.prismaService.symbolProfile
.findMany({
include: { SymbolProfileOverrides: true },
where: {
symbol: {
in: symbols
@ -45,14 +51,38 @@ export class SymbolProfileService {
.then((symbolProfiles) => this.getSymbols(symbolProfiles));
}
private getSymbols(symbolProfiles: SymbolProfile[]): EnhancedSymbolProfile[] {
return symbolProfiles.map((symbolProfile) => ({
...symbolProfile,
countries: this.getCountries(symbolProfile),
scraperConfiguration: this.getScraperConfiguration(symbolProfile),
sectors: this.getSectors(symbolProfile),
symbolMapping: this.getSymbolMapping(symbolProfile)
}));
private getSymbols(
symbolProfiles: (SymbolProfile & {
SymbolProfileOverrides: SymbolProfileOverrides;
})[]
): EnhancedSymbolProfile[] {
return symbolProfiles.map((symbolProfile) => {
const item = {
...symbolProfile,
countries: this.getCountries(symbolProfile),
scraperConfiguration: this.getScraperConfiguration(symbolProfile),
sectors: this.getSectors(symbolProfile),
symbolMapping: this.getSymbolMapping(symbolProfile)
};
if (item.SymbolProfileOverrides) {
item.assetClass =
item.SymbolProfileOverrides.assetClass ?? item.assetClass;
item.assetSubClass =
item.SymbolProfileOverrides.assetSubClass ?? item.assetSubClass;
item.countries =
(item.SymbolProfileOverrides.sectors as unknown as Country[]) ??
item.countries;
item.name = item.SymbolProfileOverrides?.name ?? item.name;
item.sectors =
(item.SymbolProfileOverrides.sectors as unknown as Sector[]) ??
item.sectors;
delete item.SymbolProfileOverrides;
}
return item;
});
}
private getCountries(symbolProfile: SymbolProfile): Country[] {

View File

@ -109,38 +109,39 @@
<mat-card-content>
<div class="row">
<div class="col-xs-12 col-md-4 my-2">
<h3 class="mb-0">{{ statistics?.activeUsers1d || '-' }}</h3>
<div class="h6 mb-0">
<span i18n>Active Users</span>&nbsp;<small class="text-muted"
>(Last 24 hours)</small
>
</div>
<gf-value
label="Active Users"
size="large"
subLabel="(Last 24 hours)"
[value]="statistics?.activeUsers1d ?? '-'"
></gf-value>
</div>
<div class="col-xs-12 col-md-4 my-2">
<h3 class="mb-0">{{ statistics?.newUsers30d ?? '-' }}</h3>
<div class="h6 mb-0">
<span i18n>New Users</span>&nbsp;<small class="text-muted"
>(Last 30 days)</small
>
</div>
<gf-value
label="New Users"
size="large"
subLabel="(Last 30 days)"
[value]="statistics?.newUsers30d ?? '-'"
></gf-value>
</div>
<div class="col-xs-12 col-md-4 my-2">
<h3 class="mb-0">{{ statistics?.activeUsers30d ?? '-' }}</h3>
<div class="h6 mb-0">
<span i18n>Active Users</span>&nbsp;<small class="text-muted"
>(Last 30 days)</small
>
</div>
<gf-value
label="Active Users"
size="large"
subLabel="(Last 30 days)"
[value]="statistics?.activeUsers30d ?? '-'"
></gf-value>
</div>
<div class="col-xs-12 col-md-4 my-2">
<a
class="d-block"
href="https://join.slack.com/t/ghostfolio/shared_invite/zt-vsaan64h-F_I0fEo5M0P88lP9ibCxFg"
>
<h3 class="mb-0">
{{ statistics?.slackCommunityUsers ?? '-' }}
</h3>
<div class="h6 mb-0" i18n>Users in Slack community</div>
<gf-value
label="Users in Slack community"
size="large"
[value]="statistics?.slackCommunityUsers ?? '-'"
></gf-value>
</a>
</div>
<div class="col-xs-12 col-md-4 my-2">
@ -148,10 +149,11 @@
class="d-block"
href="https://github.com/ghostfolio/ghostfolio/graphs/contributors"
>
<h3 class="mb-0">
{{ statistics?.gitHubContributors ?? '-' }}
</h3>
<div class="h6 mb-0" i18n>Contributors on GitHub</div>
<gf-value
label="Contributors on GitHub"
size="large"
[value]="statistics?.gitHubContributors ?? '-'"
></gf-value>
</a>
</div>
<div class="col-xs-12 col-md-4 my-2">
@ -159,8 +161,11 @@
class="d-block"
href="https://github.com/ghostfolio/ghostfolio/stargazers"
>
<h3 class="mb-0">{{ statistics?.gitHubStargazers ?? '-' }}</h3>
<div class="h6 mb-0" i18n>Stars on GitHub</div>
<gf-value
label="Stars on GitHub"
size="large"
[value]="statistics?.gitHubStargazers ?? '-'"
></gf-value>
</a>
</div>
</div>

View File

@ -2,6 +2,7 @@ import { CommonModule } from '@angular/common';
import { CUSTOM_ELEMENTS_SCHEMA, NgModule } from '@angular/core';
import { MatButtonModule } from '@angular/material/button';
import { MatCardModule } from '@angular/material/card';
import { GfValueModule } from '@ghostfolio/ui/value';
import { AboutPageRoutingModule } from './about-page-routing.module';
import { AboutPageComponent } from './about-page.component';
@ -12,6 +13,7 @@ import { AboutPageComponent } from './about-page.component';
imports: [
AboutPageRoutingModule,
CommonModule,
GfValueModule,
MatButtonModule,
MatCardModule
],

View File

@ -68,6 +68,13 @@ export class FirePageComponent implements OnDestroy, OnInit {
});
}
public onSavingsRateChange(savingsRate: number) {
this.dataService
.putUserSetting({ savingsRate })
.pipe(takeUntil(this.unsubscribeSubject))
.subscribe(() => {});
}
public ngOnDestroy() {
this.unsubscribeSubject.next();
this.unsubscribeSubject.complete();

View File

@ -41,7 +41,7 @@
[value]="withdrawalRatePerMonth?.toNumber()"
></gf-value>
per month</span
>, based on your investment of
>, based on your total assets of
<gf-value
class="d-inline-block"
[currency]="user?.settings?.baseCurrency"
@ -60,6 +60,8 @@
[deviceType]="deviceType"
[fireWealth]="fireWealth?.toNumber()"
[locale]="user?.settings?.locale"
[savingsRate]="user?.settings?.savingsRate"
(savingsRateChanged)="onSavingsRateChange($event)"
></gf-fire-calculator>
</div>
</div>

View File

@ -46,6 +46,7 @@ export class CreateOrUpdateTransactionDialog implements OnDestroy {
public filteredLookupItemsObservable: Observable<LookupItem[]>;
public isLoading = false;
public platforms: { id: string; name: string }[];
public total = 0;
public Validators = Validators;
private unsubscribeSubject = new Subject<void>();
@ -89,6 +90,25 @@ export class CreateOrUpdateTransactionDialog implements OnDestroy {
unitPrice: [this.data.activity?.unitPrice, Validators.required]
});
this.activityForm.valueChanges
.pipe(takeUntil(this.unsubscribeSubject))
.subscribe(() => {
if (
this.activityForm.controls['type'].value === 'BUY' ||
this.activityForm.controls['type'].value === 'ITEM'
) {
this.total =
this.activityForm.controls['quantity'].value *
this.activityForm.controls['unitPrice'].value +
this.activityForm.controls['fee'].value ?? 0;
} else {
this.total =
this.activityForm.controls['quantity'].value *
this.activityForm.controls['unitPrice'].value -
this.activityForm.controls['fee'].value ?? 0;
}
});
this.filteredLookupItemsObservable = this.activityForm.controls[
'searchSymbol'
].valueChanges.pipe(
@ -100,9 +120,11 @@ export class CreateOrUpdateTransactionDialog implements OnDestroy {
const filteredLookupItemsObservable =
this.dataService.fetchSymbols(query);
filteredLookupItemsObservable.subscribe((filteredLookupItems) => {
this.filteredLookupItems = filteredLookupItems;
});
filteredLookupItemsObservable
.pipe(takeUntil(this.unsubscribeSubject))
.subscribe((filteredLookupItems) => {
this.filteredLookupItems = filteredLookupItems;
});
return filteredLookupItemsObservable;
}
@ -111,45 +133,47 @@ export class CreateOrUpdateTransactionDialog implements OnDestroy {
})
);
this.activityForm.controls['type'].valueChanges.subscribe((type: Type) => {
if (type === 'ITEM') {
this.activityForm.controls['accountId'].removeValidators(
Validators.required
);
this.activityForm.controls['accountId'].updateValueAndValidity();
this.activityForm.controls['currency'].setValue(
this.data.user.settings.baseCurrency
);
this.activityForm.controls['dataSource'].removeValidators(
Validators.required
);
this.activityForm.controls['dataSource'].updateValueAndValidity();
this.activityForm.controls['name'].setValidators(Validators.required);
this.activityForm.controls['name'].updateValueAndValidity();
this.activityForm.controls['quantity'].setValue(1);
this.activityForm.controls['searchSymbol'].removeValidators(
Validators.required
);
this.activityForm.controls['searchSymbol'].updateValueAndValidity();
} else {
this.activityForm.controls['accountId'].setValidators(
Validators.required
);
this.activityForm.controls['accountId'].updateValueAndValidity();
this.activityForm.controls['dataSource'].setValidators(
Validators.required
);
this.activityForm.controls['dataSource'].updateValueAndValidity();
this.activityForm.controls['name'].removeValidators(
Validators.required
);
this.activityForm.controls['name'].updateValueAndValidity();
this.activityForm.controls['searchSymbol'].setValidators(
Validators.required
);
this.activityForm.controls['searchSymbol'].updateValueAndValidity();
}
});
this.activityForm.controls['type'].valueChanges
.pipe(takeUntil(this.unsubscribeSubject))
.subscribe((type: Type) => {
if (type === 'ITEM') {
this.activityForm.controls['accountId'].removeValidators(
Validators.required
);
this.activityForm.controls['accountId'].updateValueAndValidity();
this.activityForm.controls['currency'].setValue(
this.data.user.settings.baseCurrency
);
this.activityForm.controls['dataSource'].removeValidators(
Validators.required
);
this.activityForm.controls['dataSource'].updateValueAndValidity();
this.activityForm.controls['name'].setValidators(Validators.required);
this.activityForm.controls['name'].updateValueAndValidity();
this.activityForm.controls['quantity'].setValue(1);
this.activityForm.controls['searchSymbol'].removeValidators(
Validators.required
);
this.activityForm.controls['searchSymbol'].updateValueAndValidity();
} else {
this.activityForm.controls['accountId'].setValidators(
Validators.required
);
this.activityForm.controls['accountId'].updateValueAndValidity();
this.activityForm.controls['dataSource'].setValidators(
Validators.required
);
this.activityForm.controls['dataSource'].updateValueAndValidity();
this.activityForm.controls['name'].removeValidators(
Validators.required
);
this.activityForm.controls['name'].updateValueAndValidity();
this.activityForm.controls['searchSymbol'].setValidators(
Validators.required
);
this.activityForm.controls['searchSymbol'].updateValueAndValidity();
}
});
this.activityForm.controls['type'].setValue(this.data.activity?.type);

View File

@ -138,9 +138,9 @@
<div class="d-flex" mat-dialog-actions>
<gf-value
class="flex-grow-1"
[currency]="activityForm.controls['currency'].value"
[currency]="activityForm.controls['currency']?.value ?? data.user?.settings?.baseCurrency"
[locale]="data.user?.settings?.locale"
[value]="activityForm.controls['fee'].value + (activityForm.controls['quantity'].value * activityForm.controls['unitPrice'].value) ?? 0"
[value]="total"
></gf-value>
<div>
<button i18n mat-button type="button" (click)="onCancel()">Cancel</button>

View File

@ -181,7 +181,7 @@ export class TransactionsPageComponent implements OnDestroy, OnInit {
contentType: 'text/calendar',
fileName: `ghostfolio-draft${
data.activities.length > 1 ? 's' : ''
}-${format(parseISO(data.meta.date), 'yyyyMMddHHmm')}.ics`,
}-${format(parseISO(data.meta.date), 'yyyyMMddHHmmss')}.ics`,
format: 'string'
});
});

View File

@ -52,7 +52,6 @@ export class IcsService {
`UID:${id}`,
`DTSTAMP:${today}T000000`,
`DTSTART;VALUE=DATE:${format(date, this.ICS_DATE_FORMAT)}`,
`DTEND;VALUE=DATE:${format(date, this.ICS_DATE_FORMAT)}`,
`SUMMARY:${capitalize(type)} ${symbol}`,
'END:VEVENT'
].join(this.ICS_LINE_BREAK);

View File

@ -5,9 +5,11 @@ import {
ChangeDetectionStrategy,
ChangeDetectorRef,
Component,
EventEmitter,
Input,
OnChanges,
OnDestroy,
Output,
ViewChild
} from '@angular/core';
import { FormBuilder, FormControl } from '@angular/forms';
@ -40,6 +42,9 @@ export class FireCalculatorComponent
@Input() deviceType: string;
@Input() fireWealth: number;
@Input() locale: string;
@Input() savingsRate = 0;
@Output() savingsRateChanged = new EventEmitter<number>();
@ViewChild('chartCanvas') chartCanvas;
@ -73,7 +78,7 @@ export class FireCalculatorComponent
this.calculatorForm.setValue({
annualInterestRate: 5,
paymentPerPeriod: 500,
paymentPerPeriod: this.savingsRate,
principalInvestmentAmount: 0,
time: 10
});
@ -83,15 +88,28 @@ export class FireCalculatorComponent
.subscribe(() => {
this.initialize();
});
this.calculatorForm
.get('paymentPerPeriod')
.valueChanges.pipe(takeUntil(this.unsubscribeSubject))
.subscribe((savingsRate) => {
this.savingsRateChanged.emit(savingsRate);
});
}
public ngAfterViewInit() {
if (isNumber(this.fireWealth) && this.fireWealth >= 0) {
setTimeout(() => {
// Wait for the chartCanvas
this.calculatorForm.patchValue({
principalInvestmentAmount: this.fireWealth
});
this.calculatorForm.patchValue(
{
principalInvestmentAmount: this.fireWealth,
paymentPerPeriod: this.savingsRate ?? 0
},
{
emitEvent: false
}
);
this.calculatorForm.get('principalInvestmentAmount').disable();
this.changeDetectorRef.markForCheck();
@ -103,9 +121,15 @@ export class FireCalculatorComponent
if (isNumber(this.fireWealth) && this.fireWealth >= 0) {
setTimeout(() => {
// Wait for the chartCanvas
this.calculatorForm.patchValue({
principalInvestmentAmount: this.fireWealth
});
this.calculatorForm.patchValue(
{
principalInvestmentAmount: this.fireWealth,
paymentPerPeriod: this.savingsRate ?? 0
},
{
emitEvent: false
}
);
this.calculatorForm.get('principalInvestmentAmount').disable();
this.changeDetectorRef.markForCheck();
@ -128,8 +152,10 @@ export class FireCalculatorComponent
if (this.chartCanvas) {
if (this.chart) {
this.chart.data.labels = chartData.labels;
this.chart.data.datasets[0].data = chartData.datasets[0].data;
this.chart.data.datasets[1].data = chartData.datasets[1].data;
for (let index = 0; index < this.chart.data.datasets.length; index++) {
this.chart.data.datasets[index].data = chartData.datasets[index].data;
}
this.chart.update();
} else {
@ -138,7 +164,24 @@ export class FireCalculatorComponent
options: {
plugins: {
tooltip: {
itemSort: (a, b) => {
// Reverse order
return b.datasetIndex - a.datasetIndex;
},
mode: 'index',
callbacks: {
footer: (items) => {
const totalAmount = items.reduce(
(a, b) => a + b.parsed.y,
0
);
return `Total: ${new Intl.NumberFormat(this.locale, {
currency: this.currency,
currencyDisplay: 'code',
style: 'currency'
}).format(totalAmount)}`;
},
label: (context) => {
let label = context.dataset.label || '';

View File

@ -10,14 +10,14 @@
</ng-container>
<div
*ngIf="isPercent"
class="mb-0"
class="mb-0 value"
[ngClass]="{ h2: size === 'large', h4: size === 'medium' }"
>
{{ formattedValue }}%
</div>
<div
*ngIf="!isPercent"
class="mb-0"
class="mb-0 value"
[ngClass]="{ h2: size === 'large', h4: size === 'medium' }"
>
<ng-container *ngIf="value === null">
@ -36,7 +36,7 @@
</ng-container>
<ng-container *ngIf="isString">
<div
class="mb-0 text-truncate"
class="mb-0 text-truncate value"
[ngClass]="{ h2: size === 'large', h4: size === 'medium' }"
>
{{ formattedValue | titlecase }}
@ -45,7 +45,8 @@
</div>
<ng-container *ngIf="label">
<div *ngIf="size === 'large'">
{{ label }}
<span class="h6">{{ label }}</span>
<span *ngIf="subLabel" class="text-muted"> {{ subLabel }}</span>
</div>
<small *ngIf="size !== 'large'">
{{ label }}

View File

@ -2,4 +2,8 @@
display: flex;
flex-direction: column;
font-variant-numeric: tabular-nums;
.h2 {
line-height: 1;
}
}

View File

@ -25,6 +25,7 @@ export class ValueComponent implements OnChanges {
@Input() position = '';
@Input() precision: number | undefined;
@Input() size: 'large' | 'medium' | 'small' = 'small';
@Input() subLabel = '';
@Input() value: number | string = '';
public absoluteValue = 0;

View File

@ -1,6 +1,6 @@
{
"name": "ghostfolio",
"version": "1.138.0",
"version": "1.140.0",
"homepage": "https://ghostfol.io",
"license": "AGPL-3.0",
"scripts": {
@ -118,7 +118,7 @@
"tslib": "2.0.0",
"twitter-api-v2": "1.10.3",
"uuid": "8.3.2",
"yahoo-finance2": "2.3.0",
"yahoo-finance2": "2.3.1",
"zone.js": "0.11.4"
},
"devDependencies": {

View File

@ -0,0 +1,15 @@
-- CreateTable
CREATE TABLE "SymbolProfileOverrides" (
"assetClass" "AssetClass",
"assetSubClass" "AssetSubClass",
"countries" JSONB,
"name" TEXT,
"sectors" JSONB,
"symbolProfileId" TEXT NOT NULL,
"updatedAt" TIMESTAMP(3) NOT NULL,
CONSTRAINT "SymbolProfileOverrides_pkey" PRIMARY KEY ("symbolProfileId")
);
-- AddForeignKey
ALTER TABLE "SymbolProfileOverrides" ADD CONSTRAINT "SymbolProfileOverrides_symbolProfileId_fkey" FOREIGN KEY ("symbolProfileId") REFERENCES "SymbolProfile"("id") ON DELETE RESTRICT ON UPDATE CASCADE;

View File

@ -0,0 +1,2 @@
-- AlterEnum
ALTER TYPE "AssetSubClass" ADD VALUE 'COMMODITY';

View File

@ -112,25 +112,37 @@ model Settings {
}
model SymbolProfile {
assetClass AssetClass?
assetSubClass AssetSubClass?
countries Json?
createdAt DateTime @default(now())
currency String
dataSource DataSource
id String @id @default(uuid())
name String?
Order Order[]
updatedAt DateTime @updatedAt
scraperConfiguration Json?
sectors Json?
symbol String
symbolMapping Json?
url String?
assetClass AssetClass?
assetSubClass AssetSubClass?
countries Json?
createdAt DateTime @default(now())
currency String
dataSource DataSource
id String @id @default(uuid())
name String?
Order Order[]
updatedAt DateTime @updatedAt
scraperConfiguration Json?
sectors Json?
symbol String
symbolMapping Json?
SymbolProfileOverrides SymbolProfileOverrides?
url String?
@@unique([dataSource, symbol])
}
model SymbolProfileOverrides {
assetClass AssetClass?
assetSubClass AssetSubClass?
countries Json?
name String?
sectors Json?
SymbolProfile SymbolProfile @relation(fields: [symbolProfileId], references: [id])
symbolProfileId String @id
updatedAt DateTime @updatedAt
}
model Subscription {
createdAt DateTime @default(now())
expiresAt DateTime
@ -176,6 +188,7 @@ enum AssetClass {
enum AssetSubClass {
BOND
COMMODITY
CRYPTOCURRENCY
ETF
MUTUALFUND

View File

@ -18836,10 +18836,10 @@ y18n@^5.0.5:
resolved "https://registry.yarnpkg.com/y18n/-/y18n-5.0.8.tgz#7f4934d0f7ca8c56f95314939ddcd2dd91ce1d55"
integrity sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==
yahoo-finance2@2.3.0:
version "2.3.0"
resolved "https://registry.yarnpkg.com/yahoo-finance2/-/yahoo-finance2-2.3.0.tgz#81bd76732dfd38aa5d7019a97caf0f938c0127c2"
integrity sha512-7oj8n/WJH9MtX+q99WbHdjEVPdobTX8IyYjg7v4sDOh4f9ByT2Frxmp+Uj+rctrO0EiiD9QWTuwV4h8AemGuCg==
yahoo-finance2@2.3.1:
version "2.3.1"
resolved "https://registry.yarnpkg.com/yahoo-finance2/-/yahoo-finance2-2.3.1.tgz#d2cffbef78f6974e4e6a40487cc08ab133dc9fc5"
integrity sha512-QTXiiWgfrpVbSylchBgLqESZz+8+SyyDSqntjfZHxMIHa6d14xq+biNNDIeYd5SylcZ9Vt4zLmZXHN7EdLM1pA==
dependencies:
ajv "8.10.0"
ajv-formats "2.1.1"