Compare commits
10 Commits
Author | SHA1 | Date | |
---|---|---|---|
cc16ba5dc8 | |||
d10227bc39 | |||
4e214c32e8 | |||
49e2862e03 | |||
34e33a2400 | |||
ec9bc984af | |||
2388c494df | |||
d71ab10eed | |||
0e0592180f | |||
60e2aff488 |
27
CHANGELOG.md
27
CHANGELOG.md
@ -5,6 +5,33 @@ 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/),
|
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).
|
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
||||||
|
|
||||||
|
## 1.169.0 - 14.07.2022
|
||||||
|
|
||||||
|
### Added
|
||||||
|
|
||||||
|
- Added support for the cryptocurrency _Songbird_ (`SGB1-USD`)
|
||||||
|
- Added support for the cryptocurrency _Terra 2.0_ (`LUNA2-USD`)
|
||||||
|
- Added a blog post
|
||||||
|
|
||||||
|
### Changed
|
||||||
|
|
||||||
|
- Refreshed the cryptocurrencies list to support more coins by default
|
||||||
|
- Upgraded `date-fns` from version `2.22.1` to `2.28.0`
|
||||||
|
|
||||||
|
## 1.168.0 - 10.07.2022
|
||||||
|
|
||||||
|
### Added
|
||||||
|
|
||||||
|
- Extended the investment timeline grouped by month
|
||||||
|
|
||||||
|
### Changed
|
||||||
|
|
||||||
|
- Handled an occasional currency pair inconsistency in the _Yahoo Finance_ service (`GBP=X` instead of `USDGBP=X`)
|
||||||
|
|
||||||
|
### Fixed
|
||||||
|
|
||||||
|
- Fixed the content height of the account detail dialog
|
||||||
|
|
||||||
## 1.167.0 - 07.07.2022
|
## 1.167.0 - 07.07.2022
|
||||||
|
|
||||||
### Added
|
### Added
|
||||||
|
@ -14,8 +14,11 @@ import {
|
|||||||
format,
|
format,
|
||||||
isAfter,
|
isAfter,
|
||||||
isBefore,
|
isBefore,
|
||||||
|
isSameMonth,
|
||||||
|
isSameYear,
|
||||||
max,
|
max,
|
||||||
min
|
min,
|
||||||
|
set
|
||||||
} from 'date-fns';
|
} from 'date-fns';
|
||||||
import { first, flatten, isNumber, sortBy } from 'lodash';
|
import { first, flatten, isNumber, sortBy } from 'lodash';
|
||||||
|
|
||||||
@ -323,6 +326,46 @@ export class PortfolioCalculator {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public getInvestmentsByMonth(): { date: string; investment: Big }[] {
|
||||||
|
if (this.orders.length === 0) {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
|
const investments = [];
|
||||||
|
let currentDate = parseDate(this.orders[0].date);
|
||||||
|
let investmentByMonth = new Big(0);
|
||||||
|
|
||||||
|
for (const [index, order] of this.orders.entries()) {
|
||||||
|
if (
|
||||||
|
isSameMonth(parseDate(order.date), currentDate) &&
|
||||||
|
isSameYear(parseDate(order.date), currentDate)
|
||||||
|
) {
|
||||||
|
investmentByMonth = investmentByMonth.plus(
|
||||||
|
order.quantity.mul(order.unitPrice).mul(this.getFactor(order.type))
|
||||||
|
);
|
||||||
|
|
||||||
|
if (index === this.orders.length - 1) {
|
||||||
|
investments.push({
|
||||||
|
date: format(set(currentDate, { date: 1 }), DATE_FORMAT),
|
||||||
|
investment: investmentByMonth
|
||||||
|
});
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
investments.push({
|
||||||
|
date: format(set(currentDate, { date: 1 }), DATE_FORMAT),
|
||||||
|
investment: investmentByMonth
|
||||||
|
});
|
||||||
|
|
||||||
|
currentDate = parseDate(order.date);
|
||||||
|
investmentByMonth = order.quantity
|
||||||
|
.mul(order.unitPrice)
|
||||||
|
.mul(this.getFactor(order.type));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return investments;
|
||||||
|
}
|
||||||
|
|
||||||
public async calculateTimeline(
|
public async calculateTimeline(
|
||||||
timelineSpecification: TimelineSpecification[],
|
timelineSpecification: TimelineSpecification[],
|
||||||
endDate: string
|
endDate: string
|
||||||
|
@ -20,7 +20,12 @@ import {
|
|||||||
PortfolioReport,
|
PortfolioReport,
|
||||||
PortfolioSummary
|
PortfolioSummary
|
||||||
} from '@ghostfolio/common/interfaces';
|
} from '@ghostfolio/common/interfaces';
|
||||||
import type { DateRange, RequestWithUser } from '@ghostfolio/common/types';
|
import { InvestmentItem } from '@ghostfolio/common/interfaces/investment-item.interface';
|
||||||
|
import type {
|
||||||
|
DateRange,
|
||||||
|
GroupBy,
|
||||||
|
RequestWithUser
|
||||||
|
} from '@ghostfolio/common/types';
|
||||||
import {
|
import {
|
||||||
Controller,
|
Controller,
|
||||||
Get,
|
Get,
|
||||||
@ -217,7 +222,8 @@ export class PortfolioController {
|
|||||||
@Get('investments')
|
@Get('investments')
|
||||||
@UseGuards(AuthGuard('jwt'))
|
@UseGuards(AuthGuard('jwt'))
|
||||||
public async getInvestments(
|
public async getInvestments(
|
||||||
@Headers('impersonation-id') impersonationId: string
|
@Headers('impersonation-id') impersonationId: string,
|
||||||
|
@Query('groupBy') groupBy?: GroupBy
|
||||||
): Promise<PortfolioInvestments> {
|
): Promise<PortfolioInvestments> {
|
||||||
if (
|
if (
|
||||||
this.configurationService.get('ENABLE_FEATURE_SUBSCRIPTION') &&
|
this.configurationService.get('ENABLE_FEATURE_SUBSCRIPTION') &&
|
||||||
@ -229,9 +235,16 @@ export class PortfolioController {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
let investments = await this.portfolioService.getInvestments(
|
let investments: InvestmentItem[];
|
||||||
impersonationId
|
|
||||||
);
|
if (groupBy === 'month') {
|
||||||
|
investments = await this.portfolioService.getInvestments(
|
||||||
|
impersonationId,
|
||||||
|
'month'
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
investments = await this.portfolioService.getInvestments(impersonationId);
|
||||||
|
}
|
||||||
|
|
||||||
if (
|
if (
|
||||||
impersonationId ||
|
impersonationId ||
|
||||||
|
@ -41,6 +41,7 @@ import { InvestmentItem } from '@ghostfolio/common/interfaces/investment-item.in
|
|||||||
import type {
|
import type {
|
||||||
AccountWithValue,
|
AccountWithValue,
|
||||||
DateRange,
|
DateRange,
|
||||||
|
GroupBy,
|
||||||
Market,
|
Market,
|
||||||
OrderWithAccount,
|
OrderWithAccount,
|
||||||
RequestWithUser
|
RequestWithUser
|
||||||
@ -64,6 +65,7 @@ import {
|
|||||||
max,
|
max,
|
||||||
parse,
|
parse,
|
||||||
parseISO,
|
parseISO,
|
||||||
|
set,
|
||||||
setDayOfYear,
|
setDayOfYear,
|
||||||
startOfDay,
|
startOfDay,
|
||||||
subDays,
|
subDays,
|
||||||
@ -183,7 +185,8 @@ export class PortfolioService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public async getInvestments(
|
public async getInvestments(
|
||||||
aImpersonationId: string
|
aImpersonationId: string,
|
||||||
|
groupBy?: GroupBy
|
||||||
): Promise<InvestmentItem[]> {
|
): Promise<InvestmentItem[]> {
|
||||||
const userId = await this.getUserId(aImpersonationId, this.request.user.id);
|
const userId = await this.getUserId(aImpersonationId, this.request.user.id);
|
||||||
|
|
||||||
@ -204,28 +207,57 @@ export class PortfolioService {
|
|||||||
return [];
|
return [];
|
||||||
}
|
}
|
||||||
|
|
||||||
const investments = portfolioCalculator.getInvestments().map((item) => {
|
let investments: InvestmentItem[];
|
||||||
return {
|
|
||||||
date: item.date,
|
|
||||||
investment: item.investment.toNumber()
|
|
||||||
};
|
|
||||||
});
|
|
||||||
|
|
||||||
// Add investment of today
|
if (groupBy === 'month') {
|
||||||
const investmentOfToday = investments.filter((investment) => {
|
investments = portfolioCalculator.getInvestmentsByMonth().map((item) => {
|
||||||
return investment.date === format(new Date(), DATE_FORMAT);
|
return {
|
||||||
});
|
date: item.date,
|
||||||
|
investment: item.investment.toNumber()
|
||||||
if (investmentOfToday.length <= 0) {
|
};
|
||||||
const pastInvestments = investments.filter((investment) => {
|
|
||||||
return isBefore(parseDate(investment.date), new Date());
|
|
||||||
});
|
});
|
||||||
const lastInvestment = pastInvestments[pastInvestments.length - 1];
|
|
||||||
|
|
||||||
investments.push({
|
// Add investment of current month
|
||||||
date: format(new Date(), DATE_FORMAT),
|
const dateOfCurrentMonth = format(
|
||||||
investment: lastInvestment?.investment ?? 0
|
set(new Date(), { date: 1 }),
|
||||||
|
DATE_FORMAT
|
||||||
|
);
|
||||||
|
const investmentOfCurrentMonth = investments.filter(({ date }) => {
|
||||||
|
return date === dateOfCurrentMonth;
|
||||||
});
|
});
|
||||||
|
|
||||||
|
if (investmentOfCurrentMonth.length <= 0) {
|
||||||
|
investments.push({
|
||||||
|
date: dateOfCurrentMonth,
|
||||||
|
investment: 0
|
||||||
|
});
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
investments = portfolioCalculator
|
||||||
|
.getInvestments()
|
||||||
|
.map(({ date, investment }) => {
|
||||||
|
return {
|
||||||
|
date,
|
||||||
|
investment: investment.toNumber()
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
// Add investment of today
|
||||||
|
const investmentOfToday = investments.filter(({ date }) => {
|
||||||
|
return date === format(new Date(), DATE_FORMAT);
|
||||||
|
});
|
||||||
|
|
||||||
|
if (investmentOfToday.length <= 0) {
|
||||||
|
const pastInvestments = investments.filter(({ date }) => {
|
||||||
|
return isBefore(parseDate(date), new Date());
|
||||||
|
});
|
||||||
|
const lastInvestment = pastInvestments[pastInvestments.length - 1];
|
||||||
|
|
||||||
|
investments.push({
|
||||||
|
date: format(new Date(), DATE_FORMAT),
|
||||||
|
investment: lastInvestment?.investment ?? 0
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return sortBy(investments, (investment) => {
|
return sortBy(investments, (investment) => {
|
||||||
|
File diff suppressed because it is too large
Load Diff
@ -1,4 +1,6 @@
|
|||||||
{
|
{
|
||||||
"LUNA1": "Terra",
|
"LUNA1": "Terra",
|
||||||
|
"LUNA2": "Terra",
|
||||||
|
"SGB1": "Songbird",
|
||||||
"UNI1": "Uniswap"
|
"UNI1": "Uniswap"
|
||||||
}
|
}
|
||||||
|
@ -37,10 +37,15 @@ export class YahooFinanceService implements DataProviderInterface {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public convertFromYahooFinanceSymbol(aYahooFinanceSymbol: string) {
|
public convertFromYahooFinanceSymbol(aYahooFinanceSymbol: string) {
|
||||||
const symbol = aYahooFinanceSymbol.replace(
|
let symbol = aYahooFinanceSymbol.replace(
|
||||||
new RegExp(`-${this.baseCurrency}$`),
|
new RegExp(`-${this.baseCurrency}$`),
|
||||||
this.baseCurrency
|
this.baseCurrency
|
||||||
);
|
);
|
||||||
|
|
||||||
|
if (symbol.includes('=X') && !symbol.includes(this.baseCurrency)) {
|
||||||
|
symbol = `${this.baseCurrency}${symbol}`;
|
||||||
|
}
|
||||||
|
|
||||||
return symbol.replace('=X', '');
|
return symbol.replace('=X', '');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -78,6 +78,13 @@ const routes: Routes = [
|
|||||||
'./pages/blog/2022/01/first-months-in-open-source/first-months-in-open-source-page.module'
|
'./pages/blog/2022/01/first-months-in-open-source/first-months-in-open-source-page.module'
|
||||||
).then((m) => m.FirstMonthsInOpenSourcePageModule)
|
).then((m) => m.FirstMonthsInOpenSourcePageModule)
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
path: 'en/blog/2022/07/how-do-i-get-my-finances-in-order',
|
||||||
|
loadChildren: () =>
|
||||||
|
import(
|
||||||
|
'./pages/blog/2022/07/how-do-i-get-my-finances-in-order/how-do-i-get-my-finances-in-order-page.module'
|
||||||
|
).then((m) => m.HowDoIGetMyFinancesInOrderPageModule)
|
||||||
|
},
|
||||||
{
|
{
|
||||||
path: 'features',
|
path: 'features',
|
||||||
loadChildren: () =>
|
loadChildren: () =>
|
||||||
|
@ -1,3 +1,7 @@
|
|||||||
:host {
|
:host {
|
||||||
display: block;
|
display: block;
|
||||||
|
|
||||||
|
.mat-dialog-content {
|
||||||
|
max-height: unset;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -22,7 +22,10 @@ import {
|
|||||||
transformTickToAbbreviation
|
transformTickToAbbreviation
|
||||||
} from '@ghostfolio/common/helper';
|
} from '@ghostfolio/common/helper';
|
||||||
import { InvestmentItem } from '@ghostfolio/common/interfaces/investment-item.interface';
|
import { InvestmentItem } from '@ghostfolio/common/interfaces/investment-item.interface';
|
||||||
|
import { GroupBy } from '@ghostfolio/common/types';
|
||||||
import {
|
import {
|
||||||
|
BarController,
|
||||||
|
BarElement,
|
||||||
Chart,
|
Chart,
|
||||||
LineController,
|
LineController,
|
||||||
LineElement,
|
LineElement,
|
||||||
@ -42,6 +45,7 @@ import { addDays, isAfter, parseISO, subDays } from 'date-fns';
|
|||||||
export class InvestmentChartComponent implements OnChanges, OnDestroy {
|
export class InvestmentChartComponent implements OnChanges, OnDestroy {
|
||||||
@Input() currency: string;
|
@Input() currency: string;
|
||||||
@Input() daysInMarket: number;
|
@Input() daysInMarket: number;
|
||||||
|
@Input() groupBy: GroupBy;
|
||||||
@Input() investments: InvestmentItem[];
|
@Input() investments: InvestmentItem[];
|
||||||
@Input() isInPercent = false;
|
@Input() isInPercent = false;
|
||||||
@Input() locale: string;
|
@Input() locale: string;
|
||||||
@ -53,6 +57,8 @@ export class InvestmentChartComponent implements OnChanges, OnDestroy {
|
|||||||
|
|
||||||
public constructor() {
|
public constructor() {
|
||||||
Chart.register(
|
Chart.register(
|
||||||
|
BarController,
|
||||||
|
BarElement,
|
||||||
LinearScale,
|
LinearScale,
|
||||||
LineController,
|
LineController,
|
||||||
LineElement,
|
LineElement,
|
||||||
@ -78,7 +84,7 @@ export class InvestmentChartComponent implements OnChanges, OnDestroy {
|
|||||||
private initialize() {
|
private initialize() {
|
||||||
this.isLoading = true;
|
this.isLoading = true;
|
||||||
|
|
||||||
if (this.investments?.length > 0) {
|
if (!this.groupBy && this.investments?.length > 0) {
|
||||||
// Extend chart by 5% of days in market (before)
|
// Extend chart by 5% of days in market (before)
|
||||||
const firstItem = this.investments[0];
|
const firstItem = this.investments[0];
|
||||||
this.investments.unshift({
|
this.investments.unshift({
|
||||||
@ -102,13 +108,14 @@ export class InvestmentChartComponent implements OnChanges, OnDestroy {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const data = {
|
const data = {
|
||||||
labels: this.investments.map((position) => {
|
labels: this.investments.map((investmentItem) => {
|
||||||
return position.date;
|
return investmentItem.date;
|
||||||
}),
|
}),
|
||||||
datasets: [
|
datasets: [
|
||||||
{
|
{
|
||||||
|
backgroundColor: `rgb(${primaryColorRgb.r}, ${primaryColorRgb.g}, ${primaryColorRgb.b})`,
|
||||||
borderColor: `rgb(${primaryColorRgb.r}, ${primaryColorRgb.g}, ${primaryColorRgb.b})`,
|
borderColor: `rgb(${primaryColorRgb.r}, ${primaryColorRgb.g}, ${primaryColorRgb.b})`,
|
||||||
borderWidth: 2,
|
borderWidth: this.groupBy ? 0 : 2,
|
||||||
data: this.investments.map((position) => {
|
data: this.investments.map((position) => {
|
||||||
return position.investment;
|
return position.investment;
|
||||||
}),
|
}),
|
||||||
@ -137,6 +144,7 @@ export class InvestmentChartComponent implements OnChanges, OnDestroy {
|
|||||||
this.chart = new Chart(this.chartCanvas.nativeElement, {
|
this.chart = new Chart(this.chartCanvas.nativeElement, {
|
||||||
data,
|
data,
|
||||||
options: {
|
options: {
|
||||||
|
animation: false,
|
||||||
elements: {
|
elements: {
|
||||||
line: {
|
line: {
|
||||||
tension: 0
|
tension: 0
|
||||||
@ -178,8 +186,10 @@ export class InvestmentChartComponent implements OnChanges, OnDestroy {
|
|||||||
grid: {
|
grid: {
|
||||||
borderColor: `rgba(${getTextColor()}, 0.1)`,
|
borderColor: `rgba(${getTextColor()}, 0.1)`,
|
||||||
color: `rgba(${getTextColor()}, 0.8)`,
|
color: `rgba(${getTextColor()}, 0.8)`,
|
||||||
display: false
|
display: false,
|
||||||
|
drawBorder: false
|
||||||
},
|
},
|
||||||
|
position: 'right',
|
||||||
ticks: {
|
ticks: {
|
||||||
callback: (value: number) => {
|
callback: (value: number) => {
|
||||||
return transformTickToAbbreviation(value);
|
return transformTickToAbbreviation(value);
|
||||||
@ -192,12 +202,12 @@ export class InvestmentChartComponent implements OnChanges, OnDestroy {
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
plugins: [getVerticalHoverLinePlugin(this.chartCanvas)],
|
plugins: [getVerticalHoverLinePlugin(this.chartCanvas)],
|
||||||
type: 'line'
|
type: this.groupBy ? 'bar' : 'line'
|
||||||
});
|
});
|
||||||
|
|
||||||
this.isLoading = false;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
this.isLoading = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
private getTooltipPluginConfiguration() {
|
private getTooltipPluginConfiguration() {
|
||||||
|
@ -3,7 +3,7 @@
|
|||||||
<div class="col-md-8 offset-md-2">
|
<div class="col-md-8 offset-md-2">
|
||||||
<article>
|
<article>
|
||||||
<div class="mb-4 text-center">
|
<div class="mb-4 text-center">
|
||||||
<h1 class="mb-1" i18n>Hallo Ghostfolio 👋</h1>
|
<h1 class="mb-1">Hallo Ghostfolio 👋</h1>
|
||||||
<div class="text-muted"><small>31.07.2021</small></div>
|
<div class="text-muted"><small>31.07.2021</small></div>
|
||||||
</div>
|
</div>
|
||||||
<section class="mb-4">
|
<section class="mb-4">
|
||||||
|
@ -3,7 +3,7 @@
|
|||||||
<div class="col-md-8 offset-md-2">
|
<div class="col-md-8 offset-md-2">
|
||||||
<article>
|
<article>
|
||||||
<div class="mb-4 text-center">
|
<div class="mb-4 text-center">
|
||||||
<h1 class="mb-1" i18n>Hello Ghostfolio 👋</h1>
|
<h1 class="mb-1">Hello Ghostfolio 👋</h1>
|
||||||
<div class="text-muted"><small>31.07.2021</small></div>
|
<div class="text-muted"><small>31.07.2021</small></div>
|
||||||
</div>
|
</div>
|
||||||
<section class="mb-4">
|
<section class="mb-4">
|
||||||
|
@ -3,7 +3,7 @@
|
|||||||
<div class="col-md-8 offset-md-2">
|
<div class="col-md-8 offset-md-2">
|
||||||
<article>
|
<article>
|
||||||
<div class="mb-4 text-center">
|
<div class="mb-4 text-center">
|
||||||
<h1 class="mb-1" i18n>
|
<h1 class="mb-1">
|
||||||
👻 Ghostfolio –
|
👻 Ghostfolio –
|
||||||
<span class="text-nowrap">First months in Open Source</span>
|
<span class="text-nowrap">First months in Open Source</span>
|
||||||
</h1>
|
</h1>
|
||||||
|
@ -0,0 +1,19 @@
|
|||||||
|
import { NgModule } from '@angular/core';
|
||||||
|
import { RouterModule, Routes } from '@angular/router';
|
||||||
|
import { AuthGuard } from '@ghostfolio/client/core/auth.guard';
|
||||||
|
|
||||||
|
import { HowDoIGetMyFinancesInOrderPageComponent } from './how-do-i-get-my-finances-in-order-page.component';
|
||||||
|
|
||||||
|
const routes: Routes = [
|
||||||
|
{
|
||||||
|
path: '',
|
||||||
|
component: HowDoIGetMyFinancesInOrderPageComponent,
|
||||||
|
canActivate: [AuthGuard]
|
||||||
|
}
|
||||||
|
];
|
||||||
|
|
||||||
|
@NgModule({
|
||||||
|
imports: [RouterModule.forChild(routes)],
|
||||||
|
exports: [RouterModule]
|
||||||
|
})
|
||||||
|
export class HowDoIGetMyFinancesInOrderRoutingModule {}
|
@ -0,0 +1,9 @@
|
|||||||
|
import { Component } from '@angular/core';
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
host: { class: 'page' },
|
||||||
|
selector: 'gf-how-do-i-get-my-finances-in-order-page',
|
||||||
|
styleUrls: ['./how-do-i-get-my-finances-in-order-page.scss'],
|
||||||
|
templateUrl: './how-do-i-get-my-finances-in-order-page.html'
|
||||||
|
})
|
||||||
|
export class HowDoIGetMyFinancesInOrderPageComponent {}
|
@ -0,0 +1,206 @@
|
|||||||
|
<div class="blog container">
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-md-8 offset-md-2">
|
||||||
|
<article>
|
||||||
|
<div class="mb-4 text-center">
|
||||||
|
<h1 class="mb-1">How do I get my finances in order?</h1>
|
||||||
|
<div class="text-muted"><small>14.07.2022</small></div>
|
||||||
|
</div>
|
||||||
|
<section class="mb-4">
|
||||||
|
<p>
|
||||||
|
Before you can think of
|
||||||
|
<a [routerLink]="['/resources']">long-term investing</a>, you need
|
||||||
|
to have your finances in order. Take a look at Peter's journey to
|
||||||
|
see how you can achieve it, too.
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
Peter enjoys life, but sometimes he overspends a bit. He realizes it
|
||||||
|
when money runs out already in the middle of the month. Then the
|
||||||
|
next few days become difficult and saving money is out of the
|
||||||
|
question. That is why he wants to plan his monthly budget in the
|
||||||
|
future.
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
Peter has a decent salary in his job. But as soon as the salary
|
||||||
|
arrives in his account, it melts away. In order to find out where
|
||||||
|
his money is disappearing, he has decided to plan his monthly
|
||||||
|
budget. He wants to be able to put money aside for major expenses
|
||||||
|
and set financial goals.
|
||||||
|
</p>
|
||||||
|
</section>
|
||||||
|
<section class="mb-4">
|
||||||
|
<h2 class="h4">Keeping a traditional or digital budget book</h2>
|
||||||
|
<p>
|
||||||
|
First, Peter obtains an overview of his personal finances. To do so,
|
||||||
|
he starts keeping a budget book. This can be done on paper by
|
||||||
|
listing his income and expenses for a few months, or he can create a
|
||||||
|
simple spreadsheet in Excel. In addition, many credit card providers
|
||||||
|
offer the feature within their apps of having expenses automatically
|
||||||
|
analyzed according to different categories. According to the
|
||||||
|
<a href="https://www.bfs.admin.ch/bfs/en/home.html"
|
||||||
|
>Swiss Federal Statistical Office</a
|
||||||
|
>, households in Switzerland spend around 20 percent of their
|
||||||
|
disposable income on housing and around 10 percent on groceries.
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
With the smartphone app, Peter has a better overview of his
|
||||||
|
financial affairs. The application assigns the bookings to
|
||||||
|
individual categories. Peter can assign specific budgets to each of
|
||||||
|
them. This way, he is always informed about how much money he can
|
||||||
|
still spend on restaurant visits in the current month, for example.
|
||||||
|
A traditional method is the so-called
|
||||||
|
<a
|
||||||
|
href="https://www.investopedia.com/envelope-budgeting-system-5208026"
|
||||||
|
>envelope method</a
|
||||||
|
>. One envelope is labeled for each category like groceries, rent or
|
||||||
|
student loans. The monthly budget is put into the envelopes in cash.
|
||||||
|
Many apps offer the same budgeting system in a more convenient,
|
||||||
|
virtual way.
|
||||||
|
</p>
|
||||||
|
</section>
|
||||||
|
<section class="mb-4">
|
||||||
|
<h2 class="h4">Planning and investing</h2>
|
||||||
|
<p>
|
||||||
|
If Peter has spent less money than planned on eating out at
|
||||||
|
restaurants, he can set aside the remaining amount. This way, he can
|
||||||
|
treat himself to something special every now and then. From now on,
|
||||||
|
he saves a fixed amount of money in a separate account ("pay
|
||||||
|
yourself first") by standing order at the beginning of the month. As
|
||||||
|
soon as there are three net monthly salaries in the account, he
|
||||||
|
invests the monthly savings amount in a passively managed global
|
||||||
|
equity fund. This grows his assets over the years and allows him to
|
||||||
|
supplement his pension later.
|
||||||
|
</p>
|
||||||
|
</section>
|
||||||
|
<section class="mb-4">
|
||||||
|
<h2 class="h4">How to achieve your financial goals?</h2>
|
||||||
|
<p>
|
||||||
|
If you follow these five actionable tips, you can reach your
|
||||||
|
financial goals easier and faster. Start with one tip and when you
|
||||||
|
implement it well, you can try the next one to ultimately have more
|
||||||
|
money at the end of the month.
|
||||||
|
</p>
|
||||||
|
<h3 class="h5">1. Visualize your goals</h3>
|
||||||
|
<p>
|
||||||
|
Start visualizing your goals. For example, hang up pictures of the
|
||||||
|
travel destination you are saving for. Imagine that you have already
|
||||||
|
achieved the goal to slowly adapt your mindset.
|
||||||
|
</p>
|
||||||
|
<h3 class="h5">2. Write off personal items</h3>
|
||||||
|
<p>
|
||||||
|
Do as a business does and write off purchases annually. For a new
|
||||||
|
car, you could set aside one-sixth of the purchase price each year.
|
||||||
|
</p>
|
||||||
|
<h3 class="h5">3. Save at the beginning of the month</h3>
|
||||||
|
<p>
|
||||||
|
Have a savings amount deducted from your account at the beginning of
|
||||||
|
the month. Then you will pay yourself first and spend less money.
|
||||||
|
</p>
|
||||||
|
<h3 class="h5">4. Follow the 50-30-20 rule</h3>
|
||||||
|
<p>
|
||||||
|
You need 50 percent of your disposable income for fixed costs. 30
|
||||||
|
percent can be spent on personal needs such as hobbies, travel or
|
||||||
|
consumer electronics. 20 percent is left for savings or to pay off
|
||||||
|
potential debts.
|
||||||
|
</p>
|
||||||
|
<h3 class="h5">5. Track your progress</h3>
|
||||||
|
<p>
|
||||||
|
If you have any money to spare, invest it in a broadly diversified,
|
||||||
|
low-cost portfolio excluding the risks of individual stocks. Track
|
||||||
|
the progress of your portfolio and net worth with
|
||||||
|
<a href="https://ghostfol.io">Ghostfolio</a>, a web-based personal
|
||||||
|
finance management software.
|
||||||
|
</p>
|
||||||
|
</section>
|
||||||
|
<section class="mb-4">
|
||||||
|
<ul class="list-inline">
|
||||||
|
<li class="list-inline-item">
|
||||||
|
<span class="badge badge-light">App</span>
|
||||||
|
</li>
|
||||||
|
<li class="list-inline-item">
|
||||||
|
<span class="badge badge-light">Assets</span>
|
||||||
|
</li>
|
||||||
|
<li class="list-inline-item">
|
||||||
|
<span class="badge badge-light">Budget</span>
|
||||||
|
</li>
|
||||||
|
<li class="list-inline-item">
|
||||||
|
<span class="badge badge-light">Cash</span>
|
||||||
|
</li>
|
||||||
|
<li class="list-inline-item">
|
||||||
|
<span class="badge badge-light">Debt</span>
|
||||||
|
</li>
|
||||||
|
<li class="list-inline-item">
|
||||||
|
<span class="badge badge-light">Equity</span>
|
||||||
|
</li>
|
||||||
|
<li class="list-inline-item">
|
||||||
|
<span class="badge badge-light">ETF</span>
|
||||||
|
</li>
|
||||||
|
<li class="list-inline-item">
|
||||||
|
<span class="badge badge-light">Expense</span>
|
||||||
|
</li>
|
||||||
|
<li class="list-inline-item">
|
||||||
|
<span class="badge badge-light">Finance</span>
|
||||||
|
</li>
|
||||||
|
<li class="list-inline-item">
|
||||||
|
<span class="badge badge-light">Fintech</span>
|
||||||
|
</li>
|
||||||
|
<li class="list-inline-item">
|
||||||
|
<span class="badge badge-light">Fund</span>
|
||||||
|
</li>
|
||||||
|
<li class="list-inline-item">
|
||||||
|
<span class="badge badge-light">Ghostfolio</span>
|
||||||
|
</li>
|
||||||
|
<li class="list-inline-item">
|
||||||
|
<span class="badge badge-light">Goal</span>
|
||||||
|
</li>
|
||||||
|
<li class="list-inline-item">
|
||||||
|
<span class="badge badge-light">Income</span>
|
||||||
|
</li>
|
||||||
|
<li class="list-inline-item">
|
||||||
|
<span class="badge badge-light">Investment</span>
|
||||||
|
</li>
|
||||||
|
<li class="list-inline-item">
|
||||||
|
<span class="badge badge-light">Money</span>
|
||||||
|
</li>
|
||||||
|
<li class="list-inline-item">
|
||||||
|
<span class="badge badge-light">Net Worth</span>
|
||||||
|
</li>
|
||||||
|
<li class="list-inline-item">
|
||||||
|
<span class="badge badge-light">Pension</span>
|
||||||
|
</li>
|
||||||
|
<li class="list-inline-item">
|
||||||
|
<span class="badge badge-light">Personal Finance</span>
|
||||||
|
</li>
|
||||||
|
<li class="list-inline-item">
|
||||||
|
<span class="badge badge-light">Planning</span>
|
||||||
|
</li>
|
||||||
|
<li class="list-inline-item">
|
||||||
|
<span class="badge badge-light">Portfolio</span>
|
||||||
|
</li>
|
||||||
|
<li class="list-inline-item">
|
||||||
|
<span class="badge badge-light">Salary</span>
|
||||||
|
</li>
|
||||||
|
<li class="list-inline-item">
|
||||||
|
<span class="badge badge-light">Saving</span>
|
||||||
|
</li>
|
||||||
|
<li class="list-inline-item">
|
||||||
|
<span class="badge badge-light">Software</span>
|
||||||
|
</li>
|
||||||
|
<li class="list-inline-item">
|
||||||
|
<span class="badge badge-light">Spreadsheet</span>
|
||||||
|
</li>
|
||||||
|
<li class="list-inline-item">
|
||||||
|
<span class="badge badge-light">Stock</span>
|
||||||
|
</li>
|
||||||
|
<li class="list-inline-item">
|
||||||
|
<span class="badge badge-light">Strategy</span>
|
||||||
|
</li>
|
||||||
|
<li class="list-inline-item">
|
||||||
|
<span class="badge badge-light">Wealth Management</span>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</section>
|
||||||
|
</article>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
@ -0,0 +1,17 @@
|
|||||||
|
import { CommonModule } from '@angular/common';
|
||||||
|
import { CUSTOM_ELEMENTS_SCHEMA, NgModule } from '@angular/core';
|
||||||
|
import { RouterModule } from '@angular/router';
|
||||||
|
|
||||||
|
import { HowDoIGetMyFinancesInOrderRoutingModule } from './how-do-i-get-my-finances-in-order-page-routing.module';
|
||||||
|
import { HowDoIGetMyFinancesInOrderPageComponent } from './how-do-i-get-my-finances-in-order-page.component';
|
||||||
|
|
||||||
|
@NgModule({
|
||||||
|
declarations: [HowDoIGetMyFinancesInOrderPageComponent],
|
||||||
|
imports: [
|
||||||
|
CommonModule,
|
||||||
|
HowDoIGetMyFinancesInOrderRoutingModule,
|
||||||
|
RouterModule
|
||||||
|
],
|
||||||
|
schemas: [CUSTOM_ELEMENTS_SCHEMA]
|
||||||
|
})
|
||||||
|
export class HowDoIGetMyFinancesInOrderPageModule {}
|
@ -0,0 +1,3 @@
|
|||||||
|
:host {
|
||||||
|
display: block;
|
||||||
|
}
|
@ -2,10 +2,36 @@
|
|||||||
<div class="mb-5 row">
|
<div class="mb-5 row">
|
||||||
<div class="col">
|
<div class="col">
|
||||||
<h3 class="mb-3 text-center" i18n>Blog</h3>
|
<h3 class="mb-3 text-center" i18n>Blog</h3>
|
||||||
<mat-card class="blog-container">
|
<mat-card class="mb-3">
|
||||||
<mat-card-content>
|
<mat-card-content>
|
||||||
<div class="container p-0">
|
<div class="container p-0">
|
||||||
<div class="flex-nowrap mb-3 no-gutters row">
|
<div class="flex-nowrap no-gutters row">
|
||||||
|
<a
|
||||||
|
class="d-flex w-100"
|
||||||
|
[routerLink]="['/en', 'blog', '2022', '07', 'how-do-i-get-my-finances-in-order']"
|
||||||
|
>
|
||||||
|
<div class="flex-grow-1">
|
||||||
|
<div class="h6 m-0 text-truncate">
|
||||||
|
How do I get my finances in order?
|
||||||
|
</div>
|
||||||
|
<div class="d-flex text-muted">14.07.2022</div>
|
||||||
|
</div>
|
||||||
|
<div class="align-items-center d-flex">
|
||||||
|
<ion-icon
|
||||||
|
class="chevron text-muted"
|
||||||
|
name="chevron-forward-outline"
|
||||||
|
size="small"
|
||||||
|
></ion-icon>
|
||||||
|
</div>
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</mat-card-content>
|
||||||
|
</mat-card>
|
||||||
|
<mat-card class="mb-3">
|
||||||
|
<mat-card-content>
|
||||||
|
<div class="container p-0">
|
||||||
|
<div class="flex-nowrap no-gutters row">
|
||||||
<a
|
<a
|
||||||
class="d-flex w-100"
|
class="d-flex w-100"
|
||||||
[routerLink]="['/en', 'blog', '2022', '01', 'ghostfolio-first-months-in-open-source']"
|
[routerLink]="['/en', 'blog', '2022', '01', 'ghostfolio-first-months-in-open-source']"
|
||||||
@ -25,7 +51,13 @@
|
|||||||
</div>
|
</div>
|
||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
<div class="flex-nowrap mb-3 no-gutters row">
|
</div>
|
||||||
|
</mat-card-content>
|
||||||
|
</mat-card>
|
||||||
|
<mat-card class="mb-3">
|
||||||
|
<mat-card-content>
|
||||||
|
<div class="container p-0">
|
||||||
|
<div class="flex-nowrap no-gutters row">
|
||||||
<a
|
<a
|
||||||
class="d-flex w-100"
|
class="d-flex w-100"
|
||||||
[routerLink]="['/en', 'blog', '2021', '07', 'hello-ghostfolio']"
|
[routerLink]="['/en', 'blog', '2021', '07', 'hello-ghostfolio']"
|
||||||
@ -43,6 +75,12 @@
|
|||||||
</div>
|
</div>
|
||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
|
</mat-card-content>
|
||||||
|
</mat-card>
|
||||||
|
<mat-card class="mb-3">
|
||||||
|
<mat-card-content>
|
||||||
|
<div class="container p-0">
|
||||||
<div class="flex-nowrap no-gutters row">
|
<div class="flex-nowrap no-gutters row">
|
||||||
<a
|
<a
|
||||||
class="d-flex w-100"
|
class="d-flex w-100"
|
||||||
|
@ -4,6 +4,7 @@ import { ImpersonationStorageService } from '@ghostfolio/client/services/imperso
|
|||||||
import { UserService } from '@ghostfolio/client/services/user/user.service';
|
import { UserService } from '@ghostfolio/client/services/user/user.service';
|
||||||
import { Position, User } from '@ghostfolio/common/interfaces';
|
import { Position, User } from '@ghostfolio/common/interfaces';
|
||||||
import { InvestmentItem } from '@ghostfolio/common/interfaces/investment-item.interface';
|
import { InvestmentItem } from '@ghostfolio/common/interfaces/investment-item.interface';
|
||||||
|
import { GroupBy, ToggleOption } from '@ghostfolio/common/types';
|
||||||
import { differenceInDays } from 'date-fns';
|
import { differenceInDays } from 'date-fns';
|
||||||
import { sortBy } from 'lodash';
|
import { sortBy } from 'lodash';
|
||||||
import { DeviceDetectorService } from 'ngx-device-detector';
|
import { DeviceDetectorService } from 'ngx-device-detector';
|
||||||
@ -22,6 +23,12 @@ export class AnalysisPageComponent implements OnDestroy, OnInit {
|
|||||||
public deviceType: string;
|
public deviceType: string;
|
||||||
public hasImpersonationId: boolean;
|
public hasImpersonationId: boolean;
|
||||||
public investments: InvestmentItem[];
|
public investments: InvestmentItem[];
|
||||||
|
public investmentsByMonth: InvestmentItem[];
|
||||||
|
public mode: GroupBy;
|
||||||
|
public modeOptions: ToggleOption[] = [
|
||||||
|
{ label: 'Monthly', value: 'month' },
|
||||||
|
{ label: 'Accumulating', value: undefined }
|
||||||
|
];
|
||||||
public top3: Position[];
|
public top3: Position[];
|
||||||
public user: User;
|
public user: User;
|
||||||
|
|
||||||
@ -55,6 +62,15 @@ export class AnalysisPageComponent implements OnDestroy, OnInit {
|
|||||||
this.changeDetectorRef.markForCheck();
|
this.changeDetectorRef.markForCheck();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
this.dataService
|
||||||
|
.fetchInvestmentsByMonth()
|
||||||
|
.pipe(takeUntil(this.unsubscribeSubject))
|
||||||
|
.subscribe(({ investments }) => {
|
||||||
|
this.investmentsByMonth = investments;
|
||||||
|
|
||||||
|
this.changeDetectorRef.markForCheck();
|
||||||
|
});
|
||||||
|
|
||||||
this.dataService
|
this.dataService
|
||||||
.fetchPositions({ range: 'max' })
|
.fetchPositions({ range: 'max' })
|
||||||
.pipe(takeUntil(this.unsubscribeSubject))
|
.pipe(takeUntil(this.unsubscribeSubject))
|
||||||
@ -86,6 +102,10 @@ export class AnalysisPageComponent implements OnDestroy, OnInit {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public onChangeGroupBy(aMode: GroupBy) {
|
||||||
|
this.mode = aMode;
|
||||||
|
}
|
||||||
|
|
||||||
public ngOnDestroy() {
|
public ngOnDestroy() {
|
||||||
this.unsubscribeSubject.next();
|
this.unsubscribeSubject.next();
|
||||||
this.unsubscribeSubject.complete();
|
this.unsubscribeSubject.complete();
|
||||||
|
@ -2,8 +2,19 @@
|
|||||||
<div class="investment-chart row">
|
<div class="investment-chart row">
|
||||||
<div class="col-lg">
|
<div class="col-lg">
|
||||||
<h3 class="d-flex justify-content-center mb-3" i18n>Analysis</h3>
|
<h3 class="d-flex justify-content-center mb-3" i18n>Analysis</h3>
|
||||||
<div class="mb-3">
|
<div class="mb-4">
|
||||||
<div class="h5 mb-3" i18n>Investment Timeline</div>
|
<div class="align-items-center d-flex mb-4">
|
||||||
|
<div class="flex-grow-1 h5 mb-0 text-truncate" i18n>
|
||||||
|
Investment Timeline
|
||||||
|
</div>
|
||||||
|
<gf-toggle
|
||||||
|
class="d-none d-lg-block"
|
||||||
|
[defaultValue]="mode"
|
||||||
|
[isLoading]="false"
|
||||||
|
[options]="modeOptions"
|
||||||
|
(change)="onChangeGroupBy($event.value)"
|
||||||
|
></gf-toggle>
|
||||||
|
</div>
|
||||||
<gf-investment-chart
|
<gf-investment-chart
|
||||||
class="h-100"
|
class="h-100"
|
||||||
[currency]="user?.settings?.baseCurrency"
|
[currency]="user?.settings?.baseCurrency"
|
||||||
@ -11,6 +22,17 @@
|
|||||||
[isInPercent]="hasImpersonationId || user.settings.isRestrictedView"
|
[isInPercent]="hasImpersonationId || user.settings.isRestrictedView"
|
||||||
[investments]="investments"
|
[investments]="investments"
|
||||||
[locale]="user?.settings?.locale"
|
[locale]="user?.settings?.locale"
|
||||||
|
[ngClass]="{ 'd-none': mode }"
|
||||||
|
></gf-investment-chart>
|
||||||
|
<gf-investment-chart
|
||||||
|
class="h-100"
|
||||||
|
groupBy="month"
|
||||||
|
[currency]="user?.settings?.baseCurrency"
|
||||||
|
[daysInMarket]="daysInMarket"
|
||||||
|
[isInPercent]="hasImpersonationId || user.settings.isRestrictedView"
|
||||||
|
[investments]="investmentsByMonth"
|
||||||
|
[locale]="user?.settings?.locale"
|
||||||
|
[ngClass]="{ 'd-none': !mode }"
|
||||||
></gf-investment-chart>
|
></gf-investment-chart>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -2,6 +2,7 @@ import { CommonModule } from '@angular/common';
|
|||||||
import { CUSTOM_ELEMENTS_SCHEMA, NgModule } from '@angular/core';
|
import { CUSTOM_ELEMENTS_SCHEMA, NgModule } from '@angular/core';
|
||||||
import { MatCardModule } from '@angular/material/card';
|
import { MatCardModule } from '@angular/material/card';
|
||||||
import { GfInvestmentChartModule } from '@ghostfolio/client/components/investment-chart/investment-chart.module';
|
import { GfInvestmentChartModule } from '@ghostfolio/client/components/investment-chart/investment-chart.module';
|
||||||
|
import { GfToggleModule } from '@ghostfolio/client/components/toggle/toggle.module';
|
||||||
import { GfValueModule } from '@ghostfolio/ui/value';
|
import { GfValueModule } from '@ghostfolio/ui/value';
|
||||||
import { NgxSkeletonLoaderModule } from 'ngx-skeleton-loader';
|
import { NgxSkeletonLoaderModule } from 'ngx-skeleton-loader';
|
||||||
|
|
||||||
@ -14,6 +15,7 @@ import { AnalysisPageComponent } from './analysis-page.component';
|
|||||||
AnalysisPageRoutingModule,
|
AnalysisPageRoutingModule,
|
||||||
CommonModule,
|
CommonModule,
|
||||||
GfInvestmentChartModule,
|
GfInvestmentChartModule,
|
||||||
|
GfToggleModule,
|
||||||
GfValueModule,
|
GfValueModule,
|
||||||
MatCardModule,
|
MatCardModule,
|
||||||
NgxSkeletonLoaderModule
|
NgxSkeletonLoaderModule
|
||||||
|
@ -204,6 +204,22 @@ export class DataService {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public fetchInvestmentsByMonth(): Observable<PortfolioInvestments> {
|
||||||
|
return this.http
|
||||||
|
.get<any>('/api/v1/portfolio/investments', {
|
||||||
|
params: { groupBy: 'month' }
|
||||||
|
})
|
||||||
|
.pipe(
|
||||||
|
map((response) => {
|
||||||
|
if (response.firstOrderDate) {
|
||||||
|
response.firstOrderDate = parseISO(response.firstOrderDate);
|
||||||
|
}
|
||||||
|
|
||||||
|
return response;
|
||||||
|
})
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
public fetchSymbolItem({
|
public fetchSymbolItem({
|
||||||
dataSource,
|
dataSource,
|
||||||
includeHistoricalData,
|
includeHistoricalData,
|
||||||
|
@ -6,54 +6,58 @@
|
|||||||
http://www.sitemaps.org/schemas/sitemap/0.9/sitemap.xsd">
|
http://www.sitemaps.org/schemas/sitemap/0.9/sitemap.xsd">
|
||||||
<url>
|
<url>
|
||||||
<loc>https://ghostfol.io</loc>
|
<loc>https://ghostfol.io</loc>
|
||||||
<lastmod>2022-07-01T00:00:00+00:00</lastmod>
|
<lastmod>2022-07-14T00:00:00+00:00</lastmod>
|
||||||
</url>
|
</url>
|
||||||
<url>
|
<url>
|
||||||
<loc>https://ghostfol.io/about</loc>
|
<loc>https://ghostfol.io/about</loc>
|
||||||
<lastmod>2022-07-01T00:00:00+00:00</lastmod>
|
<lastmod>2022-07-14T00:00:00+00:00</lastmod>
|
||||||
</url>
|
</url>
|
||||||
<url>
|
<url>
|
||||||
<loc>https://ghostfol.io/about/changelog</loc>
|
<loc>https://ghostfol.io/about/changelog</loc>
|
||||||
<lastmod>2022-07-01T00:00:00+00:00</lastmod>
|
<lastmod>2022-07-14T00:00:00+00:00</lastmod>
|
||||||
</url>
|
</url>
|
||||||
<url>
|
<url>
|
||||||
<loc>https://ghostfol.io/blog</loc>
|
<loc>https://ghostfol.io/blog</loc>
|
||||||
<lastmod>2022-07-01T00:00:00+00:00</lastmod>
|
<lastmod>2022-07-14T00:00:00+00:00</lastmod>
|
||||||
</url>
|
</url>
|
||||||
<url>
|
<url>
|
||||||
<loc>https://ghostfol.io/de/blog/2021/07/hallo-ghostfolio</loc>
|
<loc>https://ghostfol.io/de/blog/2021/07/hallo-ghostfolio</loc>
|
||||||
<lastmod>2022-07-01T00:00:00+00:00</lastmod>
|
<lastmod>2022-07-14T00:00:00+00:00</lastmod>
|
||||||
</url>
|
</url>
|
||||||
<url>
|
<url>
|
||||||
<loc>https://ghostfol.io/demo</loc>
|
<loc>https://ghostfol.io/demo</loc>
|
||||||
<lastmod>2022-07-01T00:00:00+00:00</lastmod>
|
<lastmod>2022-07-14T00:00:00+00:00</lastmod>
|
||||||
</url>
|
</url>
|
||||||
<url>
|
<url>
|
||||||
<loc>https://ghostfol.io/en/blog/2021/07/hello-ghostfolio</loc>
|
<loc>https://ghostfol.io/en/blog/2021/07/hello-ghostfolio</loc>
|
||||||
<lastmod>2022-07-01T00:00:00+00:00</lastmod>
|
<lastmod>2022-07-14T00:00:00+00:00</lastmod>
|
||||||
</url>
|
</url>
|
||||||
<url>
|
<url>
|
||||||
<loc>https://ghostfol.io/en/blog/2022/01/ghostfolio-first-months-in-open-source</loc>
|
<loc>https://ghostfol.io/en/blog/2022/01/ghostfolio-first-months-in-open-source</loc>
|
||||||
<lastmod>2022-07-01T00:00:00+00:00</lastmod>
|
<lastmod>2022-07-14T00:00:00+00:00</lastmod>
|
||||||
|
</url>
|
||||||
|
<url>
|
||||||
|
<loc>https://ghostfol.io/en/blog/2022/07/how-do-i-get-my-finances-in-order</loc>
|
||||||
|
<lastmod>2022-07-14T00:00:00+00:00</lastmod>
|
||||||
</url>
|
</url>
|
||||||
<url>
|
<url>
|
||||||
<loc>https://ghostfol.io/features</loc>
|
<loc>https://ghostfol.io/features</loc>
|
||||||
<lastmod>2022-07-01T00:00:00+00:00</lastmod>
|
<lastmod>2022-07-14T00:00:00+00:00</lastmod>
|
||||||
</url>
|
</url>
|
||||||
<url>
|
<url>
|
||||||
<loc>https://ghostfol.io/markets</loc>
|
<loc>https://ghostfol.io/markets</loc>
|
||||||
<lastmod>2022-07-01T00:00:00+00:00</lastmod>
|
<lastmod>2022-07-14T00:00:00+00:00</lastmod>
|
||||||
</url>
|
</url>
|
||||||
<url>
|
<url>
|
||||||
<loc>https://ghostfol.io/pricing</loc>
|
<loc>https://ghostfol.io/pricing</loc>
|
||||||
<lastmod>2022-07-01T00:00:00+00:00</lastmod>
|
<lastmod>2022-07-14T00:00:00+00:00</lastmod>
|
||||||
</url>
|
</url>
|
||||||
<url>
|
<url>
|
||||||
<loc>https://ghostfol.io/register</loc>
|
<loc>https://ghostfol.io/register</loc>
|
||||||
<lastmod>2022-07-01T00:00:00+00:00</lastmod>
|
<lastmod>2022-07-14T00:00:00+00:00</lastmod>
|
||||||
</url>
|
</url>
|
||||||
<url>
|
<url>
|
||||||
<loc>https://ghostfol.io/resources</loc>
|
<loc>https://ghostfol.io/resources</loc>
|
||||||
<lastmod>2022-07-01T00:00:00+00:00</lastmod>
|
<lastmod>2022-07-14T00:00:00+00:00</lastmod>
|
||||||
</url>
|
</url>
|
||||||
</urlset>
|
</urlset>
|
||||||
|
@ -43,7 +43,7 @@ export function getTooltipPositionerMapTop(
|
|||||||
chart: Chart,
|
chart: Chart,
|
||||||
position: TooltipPosition
|
position: TooltipPosition
|
||||||
) {
|
) {
|
||||||
if (!position) {
|
if (!position || !chart?.chartArea) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
return {
|
return {
|
||||||
|
1
libs/common/src/lib/types/group-by.type.ts
Normal file
1
libs/common/src/lib/types/group-by.type.ts
Normal file
@ -0,0 +1 @@
|
|||||||
|
export type GroupBy = 'month';
|
@ -2,7 +2,8 @@ import type { AccessWithGranteeUser } from './access-with-grantee-user.type';
|
|||||||
import { AccountWithValue } from './account-with-value.type';
|
import { AccountWithValue } from './account-with-value.type';
|
||||||
import type { DateRange } from './date-range.type';
|
import type { DateRange } from './date-range.type';
|
||||||
import type { Granularity } from './granularity.type';
|
import type { Granularity } from './granularity.type';
|
||||||
import { MarketState } from './market-state-type';
|
import { GroupBy } from './group-by.type';
|
||||||
|
import { MarketState } from './market-state.type';
|
||||||
import { Market } from './market.type';
|
import { Market } from './market.type';
|
||||||
import type { OrderWithAccount } from './order-with-account.type';
|
import type { OrderWithAccount } from './order-with-account.type';
|
||||||
import type { RequestWithUser } from './request-with-user.type';
|
import type { RequestWithUser } from './request-with-user.type';
|
||||||
@ -13,6 +14,7 @@ export type {
|
|||||||
AccountWithValue,
|
AccountWithValue,
|
||||||
DateRange,
|
DateRange,
|
||||||
Granularity,
|
Granularity,
|
||||||
|
GroupBy,
|
||||||
Market,
|
Market,
|
||||||
MarketState,
|
MarketState,
|
||||||
OrderWithAccount,
|
OrderWithAccount,
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "ghostfolio",
|
"name": "ghostfolio",
|
||||||
"version": "1.167.0",
|
"version": "1.169.0",
|
||||||
"homepage": "https://ghostfol.io",
|
"homepage": "https://ghostfol.io",
|
||||||
"license": "AGPL-3.0",
|
"license": "AGPL-3.0",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
@ -93,7 +93,7 @@
|
|||||||
"color": "4.0.1",
|
"color": "4.0.1",
|
||||||
"countries-list": "2.6.1",
|
"countries-list": "2.6.1",
|
||||||
"countup.js": "2.0.7",
|
"countup.js": "2.0.7",
|
||||||
"date-fns": "2.22.1",
|
"date-fns": "2.28.0",
|
||||||
"envalid": "7.3.1",
|
"envalid": "7.3.1",
|
||||||
"google-spreadsheet": "3.2.0",
|
"google-spreadsheet": "3.2.0",
|
||||||
"http-status-codes": "2.2.0",
|
"http-status-codes": "2.2.0",
|
||||||
|
@ -7710,10 +7710,10 @@ data-urls@^2.0.0:
|
|||||||
whatwg-mimetype "^2.3.0"
|
whatwg-mimetype "^2.3.0"
|
||||||
whatwg-url "^8.0.0"
|
whatwg-url "^8.0.0"
|
||||||
|
|
||||||
date-fns@2.22.1:
|
date-fns@2.28.0:
|
||||||
version "2.22.1"
|
version "2.28.0"
|
||||||
resolved "https://registry.yarnpkg.com/date-fns/-/date-fns-2.22.1.tgz#1e5af959831ebb1d82992bf67b765052d8f0efc4"
|
resolved "https://registry.yarnpkg.com/date-fns/-/date-fns-2.28.0.tgz#9570d656f5fc13143e50c975a3b6bbeb46cd08b2"
|
||||||
integrity sha512-yUFPQjrxEmIsMqlHhAhmxkuH769baF21Kk+nZwZGyrMoyLA+LugaQtC0+Tqf9CBUUULWwUJt6Q5ySI3LJDDCGg==
|
integrity sha512-8d35hViGYx/QH0icHYCeLmsLmMUheMmTyV9Fcm6gvNwdw31yXXH+O85sOBJ+OLnLQMKZowvpKb6FgMIQjcpvQw==
|
||||||
|
|
||||||
date-fns@^1.27.2:
|
date-fns@^1.27.2:
|
||||||
version "1.30.1"
|
version "1.30.1"
|
||||||
|
Reference in New Issue
Block a user