Compare commits

...

13 Commits

Author SHA1 Message Date
d5d14497d6 Release 1.288.0 (#2146) 2023-07-12 19:56:16 +02:00
09c300661a Feature/improve language localization for german 20230711 (#2144)
* Improve i18n

* Update changelog
2023-07-12 19:53:55 +02:00
92382e0b4d Feature/improve loading state during filtering on allocations page (#2141)
* Improve loading state

* Update changelog
2023-07-11 21:41:12 +02:00
c25f532487 Improve product pages (#2143) 2023-07-11 21:40:45 +02:00
5d26d94586 Sort imports (#2142) 2023-07-11 20:27:54 +02:00
73b6784e9f Feature/beautify ampersand in asset profile names (#2138)
* Beautify ampersand

* Update changelog
2023-07-10 20:16:38 +02:00
6159f48a62 Feature/setup personal finance tools pages 2 (#2140) 2023-07-10 20:16:20 +02:00
7d34fba7c1 Release 1.287.0 (#2136) 2023-07-09 10:44:41 +02:00
c434b730a8 Feature/hide average buy price in position detail chart if no holding (#2133)
* Hide the average buy price if no holding

* Update changelog
2023-07-09 10:42:53 +02:00
2d23c566f1 Feature/setup personal finance tools pages (#2135) 2023-07-09 10:42:10 +02:00
ba220eaee9 Bugfix/fix sorting by currency in activities table (#2122)
* Fix sorting by currency

* Update changelog
2023-07-09 09:38:48 +02:00
09023214ce Feature/French translation update (#2130)
* French translation update

* Update changelog
2023-07-07 21:26:51 +02:00
1ceabb6e6b Feature/refactor blog articles to standalone components (#2117)
* Refactor blog articles to standalone components

* Update changelog
2023-07-04 18:42:40 +02:00
97 changed files with 20659 additions and 739 deletions

View File

@ -5,6 +5,26 @@ 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.288.0 - 2023-07-12
### Changed
- Improved the loading state during filtering on the allocations page
- Beautified the names with ampersand (`&`) in the asset profile
- Improved the language localization for German (`de`)
## 1.287.0 - 2023-07-09
### Changed
- Hid the average buy price in the position detail chart if there is no holding
- Improved the language localization for French (`fr`)
- Refactored the blog articles to standalone components
### Fixed
- Fixed the sorting by currency in the activities table
## 1.286.0 - 2023-07-03
### Fixed

View File

@ -135,6 +135,8 @@ export class YahooFinanceDataEnhancerService implements DataEnhancerInterface {
let name = longName;
if (name) {
name = name.replace('&', '&');
name = name.replace('Amundi Index Solutions - ', '');
name = name.replace('iShares ETF (CH) - ', '');
name = name.replace('iShares III Public Limited Company - ', '');

View File

@ -47,104 +47,6 @@ const routes: Routes = [
loadChildren: () =>
import('./pages/blog/blog-page.module').then((m) => m.BlogPageModule)
})),
{
path: 'blog/2021/07/hallo-ghostfolio',
loadChildren: () =>
import(
'./pages/blog/2021/07/hallo-ghostfolio/hallo-ghostfolio-page.module'
).then((m) => m.HalloGhostfolioPageModule)
},
{
path: 'blog/2021/07/hello-ghostfolio',
loadChildren: () =>
import(
'./pages/blog/2021/07/hello-ghostfolio/hello-ghostfolio-page.module'
).then((m) => m.HelloGhostfolioPageModule)
},
{
path: 'blog/2022/01/ghostfolio-first-months-in-open-source',
loadChildren: () =>
import(
'./pages/blog/2022/01/first-months-in-open-source/first-months-in-open-source-page.module'
).then((m) => m.FirstMonthsInOpenSourcePageModule)
},
{
path: 'blog/2022/07/ghostfolio-meets-internet-identity',
loadChildren: () =>
import(
'./pages/blog/2022/07/ghostfolio-meets-internet-identity/ghostfolio-meets-internet-identity-page.module'
).then((m) => m.GhostfolioMeetsInternetIdentityPageModule)
},
{
path: '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: 'blog/2022/08/500-stars-on-github',
loadChildren: () =>
import(
'./pages/blog/2022/08/500-stars-on-github/500-stars-on-github-page.module'
).then((m) => m.FiveHundredStarsOnGitHubPageModule)
},
{
path: 'blog/2022/10/hacktoberfest-2022',
loadChildren: () =>
import(
'./pages/blog/2022/10/hacktoberfest-2022/hacktoberfest-2022-page.module'
).then((m) => m.Hacktoberfest2022PageModule)
},
{
path: 'blog/2022/11/black-friday-2022',
loadChildren: () =>
import(
'./pages/blog/2022/11/black-friday-2022/black-friday-2022-page.module'
).then((m) => m.BlackFriday2022PageModule)
},
{
path: 'blog/2022/12/the-importance-of-tracking-your-personal-finances',
loadChildren: () =>
import(
'./pages/blog/2022/12/the-importance-of-tracking-your-personal-finances/the-importance-of-tracking-your-personal-finances-page.module'
).then((m) => m.TheImportanceOfTrackingYourPersonalFinancesPageModule)
},
{
path: 'blog/2023/01/ghostfolio-auf-sackgeld-vorgestellt',
loadChildren: () =>
import(
'./pages/blog/2023/01/ghostfolio-auf-sackgeld-vorgestellt/ghostfolio-auf-sackgeld-vorgestellt-page.module'
).then((m) => m.GhostfolioAufSackgeldVorgestelltPageModule)
},
{
path: 'blog/2023/02/ghostfolio-meets-umbrel',
loadChildren: () =>
import(
'./pages/blog/2023/02/ghostfolio-meets-umbrel/ghostfolio-meets-umbrel-page.module'
).then((m) => m.GhostfolioMeetsUmbrelPageModule)
},
{
path: 'blog/2023/03/ghostfolio-reaches-1000-stars-on-github',
loadChildren: () =>
import(
'./pages/blog/2023/03/1000-stars-on-github/1000-stars-on-github-page.module'
).then((m) => m.ThousandStarsOnGitHubPageModule)
},
{
path: 'blog/2023/05/unlock-your-financial-potential-with-ghostfolio',
loadChildren: () =>
import(
'./pages/blog/2023/05/unlock-your-financial-potential-with-ghostfolio/unlock-your-financial-potential-with-ghostfolio-page.module'
).then((m) => m.UnlockYourFinancialPotentialWithGhostfolioPageModule)
},
{
path: 'blog/2023/07/exploring-the-path-to-fire',
loadChildren: () =>
import(
'./pages/blog/2023/07/exploring-the-path-to-fire/exploring-the-path-to-fire-page.module'
).then((m) => m.ExploringThePathToFirePageModule)
},
{
path: 'demo',
loadChildren: () =>

View File

@ -8,11 +8,13 @@ import {
ViewChild
} from '@angular/core';
import { MatDialog } from '@angular/material/dialog';
import { MatPaginator, PageEvent } from '@angular/material/paginator';
import { MatSort, Sort } from '@angular/material/sort';
import { MatTableDataSource } from '@angular/material/table';
import { ActivatedRoute, Router } from '@angular/router';
import { AdminService } from '@ghostfolio/client/services/admin.service';
import { UserService } from '@ghostfolio/client/services/user/user.service';
import { DEFAULT_PAGE_SIZE } from '@ghostfolio/common/config';
import { getDateFormatString } from '@ghostfolio/common/helper';
import { Filter, UniqueAsset, User } from '@ghostfolio/common/interfaces';
import { AdminMarketDataItem } from '@ghostfolio/common/interfaces/admin-market-data.interface';
@ -26,8 +28,6 @@ import { AssetProfileDialog } from './asset-profile-dialog/asset-profile-dialog.
import { AssetProfileDialogParams } from './asset-profile-dialog/interfaces/interfaces';
import { CreateAssetProfileDialog } from './create-asset-profile-dialog/create-asset-profile-dialog.component';
import { CreateAssetProfileDialogParams } from './create-asset-profile-dialog/interfaces/interfaces';
import { DEFAULT_PAGE_SIZE } from '@ghostfolio/common/config';
import { MatPaginator, PageEvent } from '@angular/material/paginator';
@Component({
changeDetection: ChangeDetectionStrategy.OnPush,

View File

@ -215,6 +215,15 @@ export class PositionDetailDialog implements OnDestroy, OnInit {
this.benchmarkDataItems[0].value = this.averagePrice;
}
this.benchmarkDataItems = this.benchmarkDataItems.map(
({ date, value }) => {
return {
date,
value: value === 0 ? null : value
};
}
);
if (Number.isInteger(this.quantity)) {
this.quantityPrecision = 0;
} else if (this.SymbolProfile?.assetSubClass === 'CRYPTOCURRENCY') {

View File

@ -1,20 +0,0 @@
import { NgModule } from '@angular/core';
import { RouterModule, Routes } from '@angular/router';
import { AuthGuard } from '@ghostfolio/client/core/auth.guard';
import { HalloGhostfolioPageComponent } from './hallo-ghostfolio-page.component';
const routes: Routes = [
{
canActivate: [AuthGuard],
component: HalloGhostfolioPageComponent,
path: '',
title: 'Hallo Ghostfolio'
}
];
@NgModule({
imports: [RouterModule.forChild(routes)],
exports: [RouterModule]
})
export class HalloGhostfolioPageRoutingModule {}

View File

@ -1,8 +1,12 @@
import { Component } from '@angular/core';
import { MatButtonModule } from '@angular/material/button';
import { RouterModule } from '@angular/router';
@Component({
host: { class: 'page' },
imports: [MatButtonModule, RouterModule],
selector: 'gf-hallo-ghostfolio-page',
standalone: true,
templateUrl: './hallo-ghostfolio-page.html'
})
export class HalloGhostfolioPageComponent {}

View File

@ -1,13 +0,0 @@
import { CommonModule } from '@angular/common';
import { CUSTOM_ELEMENTS_SCHEMA, NgModule } from '@angular/core';
import { RouterModule } from '@angular/router';
import { HalloGhostfolioPageRoutingModule } from './hallo-ghostfolio-page-routing.module';
import { HalloGhostfolioPageComponent } from './hallo-ghostfolio-page.component';
@NgModule({
declarations: [HalloGhostfolioPageComponent],
imports: [CommonModule, HalloGhostfolioPageRoutingModule, RouterModule],
schemas: [CUSTOM_ELEMENTS_SCHEMA]
})
export class HalloGhostfolioPageModule {}

View File

@ -1,20 +0,0 @@
import { NgModule } from '@angular/core';
import { RouterModule, Routes } from '@angular/router';
import { AuthGuard } from '@ghostfolio/client/core/auth.guard';
import { HelloGhostfolioPageComponent } from './hello-ghostfolio-page.component';
const routes: Routes = [
{
canActivate: [AuthGuard],
component: HelloGhostfolioPageComponent,
path: '',
title: 'Hello Ghostfolio'
}
];
@NgModule({
imports: [RouterModule.forChild(routes)],
exports: [RouterModule]
})
export class HelloGhostfolioPageRoutingModule {}

View File

@ -1,8 +1,12 @@
import { Component } from '@angular/core';
import { MatButtonModule } from '@angular/material/button';
import { RouterModule } from '@angular/router';
@Component({
host: { class: 'page' },
imports: [MatButtonModule, RouterModule],
selector: 'gf-hello-ghostfolio-page',
standalone: true,
templateUrl: './hello-ghostfolio-page.html'
})
export class HelloGhostfolioPageComponent {}

View File

@ -1,13 +0,0 @@
import { CommonModule } from '@angular/common';
import { CUSTOM_ELEMENTS_SCHEMA, NgModule } from '@angular/core';
import { RouterModule } from '@angular/router';
import { HelloGhostfolioPageRoutingModule } from './hello-ghostfolio-page-routing.module';
import { HelloGhostfolioPageComponent } from './hello-ghostfolio-page.component';
@NgModule({
declarations: [HelloGhostfolioPageComponent],
imports: [CommonModule, HelloGhostfolioPageRoutingModule, RouterModule],
schemas: [CUSTOM_ELEMENTS_SCHEMA]
})
export class HelloGhostfolioPageModule {}

View File

@ -1,20 +0,0 @@
import { NgModule } from '@angular/core';
import { RouterModule, Routes } from '@angular/router';
import { AuthGuard } from '@ghostfolio/client/core/auth.guard';
import { FirstMonthsInOpenSourcePageComponent } from './first-months-in-open-source-page.component';
const routes: Routes = [
{
canActivate: [AuthGuard],
component: FirstMonthsInOpenSourcePageComponent,
path: '',
title: 'First months in Open Source'
}
];
@NgModule({
imports: [RouterModule.forChild(routes)],
exports: [RouterModule]
})
export class FirstMonthsInOpenSourceRoutingModule {}

View File

@ -1,8 +1,12 @@
import { Component } from '@angular/core';
import { MatButtonModule } from '@angular/material/button';
import { RouterModule } from '@angular/router';
@Component({
host: { class: 'page' },
imports: [MatButtonModule, RouterModule],
selector: 'gf-first-months-in-open-source-page',
standalone: true,
templateUrl: './first-months-in-open-source-page.html'
})
export class FirstMonthsInOpenSourcePageComponent {}

View File

@ -1,13 +0,0 @@
import { CommonModule } from '@angular/common';
import { CUSTOM_ELEMENTS_SCHEMA, NgModule } from '@angular/core';
import { RouterModule } from '@angular/router';
import { FirstMonthsInOpenSourceRoutingModule } from './first-months-in-open-source-page-routing.module';
import { FirstMonthsInOpenSourcePageComponent } from './first-months-in-open-source-page.component';
@NgModule({
declarations: [FirstMonthsInOpenSourcePageComponent],
imports: [CommonModule, FirstMonthsInOpenSourceRoutingModule, RouterModule],
schemas: [CUSTOM_ELEMENTS_SCHEMA]
})
export class FirstMonthsInOpenSourcePageModule {}

View File

@ -1,20 +0,0 @@
import { NgModule } from '@angular/core';
import { RouterModule, Routes } from '@angular/router';
import { AuthGuard } from '@ghostfolio/client/core/auth.guard';
import { GhostfolioMeetsInternetIdentityPageComponent } from './ghostfolio-meets-internet-identity-page.component';
const routes: Routes = [
{
canActivate: [AuthGuard],
component: GhostfolioMeetsInternetIdentityPageComponent,
path: '',
title: 'Ghostfolio meets Internet Identity'
}
];
@NgModule({
imports: [RouterModule.forChild(routes)],
exports: [RouterModule]
})
export class GhostfolioMeetsInternetIdentityRoutingModule {}

View File

@ -1,8 +1,12 @@
import { Component } from '@angular/core';
import { MatButtonModule } from '@angular/material/button';
import { RouterModule } from '@angular/router';
@Component({
host: { class: 'page' },
imports: [MatButtonModule, RouterModule],
selector: 'gf-ghostfolio-meets-internet-identity-page',
standalone: true,
templateUrl: './ghostfolio-meets-internet-identity-page.html'
})
export class GhostfolioMeetsInternetIdentityPageComponent {}

View File

@ -1,17 +0,0 @@
import { CommonModule } from '@angular/common';
import { CUSTOM_ELEMENTS_SCHEMA, NgModule } from '@angular/core';
import { RouterModule } from '@angular/router';
import { GhostfolioMeetsInternetIdentityRoutingModule } from './ghostfolio-meets-internet-identity-page-routing.module';
import { GhostfolioMeetsInternetIdentityPageComponent } from './ghostfolio-meets-internet-identity-page.component';
@NgModule({
declarations: [GhostfolioMeetsInternetIdentityPageComponent],
imports: [
CommonModule,
GhostfolioMeetsInternetIdentityRoutingModule,
RouterModule
],
schemas: [CUSTOM_ELEMENTS_SCHEMA]
})
export class GhostfolioMeetsInternetIdentityPageModule {}

View File

@ -1,20 +0,0 @@
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 = [
{
canActivate: [AuthGuard],
component: HowDoIGetMyFinancesInOrderPageComponent,
path: '',
title: 'How do I get my finances in order?'
}
];
@NgModule({
imports: [RouterModule.forChild(routes)],
exports: [RouterModule]
})
export class HowDoIGetMyFinancesInOrderRoutingModule {}

View File

@ -1,8 +1,12 @@
import { Component } from '@angular/core';
import { MatButtonModule } from '@angular/material/button';
import { RouterModule } from '@angular/router';
@Component({
host: { class: 'page' },
imports: [MatButtonModule, RouterModule],
selector: 'gf-how-do-i-get-my-finances-in-order-page',
standalone: true,
templateUrl: './how-do-i-get-my-finances-in-order-page.html'
})
export class HowDoIGetMyFinancesInOrderPageComponent {}

View File

@ -1,17 +0,0 @@
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 {}

View File

@ -1,20 +0,0 @@
import { NgModule } from '@angular/core';
import { RouterModule, Routes } from '@angular/router';
import { AuthGuard } from '@ghostfolio/client/core/auth.guard';
import { FiveHundredStarsOnGitHubPageComponent } from './500-stars-on-github-page.component';
const routes: Routes = [
{
canActivate: [AuthGuard],
component: FiveHundredStarsOnGitHubPageComponent,
path: '',
title: '500 Stars on GitHub'
}
];
@NgModule({
imports: [RouterModule.forChild(routes)],
exports: [RouterModule]
})
export class FiveHundredStarsOnGitHubRoutingModule {}

View File

@ -1,8 +1,12 @@
import { Component } from '@angular/core';
import { MatButtonModule } from '@angular/material/button';
import { RouterModule } from '@angular/router';
@Component({
host: { class: 'page' },
imports: [MatButtonModule, RouterModule],
selector: 'gf-500-stars-on-github-page',
standalone: true,
templateUrl: './500-stars-on-github-page.html'
})
export class FiveHundredStarsOnGitHubPageComponent {}

View File

@ -1,13 +0,0 @@
import { CommonModule } from '@angular/common';
import { CUSTOM_ELEMENTS_SCHEMA, NgModule } from '@angular/core';
import { RouterModule } from '@angular/router';
import { FiveHundredStarsOnGitHubRoutingModule } from './500-stars-on-github-page-routing.module';
import { FiveHundredStarsOnGitHubPageComponent } from './500-stars-on-github-page.component';
@NgModule({
declarations: [FiveHundredStarsOnGitHubPageComponent],
imports: [CommonModule, FiveHundredStarsOnGitHubRoutingModule, RouterModule],
schemas: [CUSTOM_ELEMENTS_SCHEMA]
})
export class FiveHundredStarsOnGitHubPageModule {}

View File

@ -1,20 +0,0 @@
import { NgModule } from '@angular/core';
import { RouterModule, Routes } from '@angular/router';
import { AuthGuard } from '@ghostfolio/client/core/auth.guard';
import { Hacktoberfest2022PageComponent } from './hacktoberfest-2022-page.component';
const routes: Routes = [
{
canActivate: [AuthGuard],
component: Hacktoberfest2022PageComponent,
path: '',
title: 'Hacktoberfest 2022'
}
];
@NgModule({
imports: [RouterModule.forChild(routes)],
exports: [RouterModule]
})
export class Hacktoberfest2022RoutingModule {}

View File

@ -1,8 +1,12 @@
import { Component } from '@angular/core';
import { MatButtonModule } from '@angular/material/button';
import { RouterModule } from '@angular/router';
@Component({
host: { class: 'page' },
imports: [MatButtonModule, RouterModule],
selector: 'gf-hacktoberfest-2022-page',
standalone: true,
templateUrl: './hacktoberfest-2022-page.html'
})
export class Hacktoberfest2022PageComponent {}

View File

@ -1,13 +0,0 @@
import { CommonModule } from '@angular/common';
import { CUSTOM_ELEMENTS_SCHEMA, NgModule } from '@angular/core';
import { RouterModule } from '@angular/router';
import { Hacktoberfest2022RoutingModule } from './hacktoberfest-2022-page-routing.module';
import { Hacktoberfest2022PageComponent } from './hacktoberfest-2022-page.component';
@NgModule({
declarations: [Hacktoberfest2022PageComponent],
imports: [CommonModule, Hacktoberfest2022RoutingModule, RouterModule],
schemas: [CUSTOM_ELEMENTS_SCHEMA]
})
export class Hacktoberfest2022PageModule {}

View File

@ -1,20 +0,0 @@
import { NgModule } from '@angular/core';
import { RouterModule, Routes } from '@angular/router';
import { AuthGuard } from '@ghostfolio/client/core/auth.guard';
import { BlackFriday2022PageComponent } from './black-friday-2022-page.component';
const routes: Routes = [
{
canActivate: [AuthGuard],
component: BlackFriday2022PageComponent,
path: '',
title: 'Black Friday 2022'
}
];
@NgModule({
imports: [RouterModule.forChild(routes)],
exports: [RouterModule]
})
export class BlackFriday2022RoutingModule {}

View File

@ -1,8 +1,13 @@
import { Component } from '@angular/core';
import { MatButtonModule } from '@angular/material/button';
import { RouterModule } from '@angular/router';
import { GfPremiumIndicatorModule } from '@ghostfolio/ui/premium-indicator';
@Component({
host: { class: 'page' },
imports: [GfPremiumIndicatorModule, MatButtonModule, RouterModule],
selector: 'gf-black-friday-2022-page',
standalone: true,
templateUrl: './black-friday-2022-page.html'
})
export class BlackFriday2022PageComponent {

View File

@ -1,21 +0,0 @@
import { CommonModule } from '@angular/common';
import { CUSTOM_ELEMENTS_SCHEMA, NgModule } from '@angular/core';
import { MatButtonModule } from '@angular/material/button';
import { RouterModule } from '@angular/router';
import { GfPremiumIndicatorModule } from '@ghostfolio/ui/premium-indicator';
import { BlackFriday2022RoutingModule } from './black-friday-2022-page-routing.module';
import { BlackFriday2022PageComponent } from './black-friday-2022-page.component';
@NgModule({
declarations: [BlackFriday2022PageComponent],
imports: [
BlackFriday2022RoutingModule,
CommonModule,
GfPremiumIndicatorModule,
MatButtonModule,
RouterModule
],
schemas: [CUSTOM_ELEMENTS_SCHEMA]
})
export class BlackFriday2022PageModule {}

View File

@ -1,20 +0,0 @@
import { NgModule } from '@angular/core';
import { RouterModule, Routes } from '@angular/router';
import { AuthGuard } from '@ghostfolio/client/core/auth.guard';
import { TheImportanceOfTrackingYourPersonalFinancesPageComponent } from './the-importance-of-tracking-your-personal-finances-page.component';
const routes: Routes = [
{
canActivate: [AuthGuard],
component: TheImportanceOfTrackingYourPersonalFinancesPageComponent,
path: '',
title: 'The importance of tracking your personal finances'
}
];
@NgModule({
imports: [RouterModule.forChild(routes)],
exports: [RouterModule]
})
export class TheImportanceOfTrackingYourPersonalFinancesRoutingModule {}

View File

@ -1,8 +1,12 @@
import { Component } from '@angular/core';
import { MatButtonModule } from '@angular/material/button';
import { RouterModule } from '@angular/router';
@Component({
host: { class: 'page' },
imports: [MatButtonModule, RouterModule],
selector: 'gf-the-importance-of-tracking-your-personal-finances-page',
standalone: true,
templateUrl: './the-importance-of-tracking-your-personal-finances-page.html'
})
export class TheImportanceOfTrackingYourPersonalFinancesPageComponent {}

View File

@ -1,19 +0,0 @@
import { CommonModule } from '@angular/common';
import { CUSTOM_ELEMENTS_SCHEMA, NgModule } from '@angular/core';
import { MatButtonModule } from '@angular/material/button';
import { RouterModule } from '@angular/router';
import { TheImportanceOfTrackingYourPersonalFinancesRoutingModule } from './the-importance-of-tracking-your-personal-finances-page-routing.module';
import { TheImportanceOfTrackingYourPersonalFinancesPageComponent } from './the-importance-of-tracking-your-personal-finances-page.component';
@NgModule({
declarations: [TheImportanceOfTrackingYourPersonalFinancesPageComponent],
imports: [
CommonModule,
MatButtonModule,
RouterModule,
TheImportanceOfTrackingYourPersonalFinancesRoutingModule
],
schemas: [CUSTOM_ELEMENTS_SCHEMA]
})
export class TheImportanceOfTrackingYourPersonalFinancesPageModule {}

View File

@ -1,20 +0,0 @@
import { NgModule } from '@angular/core';
import { RouterModule, Routes } from '@angular/router';
import { AuthGuard } from '@ghostfolio/client/core/auth.guard';
import { GhostfolioAufSackgeldVorgestelltPageComponent } from './ghostfolio-auf-sackgeld-vorgestellt-page.component';
const routes: Routes = [
{
canActivate: [AuthGuard],
component: GhostfolioAufSackgeldVorgestelltPageComponent,
path: '',
title: 'Ghostfolio auf Sackgeld.com vorgestellt'
}
];
@NgModule({
imports: [RouterModule.forChild(routes)],
exports: [RouterModule]
})
export class GhostfolioAufSackgeldVorgestelltPageRoutingModule {}

View File

@ -1,8 +1,12 @@
import { Component } from '@angular/core';
import { MatButtonModule } from '@angular/material/button';
import { RouterModule } from '@angular/router';
@Component({
host: { class: 'page' },
imports: [MatButtonModule, RouterModule],
selector: 'gf-ghostfolio-auf-sackgeld-vorgestellt-page',
standalone: true,
templateUrl: './ghostfolio-auf-sackgeld-vorgestellt-page.html'
})
export class GhostfolioAufSackgeldVorgestelltPageComponent {}

View File

@ -1,17 +0,0 @@
import { CommonModule } from '@angular/common';
import { CUSTOM_ELEMENTS_SCHEMA, NgModule } from '@angular/core';
import { RouterModule } from '@angular/router';
import { GhostfolioAufSackgeldVorgestelltPageRoutingModule } from './ghostfolio-auf-sackgeld-vorgestellt-page-routing.module';
import { GhostfolioAufSackgeldVorgestelltPageComponent } from './ghostfolio-auf-sackgeld-vorgestellt-page.component';
@NgModule({
declarations: [GhostfolioAufSackgeldVorgestelltPageComponent],
imports: [
CommonModule,
GhostfolioAufSackgeldVorgestelltPageRoutingModule,
RouterModule
],
schemas: [CUSTOM_ELEMENTS_SCHEMA]
})
export class GhostfolioAufSackgeldVorgestelltPageModule {}

View File

@ -1,20 +0,0 @@
import { NgModule } from '@angular/core';
import { RouterModule, Routes } from '@angular/router';
import { AuthGuard } from '@ghostfolio/client/core/auth.guard';
import { GhostfolioMeetsUmbrelPageComponent } from './ghostfolio-meets-umbrel-page.component';
const routes: Routes = [
{
canActivate: [AuthGuard],
component: GhostfolioMeetsUmbrelPageComponent,
path: '',
title: 'Ghostfolio meets Umbrel'
}
];
@NgModule({
imports: [RouterModule.forChild(routes)],
exports: [RouterModule]
})
export class GhostfolioMeetsUmbrelPageRoutingModule {}

View File

@ -1,8 +1,12 @@
import { Component } from '@angular/core';
import { MatButtonModule } from '@angular/material/button';
import { RouterModule } from '@angular/router';
@Component({
host: { class: 'page' },
imports: [MatButtonModule, RouterModule],
selector: 'gf-ghostfolio-meets-umbrel-page',
standalone: true,
templateUrl: './ghostfolio-meets-umbrel-page.html'
})
export class GhostfolioMeetsUmbrelPageComponent {}

View File

@ -1,13 +0,0 @@
import { CommonModule } from '@angular/common';
import { CUSTOM_ELEMENTS_SCHEMA, NgModule } from '@angular/core';
import { RouterModule } from '@angular/router';
import { GhostfolioMeetsUmbrelPageRoutingModule } from './ghostfolio-meets-umbrel-page-routing.module';
import { GhostfolioMeetsUmbrelPageComponent } from './ghostfolio-meets-umbrel-page.component';
@NgModule({
declarations: [GhostfolioMeetsUmbrelPageComponent],
imports: [CommonModule, GhostfolioMeetsUmbrelPageRoutingModule, RouterModule],
schemas: [CUSTOM_ELEMENTS_SCHEMA]
})
export class GhostfolioMeetsUmbrelPageModule {}

View File

@ -1,20 +0,0 @@
import { NgModule } from '@angular/core';
import { RouterModule, Routes } from '@angular/router';
import { AuthGuard } from '@ghostfolio/client/core/auth.guard';
import { ThousandStarsOnGitHubPageComponent } from './1000-stars-on-github-page.component';
const routes: Routes = [
{
canActivate: [AuthGuard],
component: ThousandStarsOnGitHubPageComponent,
path: '',
title: 'Ghostfolio reaches 1000 Stars on GitHub'
}
];
@NgModule({
imports: [RouterModule.forChild(routes)],
exports: [RouterModule]
})
export class ThousandStarsOnGitHubRoutingModule {}

View File

@ -1,8 +1,12 @@
import { Component } from '@angular/core';
import { MatButtonModule } from '@angular/material/button';
import { RouterModule } from '@angular/router';
@Component({
host: { class: 'page' },
imports: [MatButtonModule, RouterModule],
selector: 'gf-1000-stars-on-github-page',
standalone: true,
templateUrl: './1000-stars-on-github-page.html'
})
export class ThousandStarsOnGitHubPageComponent {}

View File

@ -1,13 +0,0 @@
import { CommonModule } from '@angular/common';
import { CUSTOM_ELEMENTS_SCHEMA, NgModule } from '@angular/core';
import { RouterModule } from '@angular/router';
import { ThousandStarsOnGitHubRoutingModule } from './1000-stars-on-github-page-routing.module';
import { ThousandStarsOnGitHubPageComponent } from './1000-stars-on-github-page.component';
@NgModule({
declarations: [ThousandStarsOnGitHubPageComponent],
imports: [CommonModule, ThousandStarsOnGitHubRoutingModule, RouterModule],
schemas: [CUSTOM_ELEMENTS_SCHEMA]
})
export class ThousandStarsOnGitHubPageModule {}

View File

@ -1,20 +0,0 @@
import { NgModule } from '@angular/core';
import { RouterModule, Routes } from '@angular/router';
import { AuthGuard } from '@ghostfolio/client/core/auth.guard';
import { UnlockYourFinancialPotentialWithGhostfolioPageComponent } from './unlock-your-financial-potential-with-ghostfolio-page.component';
const routes: Routes = [
{
canActivate: [AuthGuard],
component: UnlockYourFinancialPotentialWithGhostfolioPageComponent,
path: '',
title: 'Unlock your Financial Potential with Ghostfolio'
}
];
@NgModule({
imports: [RouterModule.forChild(routes)],
exports: [RouterModule]
})
export class UnlockYourFinancialPotentialWithGhostfolioRoutingModule {}

View File

@ -1,8 +1,12 @@
import { Component } from '@angular/core';
import { MatButtonModule } from '@angular/material/button';
import { RouterModule } from '@angular/router';
@Component({
host: { class: 'page' },
imports: [MatButtonModule, RouterModule],
selector: 'gf-unlock-your-financial-potential-with-ghostfolio-page',
standalone: true,
templateUrl: './unlock-your-financial-potential-with-ghostfolio-page.html'
})
export class UnlockYourFinancialPotentialWithGhostfolioPageComponent {}

View File

@ -1,19 +0,0 @@
import { CommonModule } from '@angular/common';
import { CUSTOM_ELEMENTS_SCHEMA, NgModule } from '@angular/core';
import { MatButtonModule } from '@angular/material/button';
import { RouterModule } from '@angular/router';
import { UnlockYourFinancialPotentialWithGhostfolioRoutingModule } from './unlock-your-financial-potential-with-ghostfolio-page-routing.module';
import { UnlockYourFinancialPotentialWithGhostfolioPageComponent } from './unlock-your-financial-potential-with-ghostfolio-page.component';
@NgModule({
declarations: [UnlockYourFinancialPotentialWithGhostfolioPageComponent],
imports: [
CommonModule,
MatButtonModule,
RouterModule,
UnlockYourFinancialPotentialWithGhostfolioRoutingModule
],
schemas: [CUSTOM_ELEMENTS_SCHEMA]
})
export class UnlockYourFinancialPotentialWithGhostfolioPageModule {}

View File

@ -1,20 +0,0 @@
import { NgModule } from '@angular/core';
import { RouterModule, Routes } from '@angular/router';
import { AuthGuard } from '@ghostfolio/client/core/auth.guard';
import { ExploringThePathToFirePageComponent } from './exploring-the-path-to-fire-page.component';
const routes: Routes = [
{
canActivate: [AuthGuard],
component: ExploringThePathToFirePageComponent,
path: '',
title: 'Exploring the Path to FIRE'
}
];
@NgModule({
imports: [RouterModule.forChild(routes)],
exports: [RouterModule]
})
export class ExploringThePathToFireRoutingModule {}

View File

@ -1,8 +1,12 @@
import { Component } from '@angular/core';
import { MatButtonModule } from '@angular/material/button';
import { RouterModule } from '@angular/router';
@Component({
host: { class: 'page' },
imports: [MatButtonModule, RouterModule],
selector: 'gf-exploring-the-path-to-fire-page-page',
standalone: true,
templateUrl: './exploring-the-path-to-fire-page.html'
})
export class ExploringThePathToFirePageComponent {}

View File

@ -1,19 +0,0 @@
import { CommonModule } from '@angular/common';
import { CUSTOM_ELEMENTS_SCHEMA, NgModule } from '@angular/core';
import { MatButtonModule } from '@angular/material/button';
import { RouterModule } from '@angular/router';
import { ExploringThePathToFireRoutingModule } from './exploring-the-path-to-fire-page-routing.module';
import { ExploringThePathToFirePageComponent } from './exploring-the-path-to-fire-page.component';
@NgModule({
declarations: [ExploringThePathToFirePageComponent],
imports: [
CommonModule,
ExploringThePathToFireRoutingModule,
MatButtonModule,
RouterModule
],
schemas: [CUSTOM_ELEMENTS_SCHEMA]
})
export class ExploringThePathToFirePageModule {}

View File

@ -10,6 +10,132 @@ const routes: Routes = [
component: BlogPageComponent,
path: '',
title: $localize`Blog`
},
{
canActivate: [AuthGuard],
path: '2021/07/hallo-ghostfolio',
loadComponent: () =>
import('./2021/07/hallo-ghostfolio/hallo-ghostfolio-page.component').then(
(c) => c.HalloGhostfolioPageComponent
),
title: 'Hallo Ghostfolio'
},
{
canActivate: [AuthGuard],
path: '2021/07/hello-ghostfolio',
loadComponent: () =>
import('./2021/07/hello-ghostfolio/hello-ghostfolio-page.component').then(
(c) => c.HelloGhostfolioPageComponent
),
title: 'Hello Ghostfolio'
},
{
canActivate: [AuthGuard],
path: '2022/01/ghostfolio-first-months-in-open-source',
loadComponent: () =>
import(
'./2022/01/first-months-in-open-source/first-months-in-open-source-page.component'
).then((c) => c.FirstMonthsInOpenSourcePageComponent),
title: 'First months in Open Source'
},
{
canActivate: [AuthGuard],
path: '2022/07/ghostfolio-meets-internet-identity',
loadComponent: () =>
import(
'./2022/07/ghostfolio-meets-internet-identity/ghostfolio-meets-internet-identity-page.component'
).then((c) => c.GhostfolioMeetsInternetIdentityPageComponent),
title: 'Ghostfolio meets Internet Identity'
},
{
canActivate: [AuthGuard],
path: '2022/07/how-do-i-get-my-finances-in-order',
loadComponent: () =>
import(
'./2022/07/how-do-i-get-my-finances-in-order/how-do-i-get-my-finances-in-order-page.component'
).then((c) => c.HowDoIGetMyFinancesInOrderPageComponent),
title: 'How do I get my finances in order?'
},
{
canActivate: [AuthGuard],
path: '2022/08/500-stars-on-github',
loadComponent: () =>
import(
'./2022/08/500-stars-on-github/500-stars-on-github-page.component'
).then((c) => c.FiveHundredStarsOnGitHubPageComponent),
title: '500 Stars on GitHub'
},
{
canActivate: [AuthGuard],
path: '2022/10/hacktoberfest-2022',
loadComponent: () =>
import(
'./2022/10/hacktoberfest-2022/hacktoberfest-2022-page.component'
).then((c) => c.Hacktoberfest2022PageComponent),
title: 'Hacktoberfest 2022'
},
{
canActivate: [AuthGuard],
path: '2022/11/black-friday-2022',
loadComponent: () =>
import(
'./2022/11/black-friday-2022/black-friday-2022-page.component'
).then((c) => c.BlackFriday2022PageComponent),
title: 'Black Friday 2022'
},
{
canActivate: [AuthGuard],
path: '2022/12/the-importance-of-tracking-your-personal-finances',
loadComponent: () =>
import(
'./2022/12/the-importance-of-tracking-your-personal-finances/the-importance-of-tracking-your-personal-finances-page.component'
).then((c) => c.TheImportanceOfTrackingYourPersonalFinancesPageComponent),
title: 'The importance of tracking your personal finances'
},
{
canActivate: [AuthGuard],
path: '2023/01/ghostfolio-auf-sackgeld-vorgestellt',
loadComponent: () =>
import(
'./2023/01/ghostfolio-auf-sackgeld-vorgestellt/ghostfolio-auf-sackgeld-vorgestellt-page.component'
).then((c) => c.GhostfolioAufSackgeldVorgestelltPageComponent),
title: 'Ghostfolio auf Sackgeld.com vorgestellt'
},
{
canActivate: [AuthGuard],
path: '2023/02/ghostfolio-meets-umbrel',
loadComponent: () =>
import(
'./2023/02/ghostfolio-meets-umbrel/ghostfolio-meets-umbrel-page.component'
).then((c) => c.GhostfolioMeetsUmbrelPageComponent),
title: 'Ghostfolio meets Umbrel'
},
{
canActivate: [AuthGuard],
path: '2023/03/ghostfolio-reaches-1000-stars-on-github',
loadComponent: () =>
import(
'./2023/03/1000-stars-on-github/1000-stars-on-github-page.component'
).then((c) => c.ThousandStarsOnGitHubPageComponent),
title: 'Ghostfolio reaches 1000 Stars on GitHub'
},
{
canActivate: [AuthGuard],
path: '2023/05/unlock-your-financial-potential-with-ghostfolio',
loadComponent: () =>
import(
'./2023/05/unlock-your-financial-potential-with-ghostfolio/unlock-your-financial-potential-with-ghostfolio-page.component'
).then((c) => c.UnlockYourFinancialPotentialWithGhostfolioPageComponent),
title: 'Unlock your Financial Potential with Ghostfolio'
},
{
canActivate: [AuthGuard],
path: '2023/07/exploring-the-path-to-fire',
loadComponent: () =>
import(
'./2023/07/exploring-the-path-to-fire/exploring-the-path-to-fire-page.component'
).then((c) => c.ExploringThePathToFirePageComponent),
title: 'Exploring the Path to FIRE'
}
];

View File

@ -139,6 +139,8 @@ export class AllocationsPageComponent implements OnDestroy, OnInit {
? $localize`Filter by account or tag...`
: '';
this.initialize();
return this.dataService.fetchPortfolioDetails({
filters: this.activeFilters
});
@ -146,6 +148,8 @@ export class AllocationsPageComponent implements OnDestroy, OnInit {
takeUntil(this.unsubscribeSubject)
)
.subscribe((portfolioDetails) => {
this.initialize();
this.portfolioDetails = portfolioDetails;
this.initializeAnalysisData();
@ -237,6 +241,13 @@ export class AllocationsPageComponent implements OnDestroy, OnInit {
}
};
this.platforms = {};
this.portfolioDetails = {
accounts: {},
filteredValueInPercentage: 0,
holdings: {},
platforms: {},
summary: undefined
};
this.positions = {};
this.sectors = {
[UNKNOWN_KEY]: {
@ -254,8 +265,6 @@ export class AllocationsPageComponent implements OnDestroy, OnInit {
}
public initializeAnalysisData() {
this.initialize();
for (const [
id,
{ name, valueInBaseCurrency, valueInPercentage }

View File

@ -0,0 +1,34 @@
import { NgModule } from '@angular/core';
import { RouterModule, Routes } from '@angular/router';
import { AuthGuard } from '@ghostfolio/client/core/auth.guard';
import { PersonalFinanceToolsPageComponent } from './personal-finance-tools-page.component';
import { products } from './products';
const routes: Routes = [
{
canActivate: [AuthGuard],
component: PersonalFinanceToolsPageComponent,
path: '',
title: $localize`Personal Finance Tools`
},
...products
.filter(({ key }) => {
return key !== 'ghostfolio';
})
.map(({ component, key, name }) => {
return {
canActivate: [AuthGuard],
path: `open-source-alternative-to-${key}`,
loadComponent: () =>
import(`./products/${key}-page.component`).then(() => component),
title: `Open Source Alternative to ${name}`
};
})
];
@NgModule({
imports: [RouterModule.forChild(routes)],
exports: [RouterModule]
})
export class PersonalFinanceToolsPageRoutingModule {}

View File

@ -0,0 +1,25 @@
import { Component, OnDestroy } from '@angular/core';
import { Subject } from 'rxjs';
import { products } from './products';
@Component({
host: { class: 'page' },
selector: 'gf-personal-finance-tools-page',
styleUrls: ['./personal-finance-tools-page.scss'],
templateUrl: './personal-finance-tools-page.html'
})
export class PersonalFinanceToolsPageComponent implements OnDestroy {
public products = products.filter(({ key }) => {
return key !== 'ghostfolio';
});
private unsubscribeSubject = new Subject<void>();
public constructor() {}
public ngOnDestroy() {
this.unsubscribeSubject.next();
this.unsubscribeSubject.complete();
}
}

View File

@ -0,0 +1,53 @@
<div class="container">
<div class="mb-5 row">
<div class="col">
<h3 class="d-none d-sm-block mb-3 text-center" i18n>
Discover Open Source Alternatives for Personal Finance Tools
</h3>
<div class="introduction mb-4">
<p>
This overview page features a curated collection of personal finance
tools compared to the open source alternative
<a [routerLink]="['/about']">Ghostfolio</a>. If you value
transparency, data privacy, and community collaboration, Ghostfolio
provides an excellent opportunity to take control of your financial
management.
</p>
<p>
Explore the links below to compare a variety of personal finance tools
with Ghostfolio.
</p>
</div>
<mat-card
*ngFor="let product of products"
appearance="outlined"
class="mb-3"
>
<mat-card-content>
<div class="container p-0">
<div class="flex-nowrap no-gutters row">
<a
class="d-flex overflow-hidden w-100"
title="Compare Ghostfolio to {{ product.name }}"
[routerLink]="['/resources', 'personal-finance-tools', 'open-source-alternative-to-' + product.key]"
>
<div class="flex-grow-1 overflow-hidden">
<div class="h6 m-0 text-truncate">
Open Source Alternative to {{ product.name }}
</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>
</div>
</div>
</div>

View File

@ -0,0 +1,13 @@
import { CommonModule } from '@angular/common';
import { CUSTOM_ELEMENTS_SCHEMA, NgModule } from '@angular/core';
import { MatCardModule } from '@angular/material/card';
import { PersonalFinanceToolsPageRoutingModule } from './personal-finance-tools-page-routing.module';
import { PersonalFinanceToolsPageComponent } from './personal-finance-tools-page.component';
@NgModule({
declarations: [PersonalFinanceToolsPageComponent],
imports: [CommonModule, MatCardModule, PersonalFinanceToolsPageRoutingModule],
schemas: [CUSTOM_ELEMENTS_SCHEMA]
})
export class PersonalFinanceToolsPageModule {}

View File

@ -0,0 +1,19 @@
:host {
color: rgb(var(--dark-primary-text));
display: block;
.introduction {
a {
color: rgba(var(--palette-primary-500), 1);
font-weight: 500;
&:hover {
color: rgba(var(--palette-primary-300), 1);
}
}
}
}
:host-context(.is-dark-theme) {
color: rgb(var(--light-primary-text));
}

View File

@ -0,0 +1,298 @@
<div class="container">
<div class="row">
<div class="col-md-8 offset-md-2">
<article>
<div class="mb-4 text-center">
<h1 class="mb-1">
<strong>Ghostfolio</strong>: The Open Source Alternative to
<strong>{{ product2.name }}</strong>
</h1>
</div>
<section class="mb-4">
<p>
Are you looking for an open source alternative to {{ product2.name
}}? <a [routerLink]="['/about']">Ghostfolio</a> is a powerful
portfolio management tool that provides individuals with a
comprehensive platform to track, analyze, and optimize their
investments. Whether you are an experienced investor or just
starting out, Ghostfolio offers an intuitive user interface and a
<a [routerLink]="['/features']">wide range of functionalities</a>
to help you make informed decisions and take control of your
financial future.
</p>
<p>
Ghostfolio is an open source software (OSS), providing a
cost-effective alternative to {{ product2.name }} making it
particularly suitable for individuals on a tight budget, such as
those
<a href="../en/blog/2023/07/exploring-the-path-to-fire"
>pursuing Financial Independence, Retire Early (FIRE)</a
>. By leveraging the collective efforts of a community of developers
and personal finance enthusiasts, Ghostfolio continuously enhances
its capabilities, security, and user experience.
</p>
<p>
Lets dive deeper into the detailed comparison table below to gain a
thorough understanding of how Ghostfolio positions itself relative
to {{ product2.name }}. We will explore various aspects such as
features, data privacy, pricing, and more, allowing you to make a
well-informed choice for your personal requirements.
</p>
</section>
<section class="mb-4">
<table class="gf-table w-100">
<thead>
<tr class="mat-mdc-header-row">
<th class="mat-mdc-header-cell px-1 py-2"></th>
<th class="mat-mdc-header-cell px-1 py-2">Ghostfolio</th>
<th class="mat-mdc-header-cell px-1 py-2">
{{ product2.name }}
</th>
</tr>
</thead>
<tbody>
<tr class="mat-mdc-row">
<td class="mat-mdc-cell px-3 py-2 text-right"></td>
<td class="mat-mdc-cell px-1 py-2">{{ product1.slogan }}</td>
<td class="mat-mdc-cell px-1 py-2">{{ product2.slogan }}</td>
</tr>
<tr class="mat-mdc-row">
<td class="mat-mdc-cell px-3 py-2 text-right" i18n>Founded</td>
<td class="mat-mdc-cell px-1 py-2">{{ product1.founded }}</td>
<td class="mat-mdc-cell px-1 py-2">{{ product2.founded }}</td>
</tr>
<tr class="mat-mdc-row">
<td class="mat-mdc-cell px-3 py-2 text-right" i18n>Origin</td>
<td class="mat-mdc-cell px-1 py-2">{{ product1.origin }}</td>
<td class="mat-mdc-cell px-1 py-2">{{ product2.origin }}</td>
</tr>
<tr class="mat-mdc-row">
<td class="mat-mdc-cell px-3 py-2 text-right" i18n>Region</td>
<td class="mat-mdc-cell px-1 py-2">{{ product1.region }}</td>
<td class="mat-mdc-cell px-1 py-2">{{ product2.region }}</td>
</tr>
<tr class="mat-mdc-row">
<td class="mat-mdc-cell px-3 py-2 text-right" i18n>
Available in
</td>
<td class="mat-mdc-cell px-1 py-2">
<ng-container
*ngFor="let language of product1.languages; last as isLast"
>{{ language }}{{ isLast ? '' : ', ' }}</ng-container
>
</td>
<td class="mat-mdc-cell px-1 py-2">
<ng-container
*ngFor="let language of product2.languages; last as isLast"
>{{ language }}{{ isLast ? '' : ', ' }}</ng-container
>
</td>
</tr>
<tr class="mat-mdc-row">
<td class="mat-mdc-cell px-3 py-2 text-right">
Open Source Software
</td>
<td class="mat-mdc-cell px-1 py-2">
<ng-container *ngIf="product1.isOpenSource === true" i18n
>✅ Yes</ng-container
><ng-container *ngIf="product1.isOpenSource === false" i18n
>❌ No</ng-container
>
</td>
<td class="mat-mdc-cell px-1 py-2">
<ng-container *ngIf="product2.isOpenSource === true" i18n
>✅ Yes</ng-container
><ng-container *ngIf="product2.isOpenSource === false" i18n
>❌ No
</ng-container>
</td>
</tr>
<tr class="mat-mdc-row">
<td class="mat-mdc-cell px-3 py-2 text-right" i18n>
Self-Hosting
</td>
<td class="mat-mdc-cell px-1 py-2">
<ng-container
*ngIf="product1.hasSelfHostingAbility === true"
i18n
>✅ Yes</ng-container
><ng-container
*ngIf="product1.hasSelfHostingAbility === false"
i18n
>❌ No</ng-container
>
</td>
<td class="mat-mdc-cell px-1 py-2">
<ng-container
*ngIf="product2.hasSelfHostingAbility === true"
i18n
>✅ Yes</ng-container
><ng-container
*ngIf="product2.hasSelfHostingAbility === false"
i18n
>❌ No</ng-container
>
</td>
</tr>
<tr class="mat-mdc-row">
<td class="mat-mdc-cell px-3 py-2 text-right" i18n>
Use anonymously
</td>
<td class="mat-mdc-cell px-1 py-2">
<ng-container *ngIf="product1.useAnonymously === true" i18n
>✅ Yes</ng-container
><ng-container *ngIf="product1.useAnonymously === false" i18n
>❌ No</ng-container
>
</td>
<td class="mat-mdc-cell px-1 py-2">
<ng-container *ngIf="product2.useAnonymously === true" i18n
>✅ Yes</ng-container
><ng-container *ngIf="product2.useAnonymously === false" i18n
>❌ No</ng-container
>
</td>
</tr>
<tr class="mat-mdc-row">
<td class="mat-mdc-cell px-3 py-2 text-right" i18n>
Free Plan
</td>
<td class="mat-mdc-cell px-1 py-2">
<ng-container *ngIf="product1.hasFreePlan === true" i18n
>✅ Yes</ng-container
><ng-container *ngIf="product1.hasFreePlan === false" i18n
>❌ No</ng-container
>
</td>
<td class="mat-mdc-cell px-1 py-2">
<ng-container *ngIf="product2.hasFreePlan === true" i18n
>✅ Yes</ng-container
><ng-container *ngIf="product2.hasFreePlan === false" i18n
>❌ No</ng-container
>
</td>
</tr>
<tr class="mat-mdc-row">
<td class="mat-mdc-cell px-3 py-2 text-right" i18n>Pricing</td>
<td class="mat-mdc-cell px-1 py-2">
Starting from {{ product1.pricingPerYear }} / year
</td>
<td class="mat-mdc-cell px-1 py-2">
<ng-container *ngIf="product2.pricingPerYear"
>Starting from {{ product2.pricingPerYear }} /
year</ng-container
>
</td>
</tr>
<tr *ngIf="product1.note || product2.note" class="mat-mdc-row">
<td class="mat-mdc-cell px-3 py-2 text-right" i18n>Notes</td>
<td class="mat-mdc-cell px-1 py-2">{{ product1.note }}</td>
<td class="mat-mdc-cell px-1 py-2">{{ product2.note }}</td>
</tr>
</tbody>
</table>
</section>
<section class="mb-4">
<p>
Please note that the information provided is based on our
independent research and analysis. This website is not affiliated
with {{ product2.name }} or any other product mentioned in the
comparison. As the landscape of personal finance tools evolves, it
is essential to verify any specific details or changes directly from
the respective product page. Data needs a refresh? Help us maintain
accurate data on
<a href="https://github.com/ghostfolio/ghostfolio">GitHub</a>.
</p>
</section>
<section class="call-to-action mb-4 py-3 rounded">
<h2 class="h4 mb-0 text-center">
Ready to take your <strong>investments</strong> to the
<strong>next level</strong>?
</h2>
<p class="lead mb-2 text-center" i18n>
Effortlessly track, analyze, and visualize your wealth with
Ghostfolio.
</p>
<div class="text-center">
<a color="primary" href="https://ghostfol.io" mat-flat-button>
Get Started
</a>
</div>
</section>
<section class="mb-4">
<ul class="list-inline">
<li class="list-inline-item">
<span class="badge badge-light">{{ product1.name }}</span>
</li>
<li class="list-inline-item">
<span class="badge badge-light">{{ product2.name }}</span>
</li>
<li class="list-inline-item">
<span class="badge badge-light">Alternative</span>
</li>
<li class="list-inline-item">
<span class="badge badge-light">App</span>
</li>
<li class="list-inline-item">
<span class="badge badge-light">Community</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">Investment</span>
</li>
<li class="list-inline-item">
<span class="badge badge-light">Investor</span>
</li>
<li class="list-inline-item">
<span class="badge badge-light">Open Source</span>
</li>
<li class="list-inline-item">
<span class="badge badge-light">OSS</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">Privacy</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">Software</span>
</li>
<li class="list-inline-item">
<span class="badge badge-light">Tool</span>
</li>
<li class="list-inline-item">
<span class="badge badge-light">User Experience</span>
</li>
<li class="list-inline-item">
<span class="badge badge-light">Wealth</span>
</li>
<li class="list-inline-item">
<span class="badge badge-light">Wealth Management</span>
</li>
</ul>
</section>
<nav aria-label="breadcrumb">
<ol class="breadcrumb">
<li class="breadcrumb-item">
<a i18n [routerLink]="['/resources', 'personal-finance-tools']"
>Personal Finance Tools</a
>
</li>
<li
aria-current="page"
class="active breadcrumb-item text-truncate"
>
{{ product2.name }}
</li>
</ol>
</nav>
</article>
</div>
</div>
</div>

View File

@ -0,0 +1,25 @@
:host {
color: rgb(var(--dark-primary-text));
display: block;
a {
color: rgba(var(--palette-primary-500), 1);
font-weight: 500;
&:hover {
color: rgba(var(--palette-primary-300), 1);
}
}
.call-to-action {
background-color: rgba(var(--palette-foreground-text), 0.02);
}
}
:host-context(.is-dark-theme) {
color: rgb(var(--light-primary-text));
.call-to-action {
background-color: rgba(var(--palette-foreground-text-dark), 0.02);
}
}

View File

@ -0,0 +1,338 @@
import { Product } from '@ghostfolio/common/interfaces';
import { AltooPageComponent } from './products/altoo-page.component';
import { CopilotMoneyPageComponent } from './products/copilot-money-page.component';
import { DeltaPageComponent } from './products/delta-page.component';
import { DivvyDiaryPageComponent } from './products/divvydiary-page.component';
import { ExirioPageComponent } from './products/exirio-page.component';
import { FolisharePageComponent } from './products/folishare-page.component';
import { GetquinPageComponent } from './products/getquin-page.component';
import { GoSpatzPageComponent } from './products/gospatz-page.component';
import { JustEtfPageComponent } from './products/justetf-page.component';
import { KuberaPageComponent } from './products/kubera-page.component';
import { MaybeFinancePageComponent } from './products/maybe-finance-page.component';
import { MonsePageComponent } from './products/monse-page.component';
import { ParqetPageComponent } from './products/parqet-page.component';
import { PlannixPageComponent } from './products/plannix-page.component';
import { PortfolioDividendTrackerPageComponent } from './products/portfolio-dividend-tracker-page.component';
import { PortseidoPageComponent } from './products/portseido-page.component';
import { ProjectionLabPageComponent } from './products/projectionlab-page.component';
import { SeekingAlphaPageComponent } from './products/seeking-alpha-page.component';
import { SharesightPageComponent } from './products/sharesight-page.component';
import { SimplePortfolioPageComponent } from './products/simple-portfolio-page.component';
import { SnowballAnalyticsPageComponent } from './products/snowball-analytics-page.component';
import { SumioPageComponent } from './products/sumio-page.component';
import { UtlunaPageComponent } from './products/utluna-page.component';
import { YeekateePageComponent } from './products/yeekatee-page.component';
export const products: Product[] = [
{
component: undefined,
founded: 2021,
hasFreePlan: true,
hasSelfHostingAbility: true,
isOpenSource: true,
key: 'ghostfolio',
languages: [
'Dutch',
'English',
'French',
'German',
'Italian',
'Portuguese',
'Spanish'
],
name: 'Ghostfolio',
origin: 'Switzerland',
pricingPerYear: '$19',
region: 'Global',
slogan: 'Open Source Wealth Management',
useAnonymously: true
},
{
component: AltooPageComponent,
founded: 2017,
hasSelfHostingAbility: false,
isOpenSource: false,
key: 'altoo',
name: 'Altoo Wealth Platform',
origin: 'Switzerland',
slogan: 'Simplicity for Complex Wealth'
},
{
component: CopilotMoneyPageComponent,
founded: 2019,
hasFreePlan: false,
hasSelfHostingAbility: false,
isOpenSource: false,
key: 'copilot-money',
name: 'Copilot Money',
origin: 'United States',
pricingPerYear: '$70',
slogan: 'Do money better with Copilot'
},
{
component: DeltaPageComponent,
founded: 2017,
hasFreePlan: true,
hasSelfHostingAbility: false,
isOpenSource: false,
key: 'delta',
name: 'Delta Investment Tracker',
note: 'Acquired by eToro',
origin: 'Belgium',
slogan: 'The app to track all your investments. Make smart moves only.'
},
{
component: DivvyDiaryPageComponent,
founded: 2019,
hasFreePlan: true,
hasSelfHostingAbility: false,
isOpenSource: false,
key: 'divvydiary',
languages: ['English', 'German'],
name: 'DivvyDiary',
origin: 'Germany',
pricingPerYear: '€65',
slogan: 'Your personal Dividend Calendar'
},
{
component: ExirioPageComponent,
founded: 2020,
hasFreePlan: true,
hasSelfHostingAbility: false,
isOpenSource: false,
key: 'exirio',
name: 'Exirio',
origin: 'United States',
pricingPerYear: '$100',
slogan: 'All your wealth, in one place.'
},
{
component: FolisharePageComponent,
hasFreePlan: true,
hasSelfHostingAbility: false,
isOpenSource: false,
key: 'folishare',
languages: ['English', 'German'],
name: 'folishare',
origin: 'Austria',
pricingPerYear: '$65',
slogan: 'Take control over your investments'
},
{
component: GetquinPageComponent,
founded: 2020,
hasFreePlan: true,
hasSelfHostingAbility: false,
isOpenSource: false,
key: 'getquin',
languages: ['English', 'German'],
name: 'getquin',
origin: 'Germany',
pricingPerYear: '€48',
slogan: 'Portfolio Tracker, Analysis & Community'
},
{
component: GoSpatzPageComponent,
hasFreePlan: true,
hasSelfHostingAbility: false,
isOpenSource: false,
key: 'gospatz',
name: 'goSPATZ',
origin: 'Germany',
slogan: 'Volle Kontrolle über deine Investitionen'
},
{
component: JustEtfPageComponent,
founded: 2011,
hasFreePlan: true,
hasSelfHostingAbility: false,
isOpenSource: false,
key: 'justetf',
name: 'justETF',
origin: 'Germany',
pricingPerYear: '€119',
slogan: 'ETF portfolios made simple'
},
{
component: KuberaPageComponent,
founded: 2019,
hasFreePlan: false,
hasSelfHostingAbility: false,
isOpenSource: false,
key: 'kubera',
name: 'Kubera®',
origin: 'United States',
pricingPerYear: '$150',
slogan: 'The Time Machine for your Net Worth'
},
{
component: MaybeFinancePageComponent,
founded: 2021,
hasSelfHostingAbility: false,
isOpenSource: false,
key: 'maybe-finance',
languages: ['English'],
name: 'Maybe Finance',
note: 'Sunset in 2023',
origin: 'United States',
pricingPerYear: '$145',
region: 'United States',
slogan: 'Your financial future, in your control'
},
{
component: MonsePageComponent,
hasFreePlan: false,
hasSelfHostingAbility: false,
isOpenSource: false,
key: 'monse',
name: 'Monse',
pricingPerYear: '$60',
slogan: 'Gain financial control and keep your data private.'
},
{
component: ParqetPageComponent,
founded: 2020,
hasSelfHostingAbility: false,
hasFreePlan: true,
isOpenSource: false,
key: 'parqet',
name: 'Parqet',
note: 'Originally named as Tresor One',
origin: 'Germany',
pricingPerYear: '€88',
region: 'Austria, Germany, Switzerland',
slogan: 'Dein Vermögen immer im Blick'
},
{
component: PlannixPageComponent,
founded: 2023,
hasSelfHostingAbility: false,
isOpenSource: false,
key: 'plannix',
name: 'Plannix',
origin: 'Italy',
slogan: 'Your Personal Finance Hub'
},
{
component: PortfolioDividendTrackerPageComponent,
hasFreePlan: false,
hasSelfHostingAbility: false,
isOpenSource: false,
key: 'portfolio-dividend-tracker',
languages: ['English', 'Dutch'],
name: 'Portfolio Dividend Tracker',
origin: 'Netherlands',
pricingPerYear: '€60',
slogan: 'Manage all your portfolios'
},
{
component: PortseidoPageComponent,
founded: 2021,
hasFreePlan: true,
hasSelfHostingAbility: false,
isOpenSource: false,
key: 'portseido',
languages: ['Dutch', 'English', 'French', 'German'],
name: 'Portseido',
origin: 'Thailand',
pricingPerYear: '$96',
slogan: 'Portfolio Performance and Dividend Tracker'
},
{
component: ProjectionLabPageComponent,
founded: 2021,
hasFreePlan: true,
hasSelfHostingAbility: true,
isOpenSource: false,
key: 'projectionlab',
name: 'ProjectionLab',
origin: 'United States',
pricingPerYear: '$108',
slogan: 'Build Financial Plans You Love.'
},
{
component: SeekingAlphaPageComponent,
founded: 2004,
hasFreePlan: false,
hasSelfHostingAbility: false,
isOpenSource: false,
key: 'seeking-alpha',
name: 'Seeking Alpha',
origin: 'United States',
pricingPerYear: '$239',
slogan: 'Stock Market Analysis & Tools for Investors'
},
{
component: SharesightPageComponent,
founded: 2007,
hasFreePlan: true,
hasSelfHostingAbility: false,
isOpenSource: false,
key: 'sharesight',
name: 'Sharesight',
origin: 'New Zealand',
pricingPerYear: '$135',
region: 'Global',
slogan: 'Stock Portfolio Tracker'
},
{
component: SimplePortfolioPageComponent,
hasFreePlan: true,
hasSelfHostingAbility: false,
isOpenSource: false,
key: 'simple-portfolio',
name: 'Simple Portfolio',
origin: 'Czech Republic',
pricingPerYear: '€80',
slogan: 'Stock Portfolio Tracker'
},
{
component: SnowballAnalyticsPageComponent,
founded: 2021,
hasFreePlan: true,
hasSelfHostingAbility: false,
isOpenSource: false,
key: 'snowball-analytics',
name: 'Snowball Analytics',
origin: 'France',
pricingPerYear: '$80',
slogan: 'Simple and powerful portfolio tracker'
},
{
component: SumioPageComponent,
hasFreePlan: true,
hasSelfHostingAbility: false,
isOpenSource: false,
key: 'sumio',
name: 'Sumio',
origin: 'Czech Republic',
pricingPerYear: '$20',
slogan: 'Sum up and build your wealth.'
},
{
component: UtlunaPageComponent,
hasFreePlan: true,
hasSelfHostingAbility: false,
isOpenSource: false,
key: 'utluna',
languages: ['English', 'French', 'German'],
name: 'Utluna',
origin: 'Switzerland',
pricingPerYear: '$300',
slogan: 'Your Portfolio. Revealed.',
useAnonymously: true
},
{
component: YeekateePageComponent,
founded: 2021,
hasSelfHostingAbility: false,
isOpenSource: false,
key: 'yeekatee',
name: 'yeekatee',
origin: 'Switzerland',
region: 'Switzerland',
slogan: 'Connect. Share. Invest.'
}
];

View File

@ -0,0 +1,24 @@
import { CommonModule } from '@angular/common';
import { Component } from '@angular/core';
import { MatButtonModule } from '@angular/material/button';
import { RouterModule } from '@angular/router';
import { products } from '../products';
@Component({
host: { class: 'page' },
imports: [CommonModule, MatButtonModule, RouterModule],
selector: 'gf-altoo-page',
standalone: true,
styleUrls: ['../product-page-template.scss'],
templateUrl: '../product-page-template.html'
})
export class AltooPageComponent {
public product1 = products.find(({ key }) => {
return key === 'ghostfolio';
});
public product2 = products.find(({ key }) => {
return key === 'altoo';
});
}

View File

@ -0,0 +1,24 @@
import { CommonModule } from '@angular/common';
import { Component } from '@angular/core';
import { MatButtonModule } from '@angular/material/button';
import { RouterModule } from '@angular/router';
import { products } from '../products';
@Component({
host: { class: 'page' },
imports: [CommonModule, MatButtonModule, RouterModule],
selector: 'gf-copilot-money-page',
standalone: true,
styleUrls: ['../product-page-template.scss'],
templateUrl: '../product-page-template.html'
})
export class CopilotMoneyPageComponent {
public product1 = products.find(({ key }) => {
return key === 'ghostfolio';
});
public product2 = products.find(({ key }) => {
return key === 'copilot-money';
});
}

View File

@ -0,0 +1,24 @@
import { CommonModule } from '@angular/common';
import { Component } from '@angular/core';
import { MatButtonModule } from '@angular/material/button';
import { RouterModule } from '@angular/router';
import { products } from '../products';
@Component({
host: { class: 'page' },
imports: [CommonModule, MatButtonModule, RouterModule],
selector: 'gf-delta-page',
standalone: true,
styleUrls: ['../product-page-template.scss'],
templateUrl: '../product-page-template.html'
})
export class DeltaPageComponent {
public product1 = products.find(({ key }) => {
return key === 'ghostfolio';
});
public product2 = products.find(({ key }) => {
return key === 'delta';
});
}

View File

@ -0,0 +1,24 @@
import { CommonModule } from '@angular/common';
import { Component } from '@angular/core';
import { MatButtonModule } from '@angular/material/button';
import { RouterModule } from '@angular/router';
import { products } from '../products';
@Component({
host: { class: 'page' },
imports: [CommonModule, MatButtonModule, RouterModule],
selector: 'gf-divvy-diary-page',
standalone: true,
styleUrls: ['../product-page-template.scss'],
templateUrl: '../product-page-template.html'
})
export class DivvyDiaryPageComponent {
public product1 = products.find(({ key }) => {
return key === 'ghostfolio';
});
public product2 = products.find(({ key }) => {
return key === 'divvydiary';
});
}

View File

@ -0,0 +1,24 @@
import { CommonModule } from '@angular/common';
import { Component } from '@angular/core';
import { MatButtonModule } from '@angular/material/button';
import { RouterModule } from '@angular/router';
import { products } from '../products';
@Component({
host: { class: 'page' },
imports: [CommonModule, MatButtonModule, RouterModule],
selector: 'gf-exirio-page',
standalone: true,
styleUrls: ['../product-page-template.scss'],
templateUrl: '../product-page-template.html'
})
export class ExirioPageComponent {
public product1 = products.find(({ key }) => {
return key === 'ghostfolio';
});
public product2 = products.find(({ key }) => {
return key === 'exirio';
});
}

View File

@ -0,0 +1,24 @@
import { CommonModule } from '@angular/common';
import { Component } from '@angular/core';
import { MatButtonModule } from '@angular/material/button';
import { RouterModule } from '@angular/router';
import { products } from '../products';
@Component({
host: { class: 'page' },
imports: [CommonModule, MatButtonModule, RouterModule],
selector: 'gf-folishare-page',
standalone: true,
styleUrls: ['../product-page-template.scss'],
templateUrl: '../product-page-template.html'
})
export class FolisharePageComponent {
public product1 = products.find(({ key }) => {
return key === 'ghostfolio';
});
public product2 = products.find(({ key }) => {
return key === 'folishare';
});
}

View File

@ -0,0 +1,24 @@
import { CommonModule } from '@angular/common';
import { Component } from '@angular/core';
import { MatButtonModule } from '@angular/material/button';
import { RouterModule } from '@angular/router';
import { products } from '../products';
@Component({
host: { class: 'page' },
imports: [CommonModule, MatButtonModule, RouterModule],
selector: 'gf-getquin-page',
standalone: true,
styleUrls: ['../product-page-template.scss'],
templateUrl: '../product-page-template.html'
})
export class GetquinPageComponent {
public product1 = products.find(({ key }) => {
return key === 'ghostfolio';
});
public product2 = products.find(({ key }) => {
return key === 'getquin';
});
}

View File

@ -0,0 +1,24 @@
import { CommonModule } from '@angular/common';
import { Component } from '@angular/core';
import { MatButtonModule } from '@angular/material/button';
import { RouterModule } from '@angular/router';
import { products } from '../products';
@Component({
host: { class: 'page' },
imports: [CommonModule, MatButtonModule, RouterModule],
selector: 'gf-gospatz-page',
standalone: true,
styleUrls: ['../product-page-template.scss'],
templateUrl: '../product-page-template.html'
})
export class GoSpatzPageComponent {
public product1 = products.find(({ key }) => {
return key === 'ghostfolio';
});
public product2 = products.find(({ key }) => {
return key === 'gospatz';
});
}

View File

@ -0,0 +1,24 @@
import { CommonModule } from '@angular/common';
import { Component } from '@angular/core';
import { MatButtonModule } from '@angular/material/button';
import { RouterModule } from '@angular/router';
import { products } from '../products';
@Component({
host: { class: 'page' },
imports: [CommonModule, MatButtonModule, RouterModule],
selector: 'gf-justetf-page',
standalone: true,
styleUrls: ['../product-page-template.scss'],
templateUrl: '../product-page-template.html'
})
export class JustEtfPageComponent {
public product1 = products.find(({ key }) => {
return key === 'ghostfolio';
});
public product2 = products.find(({ key }) => {
return key === 'justetf';
});
}

View File

@ -0,0 +1,24 @@
import { CommonModule } from '@angular/common';
import { Component } from '@angular/core';
import { MatButtonModule } from '@angular/material/button';
import { RouterModule } from '@angular/router';
import { products } from '../products';
@Component({
host: { class: 'page' },
imports: [CommonModule, MatButtonModule, RouterModule],
selector: 'gf-kubera-page',
standalone: true,
styleUrls: ['../product-page-template.scss'],
templateUrl: '../product-page-template.html'
})
export class KuberaPageComponent {
public product1 = products.find(({ key }) => {
return key === 'ghostfolio';
});
public product2 = products.find(({ key }) => {
return key === 'kubera';
});
}

View File

@ -0,0 +1,24 @@
import { CommonModule } from '@angular/common';
import { Component } from '@angular/core';
import { MatButtonModule } from '@angular/material/button';
import { RouterModule } from '@angular/router';
import { products } from '../products';
@Component({
host: { class: 'page' },
imports: [CommonModule, MatButtonModule, RouterModule],
selector: 'gf-maybe-finance-page',
standalone: true,
styleUrls: ['../product-page-template.scss'],
templateUrl: '../product-page-template.html'
})
export class MaybeFinancePageComponent {
public product1 = products.find(({ key }) => {
return key === 'ghostfolio';
});
public product2 = products.find(({ key }) => {
return key === 'maybe-finance';
});
}

View File

@ -0,0 +1,24 @@
import { CommonModule } from '@angular/common';
import { Component } from '@angular/core';
import { MatButtonModule } from '@angular/material/button';
import { RouterModule } from '@angular/router';
import { products } from '../products';
@Component({
host: { class: 'page' },
imports: [CommonModule, MatButtonModule, RouterModule],
selector: 'gf-monse-page',
standalone: true,
styleUrls: ['../product-page-template.scss'],
templateUrl: '../product-page-template.html'
})
export class MonsePageComponent {
public product1 = products.find(({ key }) => {
return key === 'ghostfolio';
});
public product2 = products.find(({ key }) => {
return key === 'monse';
});
}

View File

@ -0,0 +1,24 @@
import { CommonModule } from '@angular/common';
import { Component } from '@angular/core';
import { MatButtonModule } from '@angular/material/button';
import { RouterModule } from '@angular/router';
import { products } from '../products';
@Component({
host: { class: 'page' },
imports: [CommonModule, MatButtonModule, RouterModule],
selector: 'gf-parqet-page',
standalone: true,
styleUrls: ['../product-page-template.scss'],
templateUrl: '../product-page-template.html'
})
export class ParqetPageComponent {
public product1 = products.find(({ key }) => {
return key === 'ghostfolio';
});
public product2 = products.find(({ key }) => {
return key === 'parqet';
});
}

View File

@ -0,0 +1,24 @@
import { CommonModule } from '@angular/common';
import { Component } from '@angular/core';
import { MatButtonModule } from '@angular/material/button';
import { RouterModule } from '@angular/router';
import { products } from '../products';
@Component({
host: { class: 'page' },
imports: [CommonModule, MatButtonModule, RouterModule],
selector: 'gf-plannix-page',
standalone: true,
styleUrls: ['../product-page-template.scss'],
templateUrl: '../product-page-template.html'
})
export class PlannixPageComponent {
public product1 = products.find(({ key }) => {
return key === 'ghostfolio';
});
public product2 = products.find(({ key }) => {
return key === 'plannix';
});
}

View File

@ -0,0 +1,24 @@
import { CommonModule } from '@angular/common';
import { Component } from '@angular/core';
import { MatButtonModule } from '@angular/material/button';
import { RouterModule } from '@angular/router';
import { products } from '../products';
@Component({
host: { class: 'page' },
imports: [CommonModule, MatButtonModule, RouterModule],
selector: 'gf-portfolio-dividend-tracker-page',
standalone: true,
styleUrls: ['../product-page-template.scss'],
templateUrl: '../product-page-template.html'
})
export class PortfolioDividendTrackerPageComponent {
public product1 = products.find(({ key }) => {
return key === 'ghostfolio';
});
public product2 = products.find(({ key }) => {
return key === 'portfolio-dividend-tracker';
});
}

View File

@ -0,0 +1,24 @@
import { CommonModule } from '@angular/common';
import { Component } from '@angular/core';
import { MatButtonModule } from '@angular/material/button';
import { RouterModule } from '@angular/router';
import { products } from '../products';
@Component({
host: { class: 'page' },
imports: [CommonModule, MatButtonModule, RouterModule],
selector: 'gf-portseido-page',
standalone: true,
styleUrls: ['../product-page-template.scss'],
templateUrl: '../product-page-template.html'
})
export class PortseidoPageComponent {
public product1 = products.find(({ key }) => {
return key === 'ghostfolio';
});
public product2 = products.find(({ key }) => {
return key === 'portseido';
});
}

View File

@ -0,0 +1,24 @@
import { CommonModule } from '@angular/common';
import { Component } from '@angular/core';
import { MatButtonModule } from '@angular/material/button';
import { RouterModule } from '@angular/router';
import { products } from '../products';
@Component({
host: { class: 'page' },
imports: [CommonModule, MatButtonModule, RouterModule],
selector: 'gf-projection-lab-page',
standalone: true,
styleUrls: ['../product-page-template.scss'],
templateUrl: '../product-page-template.html'
})
export class ProjectionLabPageComponent {
public product1 = products.find(({ key }) => {
return key === 'ghostfolio';
});
public product2 = products.find(({ key }) => {
return key === 'projectionlab';
});
}

View File

@ -0,0 +1,24 @@
import { CommonModule } from '@angular/common';
import { Component } from '@angular/core';
import { MatButtonModule } from '@angular/material/button';
import { RouterModule } from '@angular/router';
import { products } from '../products';
@Component({
host: { class: 'page' },
imports: [CommonModule, MatButtonModule, RouterModule],
selector: 'gf-seeking-alpha-page',
standalone: true,
styleUrls: ['../product-page-template.scss'],
templateUrl: '../product-page-template.html'
})
export class SeekingAlphaPageComponent {
public product1 = products.find(({ key }) => {
return key === 'ghostfolio';
});
public product2 = products.find(({ key }) => {
return key === 'seeking-alpha';
});
}

View File

@ -0,0 +1,24 @@
import { CommonModule } from '@angular/common';
import { Component } from '@angular/core';
import { MatButtonModule } from '@angular/material/button';
import { RouterModule } from '@angular/router';
import { products } from '../products';
@Component({
host: { class: 'page' },
imports: [CommonModule, MatButtonModule, RouterModule],
selector: 'gf-sharesight-page',
standalone: true,
styleUrls: ['../product-page-template.scss'],
templateUrl: '../product-page-template.html'
})
export class SharesightPageComponent {
public product1 = products.find(({ key }) => {
return key === 'ghostfolio';
});
public product2 = products.find(({ key }) => {
return key === 'sharesight';
});
}

View File

@ -0,0 +1,24 @@
import { CommonModule } from '@angular/common';
import { Component } from '@angular/core';
import { MatButtonModule } from '@angular/material/button';
import { RouterModule } from '@angular/router';
import { products } from '../products';
@Component({
host: { class: 'page' },
imports: [CommonModule, MatButtonModule, RouterModule],
selector: 'gf-simple-portfolio-page',
standalone: true,
styleUrls: ['../product-page-template.scss'],
templateUrl: '../product-page-template.html'
})
export class SimplePortfolioPageComponent {
public product1 = products.find(({ key }) => {
return key === 'ghostfolio';
});
public product2 = products.find(({ key }) => {
return key === 'simple-portfolio';
});
}

View File

@ -0,0 +1,24 @@
import { CommonModule } from '@angular/common';
import { Component } from '@angular/core';
import { MatButtonModule } from '@angular/material/button';
import { RouterModule } from '@angular/router';
import { products } from '../products';
@Component({
host: { class: 'page' },
imports: [CommonModule, MatButtonModule, RouterModule],
selector: 'gf-snowball-analytics-page',
standalone: true,
styleUrls: ['../product-page-template.scss'],
templateUrl: '../product-page-template.html'
})
export class SnowballAnalyticsPageComponent {
public product1 = products.find(({ key }) => {
return key === 'ghostfolio';
});
public product2 = products.find(({ key }) => {
return key === 'snowball-analytics';
});
}

View File

@ -0,0 +1,24 @@
import { CommonModule } from '@angular/common';
import { Component } from '@angular/core';
import { MatButtonModule } from '@angular/material/button';
import { RouterModule } from '@angular/router';
import { products } from '../products';
@Component({
host: { class: 'page' },
imports: [CommonModule, MatButtonModule, RouterModule],
selector: 'gf-sumio-page',
standalone: true,
styleUrls: ['../product-page-template.scss'],
templateUrl: '../product-page-template.html'
})
export class SumioPageComponent {
public product1 = products.find(({ key }) => {
return key === 'ghostfolio';
});
public product2 = products.find(({ key }) => {
return key === 'sumio';
});
}

View File

@ -0,0 +1,24 @@
import { CommonModule } from '@angular/common';
import { Component } from '@angular/core';
import { MatButtonModule } from '@angular/material/button';
import { RouterModule } from '@angular/router';
import { products } from '../products';
@Component({
host: { class: 'page' },
imports: [CommonModule, MatButtonModule, RouterModule],
selector: 'gf-utluna-page',
standalone: true,
styleUrls: ['../product-page-template.scss'],
templateUrl: '../product-page-template.html'
})
export class UtlunaPageComponent {
public product1 = products.find(({ key }) => {
return key === 'ghostfolio';
});
public product2 = products.find(({ key }) => {
return key === 'utluna';
});
}

View File

@ -0,0 +1,24 @@
import { CommonModule } from '@angular/common';
import { Component } from '@angular/core';
import { MatButtonModule } from '@angular/material/button';
import { RouterModule } from '@angular/router';
import { products } from '../products';
@Component({
host: { class: 'page' },
imports: [CommonModule, MatButtonModule, RouterModule],
selector: 'gf-yeekatee-page',
standalone: true,
styleUrls: ['../product-page-template.scss'],
templateUrl: '../product-page-template.html'
})
export class YeekateePageComponent {
public product1 = products.find(({ key }) => {
return key === 'ghostfolio';
});
public product2 = products.find(({ key }) => {
return key === 'yeekatee';
});
}

View File

@ -10,7 +10,14 @@ const routes: Routes = [
component: ResourcesPageComponent,
path: '',
title: $localize`Resources`
}
},
...['personal-finance-tools'].map((path) => ({
path,
loadChildren: () =>
import(
'./personal-finance-tools/personal-finance-tools-page.module'
).then((m) => m.PersonalFinanceToolsPageModule)
}))
];
@NgModule({

View File

@ -2,7 +2,7 @@
<div class="row">
<div class="col">
<h1 class="d-none d-sm-block h3 mb-3 text-center" i18n>Resources</h1>
<h2 class="h4 mb-3">Guides</h2>
<h2 class="h4 mb-3" i18n>Guides</h2>
<div class="mb-5">
<div class="mb-4 media">
<div class="media-body">
@ -36,7 +36,7 @@
</div>
</div>
</div>
<h2 class="h4 mb-3">Market</h2>
<h2 class="h4 mb-3" i18n>Markets</h2>
<div class="mb-5">
<div class="mb-4 media">
<div class="media-body">
@ -72,7 +72,7 @@
</div>
</div>
</div>
<h2 class="h4 mb-3">Glossary</h2>
<h2 class="h4 mb-3" i18n>Glossary</h2>
<div>
<div class="mb-4 media">
<div class="media-body">
@ -170,6 +170,21 @@
</div>
</div>
</div>
<div class="mb-4 media">
<div class="media-body">
<h3 class="h5 mt-0">Personal Finance Tools</h3>
<div class="mb-1">
Personal finance tools are software applications that help
individuals manage their money, track expenses, set budgets,
monitor investments, and make informed financial decisions.
</div>
<div>
<a [routerLink]="['/resources', 'personal-finance-tools']"
>Personal Finance Tools →</a
>
</div>
</div>
</div>
<div class="mb-4 media">
<div class="media-body">
<h3 class="h5 mt-0">Stagflation</h3>

View File

@ -19,6 +19,7 @@ import { DataSource, MarketData, Platform, Prisma } from '@prisma/client';
import { JobStatus } from 'bull';
import { format, parseISO } from 'date-fns';
import { Observable, map } from 'rxjs';
import { DataService } from './data.service';
@Injectable({

View File

@ -6,410 +6,510 @@
http://www.sitemaps.org/schemas/sitemap/0.9/sitemap.xsd">
<url>
<loc>https://ghostfol.io/de</loc>
<lastmod>2023-07-01T00:00:00+00:00</lastmod>
<lastmod>2023-07-10T00:00:00+00:00</lastmod>
</url>
<url>
<loc>https://ghostfol.io/de/blog</loc>
<lastmod>2023-07-01T00:00:00+00:00</lastmod>
<lastmod>2023-07-10T00:00:00+00:00</lastmod>
</url>
<url>
<loc>https://ghostfol.io/de/blog/2021/07/hallo-ghostfolio</loc>
<lastmod>2023-07-01T00:00:00+00:00</lastmod>
<lastmod>2023-07-10T00:00:00+00:00</lastmod>
</url>
<url>
<loc>https://ghostfol.io/de/blog/2023/01/ghostfolio-auf-sackgeld-vorgestellt</loc>
<lastmod>2023-07-01T00:00:00+00:00</lastmod>
<lastmod>2023-07-10T00:00:00+00:00</lastmod>
</url>
<url>
<loc>https://ghostfol.io/de/features</loc>
<lastmod>2023-07-01T00:00:00+00:00</lastmod>
<lastmod>2023-07-10T00:00:00+00:00</lastmod>
</url>
<url>
<loc>https://ghostfol.io/de/haeufig-gestellte-fragen</loc>
<lastmod>2023-07-01T00:00:00+00:00</lastmod>
<lastmod>2023-07-10T00:00:00+00:00</lastmod>
</url>
<url>
<loc>https://ghostfol.io/de/maerkte</loc>
<changefreq>daily</changefreq>
<lastmod>2023-07-01T00:00:00+00:00</lastmod>
<lastmod>2023-07-10T00:00:00+00:00</lastmod>
</url>
<url>
<loc>https://ghostfol.io/de/open</loc>
<changefreq>daily</changefreq>
<lastmod>2023-07-01T00:00:00+00:00</lastmod>
<lastmod>2023-07-10T00:00:00+00:00</lastmod>
</url>
<url>
<loc>https://ghostfol.io/de/preise</loc>
<lastmod>2023-07-01T00:00:00+00:00</lastmod>
<lastmod>2023-07-10T00:00:00+00:00</lastmod>
</url>
<url>
<loc>https://ghostfol.io/de/registrierung</loc>
<lastmod>2023-07-01T00:00:00+00:00</lastmod>
<lastmod>2023-07-10T00:00:00+00:00</lastmod>
</url>
<url>
<loc>https://ghostfol.io/de/ressourcen</loc>
<lastmod>2023-07-01T00:00:00+00:00</lastmod>
<lastmod>2023-07-10T00:00:00+00:00</lastmod>
</url>
<url>
<loc>https://ghostfol.io/de/ueber-uns</loc>
<lastmod>2023-07-01T00:00:00+00:00</lastmod>
<lastmod>2023-07-10T00:00:00+00:00</lastmod>
</url>
<url>
<loc>https://ghostfol.io/de/ueber-uns/changelog</loc>
<lastmod>2023-07-01T00:00:00+00:00</lastmod>
<lastmod>2023-07-10T00:00:00+00:00</lastmod>
</url>
<url>
<loc>https://ghostfol.io/de/ueber-uns/datenschutzbestimmungen</loc>
<lastmod>2023-07-01T00:00:00+00:00</lastmod>
<lastmod>2023-07-10T00:00:00+00:00</lastmod>
</url>
<url>
<loc>https://ghostfol.io/de/ueber-uns/lizenz</loc>
<lastmod>2023-07-01T00:00:00+00:00</lastmod>
<lastmod>2023-07-10T00:00:00+00:00</lastmod>
</url>
<url>
<loc>https://ghostfol.io/en</loc>
<lastmod>2023-07-01T00:00:00+00:00</lastmod>
<lastmod>2023-07-10T00:00:00+00:00</lastmod>
</url>
<url>
<loc>https://ghostfol.io/en/about</loc>
<lastmod>2023-07-01T00:00:00+00:00</lastmod>
<lastmod>2023-07-10T00:00:00+00:00</lastmod>
</url>
<url>
<loc>https://ghostfol.io/en/about/changelog</loc>
<lastmod>2023-07-01T00:00:00+00:00</lastmod>
<lastmod>2023-07-10T00:00:00+00:00</lastmod>
</url>
<url>
<loc>https://ghostfol.io/en/about/license</loc>
<lastmod>2023-07-01T00:00:00+00:00</lastmod>
<lastmod>2023-07-10T00:00:00+00:00</lastmod>
</url>
<url>
<loc>https://ghostfol.io/en/blog</loc>
<lastmod>2023-07-01T00:00:00+00:00</lastmod>
<lastmod>2023-07-10T00:00:00+00:00</lastmod>
</url>
<url>
<loc>https://ghostfol.io/en/blog/2021/07/hello-ghostfolio</loc>
<lastmod>2023-07-01T00:00:00+00:00</lastmod>
<lastmod>2023-07-10T00:00:00+00:00</lastmod>
</url>
<url>
<loc>https://ghostfol.io/en/blog/2022/01/ghostfolio-first-months-in-open-source</loc>
<lastmod>2023-07-01T00:00:00+00:00</lastmod>
<lastmod>2023-07-10T00:00:00+00:00</lastmod>
</url>
<url>
<loc>https://ghostfol.io/en/blog/2022/07/ghostfolio-meets-internet-identity</loc>
<lastmod>2023-07-01T00:00:00+00:00</lastmod>
<lastmod>2023-07-10T00: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>2023-07-01T00:00:00+00:00</lastmod>
<lastmod>2023-07-10T00:00:00+00:00</lastmod>
</url>
<url>
<loc>https://ghostfol.io/en/blog/2022/08/500-stars-on-github</loc>
<lastmod>2023-07-01T00:00:00+00:00</lastmod>
<lastmod>2023-07-10T00:00:00+00:00</lastmod>
</url>
<url>
<loc>https://ghostfol.io/en/blog/2022/10/hacktoberfest-2022</loc>
<lastmod>2023-07-01T00:00:00+00:00</lastmod>
<lastmod>2023-07-10T00:00:00+00:00</lastmod>
</url>
<url>
<loc>https://ghostfol.io/en/blog/2022/11/black-friday-2022</loc>
<lastmod>2023-07-01T00:00:00+00:00</lastmod>
<lastmod>2023-07-10T00:00:00+00:00</lastmod>
</url>
<url>
<loc>https://ghostfol.io/en/blog/2022/12/the-importance-of-tracking-your-personal-finances</loc>
<lastmod>2023-07-01T00:00:00+00:00</lastmod>
<lastmod>2023-07-10T00:00:00+00:00</lastmod>
</url>
<url>
<loc>https://ghostfol.io/en/blog/2023/02/ghostfolio-meets-umbrel</loc>
<lastmod>2023-07-01T00:00:00+00:00</lastmod>
<lastmod>2023-07-10T00:00:00+00:00</lastmod>
</url>
<url>
<loc>https://ghostfol.io/en/blog/2023/03/ghostfolio-reaches-1000-stars-on-github</loc>
<lastmod>2023-07-01T00:00:00+00:00</lastmod>
<lastmod>2023-07-10T00:00:00+00:00</lastmod>
</url>
<url>
<loc>https://ghostfol.io/en/blog/2023/05/unlock-your-financial-potential-with-ghostfolio</loc>
<lastmod>2023-07-01T00:00:00+00:00</lastmod>
<lastmod>2023-07-10T00:00:00+00:00</lastmod>
</url>
<url>
<loc>https://ghostfol.io/en/blog/2023/07/exploring-the-path-to-fire</loc>
<lastmod>2023-07-01T00:00:00+00:00</lastmod>
<lastmod>2023-07-10T00:00:00+00:00</lastmod>
</url>
<url>
<loc>https://ghostfol.io/en/faq</loc>
<lastmod>2023-07-01T00:00:00+00:00</lastmod>
<lastmod>2023-07-10T00:00:00+00:00</lastmod>
</url>
<url>
<loc>https://ghostfol.io/en/features</loc>
<lastmod>2023-07-01T00:00:00+00:00</lastmod>
<lastmod>2023-07-10T00:00:00+00:00</lastmod>
</url>
<url>
<loc>https://ghostfol.io/en/markets</loc>
<changefreq>daily</changefreq>
<lastmod>2023-07-01T00:00:00+00:00</lastmod>
<lastmod>2023-07-10T00:00:00+00:00</lastmod>
</url>
<url>
<loc>https://ghostfol.io/en/open</loc>
<changefreq>daily</changefreq>
<lastmod>2023-07-01T00:00:00+00:00</lastmod>
<lastmod>2023-07-10T00:00:00+00:00</lastmod>
</url>
<url>
<loc>https://ghostfol.io/en/pricing</loc>
<lastmod>2023-07-01T00:00:00+00:00</lastmod>
<lastmod>2023-07-10T00:00:00+00:00</lastmod>
</url>
<url>
<loc>https://ghostfol.io/en/register</loc>
<lastmod>2023-07-01T00:00:00+00:00</lastmod>
<lastmod>2023-07-10T00:00:00+00:00</lastmod>
</url>
<url>
<loc>https://ghostfol.io/en/resources</loc>
<lastmod>2023-07-01T00:00:00+00:00</lastmod>
<lastmod>2023-07-10T00:00:00+00:00</lastmod>
</url>
<url>
<loc>https://ghostfol.io/en/resources/personal-finance-tools</loc>
<lastmod>2023-07-10T00:00:00+00:00</lastmod>
</url>
<url>
<loc>https://ghostfol.io/en/resources/personal-finance-tools/open-source-alternative-to-altoo</loc>
<lastmod>2023-07-10T00:00:00+00:00</lastmod>
</url>
<url>
<loc>https://ghostfol.io/en/resources/personal-finance-tools/open-source-alternative-to-copilot-money</loc>
<lastmod>2023-07-10T00:00:00+00:00</lastmod>
</url>
<url>
<loc>https://ghostfol.io/en/resources/personal-finance-tools/open-source-alternative-to-delta</loc>
<lastmod>2023-07-10T00:00:00+00:00</lastmod>
</url>
<url>
<loc>https://ghostfol.io/en/resources/personal-finance-tools/open-source-alternative-to-divvydiary</loc>
<lastmod>2023-07-10T00:00:00+00:00</lastmod>
</url>
<url>
<loc>https://ghostfol.io/en/resources/personal-finance-tools/open-source-alternative-to-exirio</loc>
<lastmod>2023-07-10T00:00:00+00:00</lastmod>
</url>
<url>
<loc>https://ghostfol.io/en/resources/personal-finance-tools/open-source-alternative-to-folishare</loc>
<lastmod>2023-07-10T00:00:00+00:00</lastmod>
</url>
<url>
<loc>https://ghostfol.io/en/resources/personal-finance-tools/open-source-alternative-to-getquin</loc>
<lastmod>2023-07-10T00:00:00+00:00</lastmod>
</url>
<url>
<loc>https://ghostfol.io/en/resources/personal-finance-tools/open-source-alternative-to-gospatz</loc>
<lastmod>2023-07-10T00:00:00+00:00</lastmod>
</url>
<url>
<loc>https://ghostfol.io/en/resources/personal-finance-tools/open-source-alternative-to-justetf</loc>
<lastmod>2023-07-10T00:00:00+00:00</lastmod>
</url>
<url>
<loc>https://ghostfol.io/en/resources/personal-finance-tools/open-source-alternative-to-kubera</loc>
<lastmod>2023-07-10T00:00:00+00:00</lastmod>
</url>
<url>
<loc>https://ghostfol.io/en/resources/personal-finance-tools/open-source-alternative-to-maybe-finance</loc>
<lastmod>2023-07-10T00:00:00+00:00</lastmod>
</url>
<url>
<loc>https://ghostfol.io/en/resources/personal-finance-tools/open-source-alternative-to-monse</loc>
<lastmod>2023-07-10T00:00:00+00:00</lastmod>
</url>
<url>
<loc>https://ghostfol.io/en/resources/personal-finance-tools/open-source-alternative-to-parqet</loc>
<lastmod>2023-07-10T00:00:00+00:00</lastmod>
</url>
<url>
<loc>https://ghostfol.io/en/resources/personal-finance-tools/open-source-alternative-to-plannix</loc>
<lastmod>2023-07-10T00:00:00+00:00</lastmod>
</url>
<url>
<loc>https://ghostfol.io/en/resources/personal-finance-tools/open-source-alternative-to-portfolio-dividend-tracker</loc>
<lastmod>2023-07-10T00:00:00+00:00</lastmod>
</url>
<url>
<loc>https://ghostfol.io/en/resources/personal-finance-tools/open-source-alternative-to-portseido</loc>
<lastmod>2023-07-10T00:00:00+00:00</lastmod>
</url>
<url>
<loc>https://ghostfol.io/en/resources/personal-finance-tools/open-source-alternative-to-projectionlab</loc>
<lastmod>2023-07-10T00:00:00+00:00</lastmod>
</url>
<url>
<loc>https://ghostfol.io/en/resources/personal-finance-tools/open-source-alternative-to-seeking-alpha</loc>
<lastmod>2023-07-10T00:00:00+00:00</lastmod>
</url>
<url>
<loc>https://ghostfol.io/en/resources/personal-finance-tools/open-source-alternative-to-sharesight</loc>
<lastmod>2023-07-10T00:00:00+00:00</lastmod>
</url>
<url>
<loc>https://ghostfol.io/en/resources/personal-finance-tools/open-source-alternative-to-simple-portfolio</loc>
<lastmod>2023-07-10T00:00:00+00:00</lastmod>
</url>
<url>
<loc>https://ghostfol.io/en/resources/personal-finance-tools/open-source-alternative-to-snowball-analytics</loc>
<lastmod>2023-07-10T00:00:00+00:00</lastmod>
</url>
<url>
<loc>https://ghostfol.io/en/resources/personal-finance-tools/open-source-alternative-to-sumio</loc>
<lastmod>2023-07-10T00:00:00+00:00</lastmod>
</url>
<url>
<loc>https://ghostfol.io/en/resources/personal-finance-tools/open-source-alternative-to-utluna</loc>
<lastmod>2023-07-10T00:00:00+00:00</lastmod>
</url>
<url>
<loc>https://ghostfol.io/en/resources/personal-finance-tools/open-source-alternative-to-yeekatee</loc>
<lastmod>2023-07-10T00:00:00+00:00</lastmod>
</url>
<url>
<loc>https://ghostfol.io/es</loc>
<lastmod>2023-07-01T00:00:00+00:00</lastmod>
<lastmod>2023-07-10T00:00:00+00:00</lastmod>
</url>
<url>
<loc>https://ghostfol.io/es/funcionalidades</loc>
<lastmod>2023-07-01T00:00:00+00:00</lastmod>
<lastmod>2023-07-10T00:00:00+00:00</lastmod>
</url>
<url>
<loc>https://ghostfol.io/es/mercados</loc>
<changefreq>daily</changefreq>
<lastmod>2023-07-01T00:00:00+00:00</lastmod>
<lastmod>2023-07-10T00:00:00+00:00</lastmod>
</url>
<url>
<loc>https://ghostfol.io/es/open</loc>
<changefreq>daily</changefreq>
<lastmod>2023-07-01T00:00:00+00:00</lastmod>
<lastmod>2023-07-10T00:00:00+00:00</lastmod>
</url>
<url>
<loc>https://ghostfol.io/es/precios</loc>
<lastmod>2023-07-01T00:00:00+00:00</lastmod>
<lastmod>2023-07-10T00:00:00+00:00</lastmod>
</url>
<url>
<loc>https://ghostfol.io/es/preguntas-mas-frecuentes</loc>
<lastmod>2023-07-01T00:00:00+00:00</lastmod>
<lastmod>2023-07-10T00:00:00+00:00</lastmod>
</url>
<url>
<loc>https://ghostfol.io/es/recursos</loc>
<lastmod>2023-07-01T00:00:00+00:00</lastmod>
<lastmod>2023-07-10T00:00:00+00:00</lastmod>
</url>
<url>
<loc>https://ghostfol.io/es/registro</loc>
<lastmod>2023-07-01T00:00:00+00:00</lastmod>
<lastmod>2023-07-10T00:00:00+00:00</lastmod>
</url>
<url>
<loc>https://ghostfol.io/es/sobre</loc>
<lastmod>2023-07-01T00:00:00+00:00</lastmod>
<lastmod>2023-07-10T00:00:00+00:00</lastmod>
</url>
<url>
<loc>https://ghostfol.io/es/sobre/changelog</loc>
<lastmod>2023-07-01T00:00:00+00:00</lastmod>
<lastmod>2023-07-10T00:00:00+00:00</lastmod>
</url>
<url>
<loc>https://ghostfol.io/es/sobre/licencia</loc>
<lastmod>2023-07-01T00:00:00+00:00</lastmod>
<lastmod>2023-07-10T00:00:00+00:00</lastmod>
</url>
<url>
<loc>https://ghostfol.io/es/sobre/politica-de-privacidad</loc>
<lastmod>2023-07-01T00:00:00+00:00</lastmod>
<lastmod>2023-07-10T00:00:00+00:00</lastmod>
</url>
<url>
<loc>https://ghostfol.io/fr</loc>
<lastmod>2023-07-01T00:00:00+00:00</lastmod>
<lastmod>2023-07-10T00:00:00+00:00</lastmod>
</url>
<url>
<loc>https://ghostfol.io/fr/a-propos</loc>
<lastmod>2023-07-01T00:00:00+00:00</lastmod>
<lastmod>2023-07-10T00:00:00+00:00</lastmod>
</url>
<url>
<loc>https://ghostfol.io/fr/a-propos/changelog</loc>
<lastmod>2023-07-01T00:00:00+00:00</lastmod>
<lastmod>2023-07-10T00:00:00+00:00</lastmod>
</url>
<url>
<loc>https://ghostfol.io/fr/a-propos/licence</loc>
<lastmod>2023-07-01T00:00:00+00:00</lastmod>
<lastmod>2023-07-10T00:00:00+00:00</lastmod>
</url>
<url>
<loc>https://ghostfol.io/fr/a-propos/politique-de-confidentialite</loc>
<lastmod>2023-07-01T00:00:00+00:00</lastmod>
<lastmod>2023-07-10T00:00:00+00:00</lastmod>
</url>
<url>
<loc>https://ghostfol.io/fr/enregistrement</loc>
<lastmod>2023-07-01T00:00:00+00:00</lastmod>
<lastmod>2023-07-10T00:00:00+00:00</lastmod>
</url>
<url>
<loc>https://ghostfol.io/fr/fonctionnalites</loc>
<lastmod>2023-07-01T00:00:00+00:00</lastmod>
<lastmod>2023-07-10T00:00:00+00:00</lastmod>
</url>
<url>
<loc>https://ghostfol.io/fr/foire-aux-questions</loc>
<lastmod>2023-07-01T00:00:00+00:00</lastmod>
<lastmod>2023-07-10T00:00:00+00:00</lastmod>
</url>
<url>
<loc>https://ghostfol.io/fr/marches</loc>
<changefreq>daily</changefreq>
<lastmod>2023-07-01T00:00:00+00:00</lastmod>
<lastmod>2023-07-10T00:00:00+00:00</lastmod>
</url>
<url>
<loc>https://ghostfol.io/fr/open</loc>
<changefreq>daily</changefreq>
<lastmod>2023-07-01T00:00:00+00:00</lastmod>
<lastmod>2023-07-10T00:00:00+00:00</lastmod>
</url>
<url>
<loc>https://ghostfol.io/fr/prix</loc>
<lastmod>2023-07-01T00:00:00+00:00</lastmod>
<lastmod>2023-07-10T00:00:00+00:00</lastmod>
</url>
<url>
<loc>https://ghostfol.io/fr/ressources</loc>
<lastmod>2023-07-01T00:00:00+00:00</lastmod>
<lastmod>2023-07-10T00:00:00+00:00</lastmod>
</url>
<url>
<loc>https://ghostfol.io/it</loc>
<lastmod>2023-07-01T00:00:00+00:00</lastmod>
<lastmod>2023-07-10T00:00:00+00:00</lastmod>
</url>
<url>
<loc>https://ghostfol.io/it/domande-piu-frequenti</loc>
<lastmod>2023-07-01T00:00:00+00:00</lastmod>
<lastmod>2023-07-10T00:00:00+00:00</lastmod>
</url>
<url>
<loc>https://ghostfol.io/it/funzionalita</loc>
<lastmod>2023-07-01T00:00:00+00:00</lastmod>
<lastmod>2023-07-10T00:00:00+00:00</lastmod>
</url>
<url>
<loc>https://ghostfol.io/it/informazioni-su</loc>
<lastmod>2023-07-01T00:00:00+00:00</lastmod>
<lastmod>2023-07-10T00:00:00+00:00</lastmod>
</url>
<url>
<loc>https://ghostfol.io/it/informazioni-su/changelog</loc>
<lastmod>2023-07-01T00:00:00+00:00</lastmod>
<lastmod>2023-07-10T00:00:00+00:00</lastmod>
</url>
<url>
<loc>https://ghostfol.io/it/informazioni-su/licenza</loc>
<lastmod>2023-07-01T00:00:00+00:00</lastmod>
<lastmod>2023-07-10T00:00:00+00:00</lastmod>
</url>
<url>
<loc>https://ghostfol.io/it/informazioni-su/informativa-sulla-privacy</loc>
<lastmod>2023-07-01T00:00:00+00:00</lastmod>
<lastmod>2023-07-10T00:00:00+00:00</lastmod>
</url>
<url>
<loc>https://ghostfol.io/it/iscrizione</loc>
<lastmod>2023-07-01T00:00:00+00:00</lastmod>
<lastmod>2023-07-10T00:00:00+00:00</lastmod>
</url>
<url>
<loc>https://ghostfol.io/it/mercati</loc>
<changefreq>daily</changefreq>
<lastmod>2023-07-01T00:00:00+00:00</lastmod>
<lastmod>2023-07-10T00:00:00+00:00</lastmod>
</url>
<url>
<loc>https://ghostfol.io/it/open</loc>
<changefreq>daily</changefreq>
<lastmod>2023-07-01T00:00:00+00:00</lastmod>
<lastmod>2023-07-10T00:00:00+00:00</lastmod>
</url>
<url>
<loc>https://ghostfol.io/it/prezzi</loc>
<lastmod>2023-07-01T00:00:00+00:00</lastmod>
<lastmod>2023-07-10T00:00:00+00:00</lastmod>
</url>
<url>
<loc>https://ghostfol.io/it/risorse</loc>
<lastmod>2023-07-01T00:00:00+00:00</lastmod>
<lastmod>2023-07-10T00:00:00+00:00</lastmod>
</url>
<url>
<loc>https://ghostfol.io/nl</loc>
<lastmod>2023-07-01T00:00:00+00:00</lastmod>
<lastmod>2023-07-10T00:00:00+00:00</lastmod>
</url>
<url>
<loc>https://ghostfol.io/nl/bronnen</loc>
<lastmod>2023-07-01T00:00:00+00:00</lastmod>
<lastmod>2023-07-10T00:00:00+00:00</lastmod>
</url>
<url>
<loc>https://ghostfol.io/nl/kenmerken</loc>
<lastmod>2023-07-01T00:00:00+00:00</lastmod>
<lastmod>2023-07-10T00:00:00+00:00</lastmod>
</url>
<url>
<loc>https://ghostfol.io/nl/markten</loc>
<changefreq>daily</changefreq>
<lastmod>2023-07-01T00:00:00+00:00</lastmod>
<lastmod>2023-07-10T00:00:00+00:00</lastmod>
</url>
<url>
<loc>https://ghostfol.io/nl/open</loc>
<changefreq>daily</changefreq>
<lastmod>2023-07-01T00:00:00+00:00</lastmod>
<lastmod>2023-07-10T00:00:00+00:00</lastmod>
</url>
<url>
<loc>https://ghostfol.io/nl/over</loc>
<lastmod>2023-07-01T00:00:00+00:00</lastmod>
<lastmod>2023-07-10T00:00:00+00:00</lastmod>
</url>
<url>
<loc>https://ghostfol.io/nl/over/changelog</loc>
<lastmod>2023-07-01T00:00:00+00:00</lastmod>
<lastmod>2023-07-10T00:00:00+00:00</lastmod>
</url>
<url>
<loc>https://ghostfol.io/nl/over/licentie</loc>
<lastmod>2023-07-01T00:00:00+00:00</lastmod>
<lastmod>2023-07-10T00:00:00+00:00</lastmod>
</url>
<url>
<loc>https://ghostfol.io/nl/over/privacybeleid</loc>
<lastmod>2023-07-01T00:00:00+00:00</lastmod>
<lastmod>2023-07-10T00:00:00+00:00</lastmod>
</url>
<url>
<loc>https://ghostfol.io/nl/prijzen</loc>
<lastmod>2023-07-01T00:00:00+00:00</lastmod>
<lastmod>2023-07-10T00:00:00+00:00</lastmod>
</url>
<url>
<loc>https://ghostfol.io/nl/registratie</loc>
<lastmod>2023-07-01T00:00:00+00:00</lastmod>
<lastmod>2023-07-10T00:00:00+00:00</lastmod>
</url>
<url>
<loc>https://ghostfol.io/nl/vaak-gestelde-vragen</loc>
<lastmod>2023-07-01T00:00:00+00:00</lastmod>
<lastmod>2023-07-10T00:00:00+00:00</lastmod>
</url>
<url>
<loc>https://ghostfol.io/pt/blog</loc>
<lastmod>2023-07-01T00:00:00+00:00</lastmod>
<lastmod>2023-07-10T00:00:00+00:00</lastmod>
</url>
<url>
<loc>https://ghostfol.io/pt/funcionalidades</loc>
<lastmod>2023-07-01T00:00:00+00:00</lastmod>
<lastmod>2023-07-10T00:00:00+00:00</lastmod>
</url>
<url>
<loc>https://ghostfol.io/pt/mercados</loc>
<lastmod>2023-07-01T00:00:00+00:00</lastmod>
<lastmod>2023-07-10T00:00:00+00:00</lastmod>
</url>
<url>
<loc>https://ghostfol.io/pt/open</loc>
<lastmod>2023-07-01T00:00:00+00:00</lastmod>
<lastmod>2023-07-10T00:00:00+00:00</lastmod>
</url>
<url>
<loc>https://ghostfol.io/pt/perguntas-mais-frequentes</loc>
<lastmod>2023-07-01T00:00:00+00:00</lastmod>
<lastmod>2023-07-10T00:00:00+00:00</lastmod>
</url>
<url>
<loc>https://ghostfol.io/pt/precos</loc>
<lastmod>2023-07-01T00:00:00+00:00</lastmod>
<lastmod>2023-07-10T00:00:00+00:00</lastmod>
</url>
<url>
<loc>https://ghostfol.io/pt/recursos</loc>
<lastmod>2023-07-01T00:00:00+00:00</lastmod>
<lastmod>2023-07-10T00:00:00+00:00</lastmod>
</url>
<url>
<loc>https://ghostfol.io/pt/registo</loc>
<lastmod>2023-07-01T00:00:00+00:00</lastmod>
<lastmod>2023-07-10T00:00:00+00:00</lastmod>
</url>
<url>
<loc>https://ghostfol.io/pt/sobre</loc>
<lastmod>2023-07-01T00:00:00+00:00</lastmod>
<lastmod>2023-07-10T00:00:00+00:00</lastmod>
</url>
<url>
<loc>https://ghostfol.io/pt/sobre/changelog</loc>
<lastmod>2023-07-01T00:00:00+00:00</lastmod>
<lastmod>2023-07-10T00:00:00+00:00</lastmod>
</url>
<url>
<loc>https://ghostfol.io/pt/sobre/licenca</loc>
<lastmod>2023-07-01T00:00:00+00:00</lastmod>
<lastmod>2023-07-10T00:00:00+00:00</lastmod>
</url>
<url>
<loc>https://ghostfol.io/pt/sobre/politica-de-privacidade</loc>
<lastmod>2023-07-01T00:00:00+00:00</lastmod>
<lastmod>2023-07-10T00:00:00+00:00</lastmod>
</url>
</urlset>

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@ -32,6 +32,7 @@ import type { PortfolioReportRule } from './portfolio-report-rule.interface';
import type { PortfolioReport } from './portfolio-report.interface';
import type { PortfolioSummary } from './portfolio-summary.interface';
import type { Position } from './position.interface';
import type { Product } from './product';
import type { BenchmarkResponse } from './responses/benchmark-response.interface';
import type { ResponseError } from './responses/errors.interface';
import type { ImportResponse } from './responses/import-response.interface';
@ -83,6 +84,7 @@ export {
PortfolioReportRule,
PortfolioSummary,
Position,
Product,
ResponseError,
ScraperConfiguration,
Statistics,

View File

@ -0,0 +1,16 @@
export interface Product {
component: any;
founded?: number;
hasFreePlan?: boolean;
hasSelfHostingAbility?: boolean;
isOpenSource: boolean;
key: string;
languages?: string[];
name: string;
note?: string;
origin?: string;
pricingPerYear?: string;
region?: string;
slogan?: string;
useAnonymously?: boolean;
}

View File

@ -223,7 +223,7 @@
*matHeaderCellDef
class="d-none d-lg-table-cell px-1"
mat-header-cell
mat-sort-header
mat-sort-header="SymbolProfile.currency"
>
<ng-container i18n>Currency</ng-container>
</th>

View File

@ -154,7 +154,8 @@ export class LineChartComponent implements AfterViewInit, OnChanges, OnDestroy {
data: benchmarkPrices,
fill: false,
label: this.benchmarkLabel,
pointRadius: 0
pointRadius: 0,
spanGaps: false
},
{
backgroundColor: gradient,

View File

@ -1,6 +1,6 @@
{
"name": "ghostfolio",
"version": "1.286.0",
"version": "1.288.0",
"homepage": "https://ghostfol.io",
"license": "AGPL-3.0",
"scripts": {