Compare commits

...

61 Commits

Author SHA1 Message Date
4af76764be Release 1.204.0 (#1356) 2022-10-15 17:47:35 +02:00
a65424aafa Feature/add total amount chart to investment timeline (#1344)
* Add total amount chart

* Update changelog
2022-10-15 17:45:34 +02:00
f9cd629470 Update messages.es.xlf (#1355) 2022-10-15 17:44:20 +02:00
ccb8c86596 Feature/minor improvements in chart components (#1353)
* Move y axis to the right

* Update changelog
2022-10-15 11:27:55 +02:00
246de7aa86 Consider current month in FIRE calculation (#1349) 2022-10-15 10:45:11 +02:00
a323313c71 Bugfix/fix alignment of value component on allocation page (#1351)
* Fix alignment

* Update changelog
2022-10-15 10:32:08 +02:00
538c8947cd Feature/rename data source from rakuten to rapid api (#1350)
* Rename Rakuten to Rapid API

* Update changelog
2022-10-15 10:31:46 +02:00
1ec5fd12fe Feature/setup prettier plugin organize attributes (#1346)
* Setup prettier plugin: prettier-plugin-organize-attributes

* Update changelog
2022-10-15 10:30:12 +02:00
4376b8903e Bugifx/fix url in blog posts (#1347)
* Fix url

* Update changelog
2022-10-14 19:52:00 +02:00
a8e096f9ac Feature/minor improvements for appearance selector (#1345)
* Improve appearance selector

* Update changelog
2022-10-12 13:38:58 +02:00
8e577592f6 Fix issue with $localize in storybook (#1343) 2022-10-12 09:48:33 +02:00
c896bf9199 Add appearance option in settings (#1342)
* Add appearance option in settings
2022-10-11 21:34:52 +02:00
16145f18d9 Improve template (#1324)
* Improve template
2022-10-09 10:42:02 +02:00
5398da0dc8 Feature/simplify admin settings management (#1340)
* Simplify settings management

* Update changelog
2022-10-08 17:35:18 +02:00
2466f4ff5d Release 1.203.0 (#1338) 2022-10-08 14:22:43 +02:00
8f3a9bdfbb Feature/refactor animation configuration (#1337)
* Refactor animation configuration

* Update changelog
2022-10-08 14:21:17 +02:00
44dfd2bd48 Add animation to line chart (#1328) 2022-10-08 13:47:48 +02:00
3fc2228f1d Feature/switch to new performance calculation (#1336)
* Switch to new performance calculation

* Update changelog
2022-10-08 13:20:52 +02:00
b018819a1f Bugfix/fix todays performance and chart calculation (#1333)
* Fix today's performance and chart calculation

* Update changelog
2022-10-08 13:20:25 +02:00
ac9311d783 Bugfix/fix alignment in users table (#1335)
* Fix alignment

* Update changelog
2022-10-08 11:38:58 +02:00
e23ce0f35d Feature/improve gui of benchmark comparator (#1334)
* Improve GUI

* Update changelog
2022-10-08 11:07:42 +02:00
f4b52aa41c Add Italian localization for the 4% rule (#1329)
* Update messages.it.xlf
2022-10-08 11:05:53 +02:00
655b040d4d Add missing title (#1332) 2022-10-07 20:54:05 +02:00
0f637a5d0f Release 1.202.0 (#1331) 2022-10-07 20:49:57 +02:00
3f85c327f5 Bugfix/fix text truncation in value component (#1330)
* Fix text truncation

* Update changelog
2022-10-07 20:48:39 +02:00
c2df99072d Feature/refactor filters (#1299)
* Refactor filters

Co-Authored-By: Zakaria YAHI <9142557+ZakYahi@users.noreply.github.com>
2022-10-07 20:39:29 +02:00
e8afbcad9c Feature/localize 4 percentage rule (#1327)
* Setup translation for 4% rule

* Update changelog
2022-10-07 20:21:52 +02:00
e6d8de781b Feature/improve wording in twitter bot service (#1326)
* Improve wording

* Update changelog
2022-10-06 20:52:34 +02:00
6e1935899f Bugfix/fix cryptocurrency symbols with less than 3 characters (#1325)
* Fix cryptocurrency symbols with less than 3 characters

* Update changelog
2022-10-06 15:15:36 +02:00
169cb85b66 Improve Italian translation (#1318)
* Update messages.it.xlf
2022-10-05 07:53:03 +02:00
fe6658d0ac Update messages.es.xlf (#1319) 2022-10-04 17:43:25 +02:00
1f0381228e Feature/improve caching of benchmarks (#1320)
* Improve caching

* Update changelog
2022-10-04 17:39:51 +02:00
f4b63b5de5 Release 1.201.0 (#1313) 2022-10-01 18:39:15 +02:00
e45a0ad068 Spanish (#1312)
* Update messages.es.xlf
2022-10-01 18:36:41 +02:00
81c6cc021d Feature/add blog post hacktoberfest 2022 (#1310)
* Add blog post: Hacktoberfest 2022

* Update changelog
2022-10-01 18:35:55 +02:00
859b24aa5b Fix alignment (#1311) 2022-10-01 18:35:27 +02:00
2bc325f182 Update messages.es.xlf (#1305)
* Update messages.es.xlf

Co-authored-by: fdp10381 <63880387+fdp10381@users.noreply.github.com>
2022-10-01 16:45:44 +02:00
a6186c23e2 Feature/improve usage of value component (#1308)
* Improve usage of value component

* Update changelog
2022-10-01 13:53:43 +02:00
cf234003ec Release 1.200.0 (#1307) 2022-10-01 11:18:15 +02:00
8d3954304e Feature/add statistics section to landing page (#1306)
* Add pulls on Docker Hub to statistics

* Add statistics to landing page

* Update changelog
2022-10-01 11:16:43 +02:00
9562139fa6 Feature/upgrade prisma to version 4.4.0 (#1304)
* Upgrade prisma to version 4.4.0

* Update changelog
2022-10-01 09:42:07 +02:00
c857ea9a8f Feature/add as seen in section on landing page (#1302)
* Add as seen in section

* Update changelog
2022-09-29 21:59:51 +02:00
5c9fa71d95 Release 1.199.1 (#1301) 2022-09-27 20:44:32 +02:00
fefbfa31d1 Release 1.199.0 (#1300) 2022-09-27 20:28:46 +02:00
93a1fae51c Feature/support sectors of mutual funds (#1298)
* Support sectors

* Update changelog

Co-authored-by: Mitchell <5503199+m11tch@users.noreply.github.com>
2022-09-27 17:38:53 +02:00
3715edd9ba Extract locales (#1297) 2022-09-26 19:44:19 +02:00
e3916e1ba3 Feature/setup espanol (#1293)
* Setup Español

* Update changelog
2022-09-26 18:39:11 +02:00
76ceac4edc Add spanish translation (#1296)
Co-Authored-By: alfredonodo <41476198+alfredonodo@users.noreply.github.com>
Co-Authored-By: casitu <25199636+casitu@users.noreply.github.com>
2022-09-26 18:14:53 +02:00
333b63bfe2 Release 1.198.0 (#1294) 2022-09-25 21:46:19 +02:00
3006c21b12 Add dutch translation (#1291)
* Add dutch translation
2022-09-25 18:12:33 +02:00
f01a3f893d Exclude accounts (#1289)
* Exclude accounts

* Update changelog
2022-09-25 18:02:46 +02:00
72974e888f Clean up spaces (#1288) 2022-09-25 15:14:51 +02:00
0cee7a0b35 Release 1.197.0 (#1287) 2022-09-24 13:16:47 +02:00
f3d337b044 Feature/add value of active filters on allocations page (#1286)
* Add value

* Update changelog
2022-09-24 13:15:16 +02:00
7667af059c Feature/combine performance and chart calculation (#1285)
* Combine performance and chart calculation endpoints

* Update changelog
2022-09-24 13:12:40 +02:00
1095b47f45 Feature/add multi language support to feature overview (#1284)
* Add multi-language support

* Update changelog
2022-09-24 12:29:36 +02:00
dacd7271eb Feature/improve density of various selectors (#1283)
* Improve density

* Update changelog
2022-09-24 09:58:09 +02:00
e093041184 Release 1.196.0 (#1281) 2022-09-22 21:05:05 +02:00
8f2caa508a Feature/extend landing page (#1279)
* Extend landing page

* Update changelog
2022-09-22 20:52:46 +02:00
862f670ccf Feature/setup italiano (#1276)
* Setup italiano

* Update changelog
2022-09-22 20:52:03 +02:00
54bf4c7a43 Update messages.it.xlf (#1280) 2022-09-22 20:51:31 +02:00
132 changed files with 8524 additions and 1424 deletions

View File

@ -1,37 +1,45 @@
--- ---
name: Bug report name: Bug report
about: Create a report to help us improve about: Create a report to help us improve
title: "[BUG]" title: '[BUG]'
labels: '' labels: ''
assignees: '' assignees: ''
--- ---
The Issue tracker is **ONLY** used for reporting bugs. New features should be discussed on our [Slack channel](https://ghostfolio.slack.com) or in [Discussions](https://github.com/ghostfolio/ghostfolio/discussions). The Issue tracker is **ONLY** used for reporting bugs. New features should be discussed on our [Slack channel](https://ghostfolio.slack.com) or in [Discussions](https://github.com/ghostfolio/ghostfolio/discussions).
**Describe the bug** **Bug Description**
<!-- A clear and concise description of what the bug is. --> <!-- A clear and concise description of what the bug is. -->
**To Reproduce** **To Reproduce**
Steps to reproduce the behavior:
<!-- Steps to reproduce the behavior -->
1. 1.
2. 2.
3. 3.
4.
**Expected behavior** **Expected behavior**
<!-- A clear and concise description of what you expected to happen. --> <!-- A clear and concise description of what you expected to happen. -->
**Screenshots** **Screenshots**
<!-- If applicable, add screenshots to help explain your problem. --> <!-- If applicable, add screenshots to help explain your problem. -->
**Logs** **Logs**
<!-- If applicable, add logs to help explain your problem. --> <!-- If applicable, add logs to help explain your problem. -->
**Environment (please complete the following information):** **Environment**
- Ghostfolio Version [e.g. 1.194.0]
- Browser [e.g. chrome] <!-- Please complete the following information -->
- OS
- Ghostfolio Version X.Y.Z
- Browser
- OS
**Additional context** **Additional context**
<!-- Add any other context about the problem here. --> <!-- Add any other context about the problem here. -->

View File

@ -1,4 +1,13 @@
{ {
"attributeGroups": [
"$ANGULAR_ELEMENT_REF",
"$ANGULAR_STRUCTURAL_DIRECTIVE",
"$DEFAULT",
"$ANGULAR_INPUT",
"$ANGULAR_TWO_WAY_BINDING",
"$ANGULAR_OUTPUT"
],
"attributeSort": "ASC",
"endOfLine": "auto", "endOfLine": "auto",
"printWidth": 80, "printWidth": 80,
"singleQuote": true, "singleQuote": true,

View File

@ -5,6 +5,121 @@ All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
## 1.204.0 - 15.10.2022
### Added
- Added support to change the appearance (dark mode) in user settings
- Added the total amount chart to the investment timeline
- Setup the `prettier` plugin `prettier-plugin-organize-attributes`
### Changed
- Respected the current date in the _FIRE_ calculator
- Simplified the settings management in the admin control panel
- Renamed the data source type `RAKUTEN` to `RAPID_API`
### Fixed
- Fixed some links in the blog posts
- Fixed the alignment of the value component on the allocations page
### Todo
- Rename the environment variable from `RAKUTEN_RAPID_API_KEY` to `RAPID_API_API_KEY`
## 1.203.0 - 08.10.2022
### Added
- Supported a progressive line animation in the line chart component
### Changed
- Moved the benchmark comparator from experimental to general availability
- Improved the user interface of the benchmark comparator
### Fixed
- Fixed an issue in the performance and chart calculation of today
- Fixed the alignment of the value component in the admin control panel
## 1.202.0 - 07.10.2022
### Added
- Added support for a translated 4% rule in the _FIRE_ section
### Changed
- Improved the caching of the benchmarks in the markets overview (only cache if fetching was successful)
- Improved the wording in the twitter bot service
### Fixed
- Fixed the support for cryptocurrencies having a symbol with less than 3 characters (e.g. `SC-USD`)
- Fixed the text truncation in the value component
## 1.201.0 - 01.10.2022
### Added
- Added a blog post: _Hacktoberfest 2022_
### Changed
- Improved the usage of the value component in the admin control panel
- Improved the language localization for Español (`es`)
### Fixed
- Fixed the usage of the value component on the allocations page
## 1.200.0 - 01.10.2022
### Added
- Added a mini statistics section to the landing page including pulls on _Docker Hub_
- Added an _As seen in_ section to the landing page
- Added support for an icon in the value component
### Changed
- Upgraded `prisma` from version `4.1.1` to `4.4.0`
## 1.199.1 - 27.09.2022
### Added
- Set up the language localization for Español (`es`)
- Added support for sectors in mutual funds
## 1.198.0 - 25.09.2022
### Added
- Added support to exclude an account from analysis
- Set up the language localization for Nederlands (`nl`)
## 1.197.0 - 24.09.2022
### Added
- Added the value of the active filter in percentage on the allocations page
- Extended the feature overview page by multi-language support (English, German, Italian)
### Changed
- Combined the performance and chart calculation
- Improved the style of various selectors (density)
## 1.196.0 - 22.09.2022
### Added
- Set up the language localization for Italiano (`it`)
- Extended the landing page
## 1.195.0 - 20.09.2022 ## 1.195.0 - 20.09.2022
### Changed ### Changed
@ -195,7 +310,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
### Added ### Added
- Set up `ng-extract-i18n-merge` to improve the i18n extraction and merge workflow - Set up `ng-extract-i18n-merge` to improve the i18n extraction and merge workflow
- Set up language localization for German (`de`) - Set up the language localization for German (`de`)
- Resolved the feature graphic of the blog post - Resolved the feature graphic of the blog post
### Changed ### Changed
@ -857,8 +972,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
### Changed ### Changed
- Display the value in base currency in the accounts table on mobile - Displayed the value in base currency in the accounts table on mobile
- Display the value in base currency in the activities table on mobile - Displayed the value in base currency in the activities table on mobile
- Renamed `orders` to `activities` in import and export functionality - Renamed `orders` to `activities` in import and export functionality
- Harmonized the algebraic sign of `currentGrossPerformancePercent` and `currentNetPerformancePercent` with `currentGrossPerformance` and `currentNetPerformance` - Harmonized the algebraic sign of `currentGrossPerformancePercent` and `currentNetPerformancePercent` with `currentGrossPerformance` and `currentNetPerformance`
- Improved the pricing page - Improved the pricing page

View File

@ -136,10 +136,18 @@
"baseHref": "/en/", "baseHref": "/en/",
"localize": ["en"] "localize": ["en"]
}, },
"development-es": {
"baseHref": "/es/",
"localize": ["es"]
},
"development-it": { "development-it": {
"baseHref": "/it/", "baseHref": "/it/",
"localize": ["it"] "localize": ["it"]
}, },
"development-nl": {
"baseHref": "/nl/",
"localize": ["nl"]
},
"production": { "production": {
"fileReplacements": [ "fileReplacements": [
{ {
@ -184,9 +192,15 @@
"development-en": { "development-en": {
"browserTarget": "client:build:development-en" "browserTarget": "client:build:development-en"
}, },
"development-es": {
"browserTarget": "client:build:development-es"
},
"development-it": { "development-it": {
"browserTarget": "client:build:development-it" "browserTarget": "client:build:development-it"
}, },
"development-nl": {
"browserTarget": "client:build:development-nl"
},
"production": { "production": {
"browserTarget": "client:build:production" "browserTarget": "client:build:production"
} }
@ -198,7 +212,12 @@
"browserTarget": "client:build", "browserTarget": "client:build",
"includeContext": true, "includeContext": true,
"outputPath": "src/locales", "outputPath": "src/locales",
"targetFiles": ["messages.de.xlf", "messages.it.xlf"] "targetFiles": [
"messages.de.xlf",
"messages.es.xlf",
"messages.it.xlf",
"messages.nl.xlf"
]
} }
}, },
"lint": { "lint": {
@ -222,9 +241,17 @@
"baseHref": "/de/", "baseHref": "/de/",
"translation": "apps/client/src/locales/messages.de.xlf" "translation": "apps/client/src/locales/messages.de.xlf"
}, },
"es": {
"baseHref": "/es/",
"translation": "apps/client/src/locales/messages.es.xlf"
},
"it": { "it": {
"baseHref": "/it/", "baseHref": "/it/",
"translation": "apps/client/src/locales/messages.it.xlf" "translation": "apps/client/src/locales/messages.it.xlf"
},
"nl": {
"baseHref": "/nl/",
"translation": "apps/client/src/locales/messages.nl.xlf"
} }
}, },
"sourceLocale": "en" "sourceLocale": "en"

View File

@ -95,9 +95,10 @@ export class AccountController {
); );
let accountsWithAggregations = let accountsWithAggregations =
await this.portfolioService.getAccountsWithAggregations( await this.portfolioService.getAccountsWithAggregations({
impersonationUserId || this.request.user.id userId: impersonationUserId || this.request.user.id,
); withExcludedAccounts: true
});
if ( if (
impersonationUserId || impersonationUserId ||
@ -137,10 +138,11 @@ export class AccountController {
); );
let accountsWithAggregations = let accountsWithAggregations =
await this.portfolioService.getAccountsWithAggregations( await this.portfolioService.getAccountsWithAggregations({
impersonationUserId || this.request.user.id, filters: [{ id, type: 'ACCOUNT' }],
[{ id, type: 'ACCOUNT' }] userId: impersonationUserId || this.request.user.id,
); withExcludedAccounts: true
});
if ( if (
impersonationUserId || impersonationUserId ||

View File

@ -107,15 +107,23 @@ export class AccountService {
public async getCashDetails({ public async getCashDetails({
currency, currency,
filters = [], filters = [],
userId userId,
withExcludedAccounts = false
}: { }: {
currency: string; currency: string;
filters?: Filter[]; filters?: Filter[];
userId: string; userId: string;
withExcludedAccounts?: boolean;
}): Promise<CashDetails> { }): Promise<CashDetails> {
let totalCashBalanceInBaseCurrency = new Big(0); let totalCashBalanceInBaseCurrency = new Big(0);
const where: Prisma.AccountWhereInput = { userId }; const where: Prisma.AccountWhereInput = {
userId
};
if (withExcludedAccounts === false) {
where.isExcluded = false;
}
const { const {
ACCOUNT: filtersByAccount, ACCOUNT: filtersByAccount,

View File

@ -1,5 +1,11 @@
import { AccountType } from '@prisma/client'; import { AccountType } from '@prisma/client';
import { IsNumber, IsString, ValidateIf } from 'class-validator'; import {
IsBoolean,
IsNumber,
IsOptional,
IsString,
ValidateIf
} from 'class-validator';
export class CreateAccountDto { export class CreateAccountDto {
@IsString() @IsString()
@ -11,6 +17,10 @@ export class CreateAccountDto {
@IsString() @IsString()
currency: string; currency: string;
@IsBoolean()
@IsOptional()
isExcluded?: boolean;
@IsString() @IsString()
name: string; name: string;

View File

@ -1,5 +1,11 @@
import { AccountType } from '@prisma/client'; import { AccountType } from '@prisma/client';
import { IsNumber, IsString, ValidateIf } from 'class-validator'; import {
IsBoolean,
IsNumber,
IsOptional,
IsString,
ValidateIf
} from 'class-validator';
export class UpdateAccountDto { export class UpdateAccountDto {
@IsString() @IsString()
@ -14,6 +20,10 @@ export class UpdateAccountDto {
@IsString() @IsString()
id: string; id: string;
@IsBoolean()
@IsOptional()
isExcluded?: boolean;
@IsString() @IsString()
name: string; name: string;

View File

@ -181,10 +181,10 @@ export class AdminService {
public async putSetting(key: string, value: string) { public async putSetting(key: string, value: string) {
let response: Property; let response: Property;
if (value === '') { if (value) {
response = await this.propertyService.delete({ key });
} else {
response = await this.propertyService.put({ key, value }); response = await this.propertyService.put({ key, value });
} else {
response = await this.propertyService.delete({ key });
} }
if (key === PROPERTY_CURRENCIES) { if (key === PROPERTY_CURRENCIES) {

View File

@ -73,6 +73,7 @@ export class BenchmarkService {
} }
const allTimeHighs = await Promise.all(promises); const allTimeHighs = await Promise.all(promises);
let storeInCache = true;
benchmarks = allTimeHighs.map((allTimeHigh, index) => { benchmarks = allTimeHighs.map((allTimeHigh, index) => {
const { marketPrice } = const { marketPrice } =
@ -85,6 +86,8 @@ export class BenchmarkService {
allTimeHigh, allTimeHigh,
marketPrice marketPrice
); );
} else {
storeInCache = false;
} }
return { return {
@ -100,11 +103,13 @@ export class BenchmarkService {
}; };
}); });
await this.redisCacheService.set( if (storeInCache) {
this.CACHE_KEY_BENCHMARKS, await this.redisCacheService.set(
JSON.stringify(benchmarks), this.CACHE_KEY_BENCHMARKS,
ms('4 hours') / 1000 JSON.stringify(benchmarks),
); ms('4 hours') / 1000
);
}
return benchmarks; return benchmarks;
} }
@ -164,7 +169,7 @@ export class BenchmarkService {
); );
const marketPriceAtStartDate = marketDataItems?.[0]?.marketPrice ?? 0; const marketPriceAtStartDate = marketDataItems?.[0]?.marketPrice ?? 0;
return { const response = {
marketData: [ marketData: [
...marketDataItems ...marketDataItems
.filter((marketDataItem, index) => { .filter((marketDataItem, index) => {
@ -181,17 +186,22 @@ export class BenchmarkService {
marketDataItem.marketPrice marketDataItem.marketPrice
) * 100 ) * 100
}; };
}), })
{
date: format(new Date(), DATE_FORMAT),
value:
this.calculateChangeInPercentage(
marketPriceAtStartDate,
currentSymbolItem.marketPrice
) * 100
}
] ]
}; };
if (currentSymbolItem?.marketPrice) {
response.marketData.push({
date: format(new Date(), DATE_FORMAT),
value:
this.calculateChangeInPercentage(
marketPriceAtStartDate,
currentSymbolItem.marketPrice
) * 100
});
}
return response;
} }
private getMarketCondition(aPerformanceInPercent: number) { private getMarketCondition(aPerformanceInPercent: number) {

View File

@ -11,6 +11,9 @@ import { NextFunction, Request, Response } from 'express';
export class FrontendMiddleware implements NestMiddleware { export class FrontendMiddleware implements NestMiddleware {
public indexHtmlDe = ''; public indexHtmlDe = '';
public indexHtmlEn = ''; public indexHtmlEn = '';
public indexHtmlEs = '';
public indexHtmlIt = '';
public indexHtmlNl = '';
public isProduction: boolean; public isProduction: boolean;
public constructor( public constructor(
@ -32,6 +35,18 @@ export class FrontendMiddleware implements NestMiddleware {
this.getPathOfIndexHtmlFile(DEFAULT_LANGUAGE_CODE), this.getPathOfIndexHtmlFile(DEFAULT_LANGUAGE_CODE),
'utf8' 'utf8'
); );
this.indexHtmlEs = fs.readFileSync(
this.getPathOfIndexHtmlFile('es'),
'utf8'
);
this.indexHtmlIt = fs.readFileSync(
this.getPathOfIndexHtmlFile('it'),
'utf8'
);
this.indexHtmlNl = fs.readFileSync(
this.getPathOfIndexHtmlFile('nl'),
'utf8'
);
} catch {} } catch {}
} }
@ -43,6 +58,11 @@ export class FrontendMiddleware implements NestMiddleware {
req.path === '/en/blog/2022/08/500-stars-on-github/' req.path === '/en/blog/2022/08/500-stars-on-github/'
) { ) {
featureGraphicPath = 'assets/images/blog/500-stars-on-github.jpg'; featureGraphicPath = 'assets/images/blog/500-stars-on-github.jpg';
} else if (
req.path === '/en/blog/2022/10/hacktoberfest-2022' ||
req.path === '/en/blog/2022/10/hacktoberfest-2022/'
) {
featureGraphicPath = 'assets/images/blog/hacktoberfest-2022.png';
} }
if ( if (
@ -61,6 +81,33 @@ export class FrontendMiddleware implements NestMiddleware {
rootUrl: this.configurationService.get('ROOT_URL') rootUrl: this.configurationService.get('ROOT_URL')
}) })
); );
} else if (req.path === '/es' || req.path.startsWith('/es/')) {
res.send(
this.interpolate(this.indexHtmlEs, {
featureGraphicPath,
languageCode: 'es',
path: req.path,
rootUrl: this.configurationService.get('ROOT_URL')
})
);
} else if (req.path === '/it' || req.path.startsWith('/it/')) {
res.send(
this.interpolate(this.indexHtmlIt, {
featureGraphicPath,
languageCode: 'it',
path: req.path,
rootUrl: this.configurationService.get('ROOT_URL')
})
);
} else if (req.path === '/nl' || req.path.startsWith('/nl/')) {
res.send(
this.interpolate(this.indexHtmlNl, {
featureGraphicPath,
languageCode: 'nl',
path: req.path,
rootUrl: this.configurationService.get('ROOT_URL')
})
);
} else { } else {
res.send( res.send(
this.interpolate(this.indexHtmlEn, { this.interpolate(this.indexHtmlEn, {

View File

@ -145,6 +145,27 @@ export class InfoService {
}); });
} }
private async countDockerHubPulls(): Promise<number> {
try {
const get = bent(
`https://hub.docker.com/v2/repositories/ghostfolio/ghostfolio`,
'GET',
'json',
200,
{
'User-Agent': 'request'
}
);
const { pull_count } = await get();
return pull_count;
} catch (error) {
Logger.error(error, 'InfoService');
return undefined;
}
}
private async countGitHubContributors(): Promise<number> { private async countGitHubContributors(): Promise<number> {
try { try {
const get = bent( const get = bent(
@ -245,6 +266,8 @@ export class InfoService {
const activeUsers1d = await this.countActiveUsers(1); const activeUsers1d = await this.countActiveUsers(1);
const activeUsers30d = await this.countActiveUsers(30); const activeUsers30d = await this.countActiveUsers(30);
const newUsers30d = await this.countNewUsers(30); const newUsers30d = await this.countNewUsers(30);
const dockerHubPulls = await this.countDockerHubPulls();
const gitHubContributors = await this.countGitHubContributors(); const gitHubContributors = await this.countGitHubContributors();
const gitHubStargazers = await this.countGitHubStargazers(); const gitHubStargazers = await this.countGitHubStargazers();
const slackCommunityUsers = await this.countSlackCommunityUsers(); const slackCommunityUsers = await this.countSlackCommunityUsers();
@ -252,6 +275,7 @@ export class InfoService {
statistics = { statistics = {
activeUsers1d, activeUsers1d,
activeUsers30d, activeUsers30d,
dockerHubPulls,
gitHubContributors, gitHubContributors,
gitHubStargazers, gitHubStargazers,
newUsers30d, newUsers30d,

View File

@ -3,8 +3,8 @@ import { nullifyValuesInObjects } from '@ghostfolio/api/helper/object.helper';
import { RedactValuesInResponseInterceptor } from '@ghostfolio/api/interceptors/redact-values-in-response.interceptor'; import { RedactValuesInResponseInterceptor } from '@ghostfolio/api/interceptors/redact-values-in-response.interceptor';
import { TransformDataSourceInRequestInterceptor } from '@ghostfolio/api/interceptors/transform-data-source-in-request.interceptor'; import { TransformDataSourceInRequestInterceptor } from '@ghostfolio/api/interceptors/transform-data-source-in-request.interceptor';
import { TransformDataSourceInResponseInterceptor } from '@ghostfolio/api/interceptors/transform-data-source-in-response.interceptor'; import { TransformDataSourceInResponseInterceptor } from '@ghostfolio/api/interceptors/transform-data-source-in-response.interceptor';
import { ApiService } from '@ghostfolio/api/services/api/api.service';
import { ImpersonationService } from '@ghostfolio/api/services/impersonation.service'; import { ImpersonationService } from '@ghostfolio/api/services/impersonation.service';
import { Filter } from '@ghostfolio/common/interfaces';
import { hasPermission, permissions } from '@ghostfolio/common/permissions'; import { hasPermission, permissions } from '@ghostfolio/common/permissions';
import type { RequestWithUser } from '@ghostfolio/common/types'; import type { RequestWithUser } from '@ghostfolio/common/types';
import { import {
@ -36,6 +36,7 @@ import { UpdateOrderDto } from './update-order.dto';
@Controller('order') @Controller('order')
export class OrderController { export class OrderController {
public constructor( public constructor(
private readonly apiService: ApiService,
private readonly impersonationService: ImpersonationService, private readonly impersonationService: ImpersonationService,
private readonly orderService: OrderService, private readonly orderService: OrderService,
@Inject(REQUEST) private readonly request: RequestWithUser, @Inject(REQUEST) private readonly request: RequestWithUser,
@ -73,30 +74,11 @@ export class OrderController {
@Query('assetClasses') filterByAssetClasses?: string, @Query('assetClasses') filterByAssetClasses?: string,
@Query('tags') filterByTags?: string @Query('tags') filterByTags?: string
): Promise<Activities> { ): Promise<Activities> {
const accountIds = filterByAccounts?.split(',') ?? []; const filters = this.apiService.buildFiltersFromQueryParams({
const assetClasses = filterByAssetClasses?.split(',') ?? []; filterByAccounts,
const tagIds = filterByTags?.split(',') ?? []; filterByAssetClasses,
filterByTags
const filters: Filter[] = [ });
...accountIds.map((accountId) => {
return <Filter>{
id: accountId,
type: 'ACCOUNT'
};
}),
...assetClasses.map((assetClass) => {
return <Filter>{
id: assetClass,
type: 'ASSET_CLASS'
};
}),
...tagIds.map((tagId) => {
return <Filter>{
id: tagId,
type: 'TAG'
};
})
];
const impersonationUserId = const impersonationUserId =
await this.impersonationService.validateImpersonationId( await this.impersonationService.validateImpersonationId(
@ -109,7 +91,8 @@ export class OrderController {
filters, filters,
userCurrency, userCurrency,
includeDrafts: true, includeDrafts: true,
userId: impersonationUserId || this.request.user.id userId: impersonationUserId || this.request.user.id,
withExcludedAccounts: true
}); });
if ( if (

View File

@ -2,6 +2,7 @@ import { AccountService } from '@ghostfolio/api/app/account/account.service';
import { CacheModule } from '@ghostfolio/api/app/cache/cache.module'; import { CacheModule } from '@ghostfolio/api/app/cache/cache.module';
import { RedisCacheModule } from '@ghostfolio/api/app/redis-cache/redis-cache.module'; import { RedisCacheModule } from '@ghostfolio/api/app/redis-cache/redis-cache.module';
import { UserModule } from '@ghostfolio/api/app/user/user.module'; import { UserModule } from '@ghostfolio/api/app/user/user.module';
import { ApiModule } from '@ghostfolio/api/services/api/api.module';
import { ConfigurationModule } from '@ghostfolio/api/services/configuration.module'; import { ConfigurationModule } from '@ghostfolio/api/services/configuration.module';
import { DataGatheringModule } from '@ghostfolio/api/services/data-gathering.module'; import { DataGatheringModule } from '@ghostfolio/api/services/data-gathering.module';
import { DataProviderModule } from '@ghostfolio/api/services/data-provider/data-provider.module'; import { DataProviderModule } from '@ghostfolio/api/services/data-provider/data-provider.module';
@ -18,6 +19,7 @@ import { OrderService } from './order.service';
controllers: [OrderController], controllers: [OrderController],
exports: [OrderService], exports: [OrderService],
imports: [ imports: [
ApiModule,
CacheModule, CacheModule,
ConfigurationModule, ConfigurationModule,
DataGatheringModule, DataGatheringModule,

View File

@ -189,13 +189,15 @@ export class OrderService {
includeDrafts = false, includeDrafts = false,
types, types,
userCurrency, userCurrency,
userId userId,
withExcludedAccounts = false
}: { }: {
filters?: Filter[]; filters?: Filter[];
includeDrafts?: boolean; includeDrafts?: boolean;
types?: TypeOfOrder[]; types?: TypeOfOrder[];
userCurrency: string; userCurrency: string;
userId: string; userId: string;
withExcludedAccounts?: boolean;
}): Promise<Activity[]> { }): Promise<Activity[]> {
const where: Prisma.OrderWhereInput = { userId }; const where: Prisma.OrderWhereInput = { userId };
@ -284,24 +286,28 @@ export class OrderService {
}, },
orderBy: { date: 'asc' } orderBy: { date: 'asc' }
}) })
).map((order) => { )
const value = new Big(order.quantity).mul(order.unitPrice).toNumber(); .filter((order) => {
return withExcludedAccounts || order.Account?.isExcluded === false;
})
.map((order) => {
const value = new Big(order.quantity).mul(order.unitPrice).toNumber();
return { return {
...order, ...order,
value,
feeInBaseCurrency: this.exchangeRateDataService.toCurrency(
order.fee,
order.SymbolProfile.currency,
userCurrency
),
valueInBaseCurrency: this.exchangeRateDataService.toCurrency(
value, value,
order.SymbolProfile.currency, feeInBaseCurrency: this.exchangeRateDataService.toCurrency(
userCurrency order.fee,
) order.SymbolProfile.currency,
}; userCurrency
}); ),
valueInBaseCurrency: this.exchangeRateDataService.toCurrency(
value,
order.SymbolProfile.currency,
userCurrency
)
};
});
} }
public async updateOrder({ public async updateOrder({

View File

@ -14,6 +14,7 @@ import {
format, format,
isAfter, isAfter,
isBefore, isBefore,
isSameDay,
isSameMonth, isSameMonth,
isSameYear, isSameYear,
max, max,
@ -187,7 +188,9 @@ export class PortfolioCalculator {
day = addDays(day, step); day = addDays(day, step);
} }
dates.push(resetHours(end)); if (!isSameDay(last(dates), end)) {
dates.push(resetHours(end));
}
for (const item of transactionPointsBeforeEndDate[firstIndex - 1].items) { for (const item of transactionPointsBeforeEndDate[firstIndex - 1].items) {
dataGatheringItems.push({ dataGatheringItems.push({
@ -272,23 +275,23 @@ export class PortfolioCalculator {
} }
} }
const isInPercentage = true;
return Object.keys(totalNetPerformanceValues).map((date) => { return Object.keys(totalNetPerformanceValues).map((date) => {
return isInPercentage const netPerformanceInPercentage = totalInvestmentValues[date].eq(0)
? { ? 0
date, : totalNetPerformanceValues[date]
value: totalInvestmentValues[date].eq(0) .div(totalInvestmentValues[date])
? 0 .mul(100)
: totalNetPerformanceValues[date] .toNumber();
.div(totalInvestmentValues[date])
.mul(100) return {
.toNumber() date,
} netPerformanceInPercentage,
: { netPerformance: totalNetPerformanceValues[date].toNumber(),
date, totalInvestment: totalInvestmentValues[date].toNumber(),
value: totalNetPerformanceValues[date].toNumber() value: totalInvestmentValues[date]
}; .plus(totalNetPerformanceValues[date])
.toNumber()
};
}); });
} }

View File

@ -7,18 +7,16 @@ import {
import { RedactValuesInResponseInterceptor } from '@ghostfolio/api/interceptors/redact-values-in-response.interceptor'; import { RedactValuesInResponseInterceptor } from '@ghostfolio/api/interceptors/redact-values-in-response.interceptor';
import { TransformDataSourceInRequestInterceptor } from '@ghostfolio/api/interceptors/transform-data-source-in-request.interceptor'; import { TransformDataSourceInRequestInterceptor } from '@ghostfolio/api/interceptors/transform-data-source-in-request.interceptor';
import { TransformDataSourceInResponseInterceptor } from '@ghostfolio/api/interceptors/transform-data-source-in-response.interceptor'; import { TransformDataSourceInResponseInterceptor } from '@ghostfolio/api/interceptors/transform-data-source-in-response.interceptor';
import { ApiService } from '@ghostfolio/api/services/api/api.service';
import { ConfigurationService } from '@ghostfolio/api/services/configuration.service'; import { ConfigurationService } from '@ghostfolio/api/services/configuration.service';
import { ExchangeRateDataService } from '@ghostfolio/api/services/exchange-rate-data.service'; import { ExchangeRateDataService } from '@ghostfolio/api/services/exchange-rate-data.service';
import { parseDate } from '@ghostfolio/common/helper'; import { parseDate } from '@ghostfolio/common/helper';
import { import {
Filter,
PortfolioChart,
PortfolioDetails, PortfolioDetails,
PortfolioInvestments, PortfolioInvestments,
PortfolioPerformanceResponse, PortfolioPerformanceResponse,
PortfolioPublicDetails, PortfolioPublicDetails,
PortfolioReport, PortfolioReport
PortfolioSummary
} from '@ghostfolio/common/interfaces'; } from '@ghostfolio/common/interfaces';
import { InvestmentItem } from '@ghostfolio/common/interfaces/investment-item.interface'; import { InvestmentItem } from '@ghostfolio/common/interfaces/investment-item.interface';
import type { import type {
@ -40,6 +38,7 @@ import {
} from '@nestjs/common'; } from '@nestjs/common';
import { REQUEST } from '@nestjs/core'; import { REQUEST } from '@nestjs/core';
import { AuthGuard } from '@nestjs/passport'; import { AuthGuard } from '@nestjs/passport';
import Big from 'big.js';
import { StatusCodes, getReasonPhrase } from 'http-status-codes'; import { StatusCodes, getReasonPhrase } from 'http-status-codes';
import { PortfolioPositionDetail } from './interfaces/portfolio-position-detail.interface'; import { PortfolioPositionDetail } from './interfaces/portfolio-position-detail.interface';
@ -52,6 +51,7 @@ export class PortfolioController {
public constructor( public constructor(
private readonly accessService: AccessService, private readonly accessService: AccessService,
private readonly apiService: ApiService,
private readonly configurationService: ConfigurationService, private readonly configurationService: ConfigurationService,
private readonly exchangeRateDataService: ExchangeRateDataService, private readonly exchangeRateDataService: ExchangeRateDataService,
private readonly portfolioService: PortfolioService, private readonly portfolioService: PortfolioService,
@ -61,75 +61,6 @@ export class PortfolioController {
this.baseCurrency = this.configurationService.get('BASE_CURRENCY'); this.baseCurrency = this.configurationService.get('BASE_CURRENCY');
} }
@Get('chart')
@UseGuards(AuthGuard('jwt'))
public async getChart(
@Headers('impersonation-id') impersonationId: string,
@Query('range') range
): Promise<PortfolioChart> {
const historicalDataContainer = await this.portfolioService.getChart(
impersonationId,
range
);
let chartData = historicalDataContainer.items;
let hasError = false;
chartData.forEach((chartDataItem) => {
if (hasNotDefinedValuesInObject(chartDataItem)) {
hasError = true;
}
});
if (
impersonationId ||
this.userService.isRestrictedView(this.request.user)
) {
let maxValue = 0;
chartData.forEach((portfolioItem) => {
if (portfolioItem.value > maxValue) {
maxValue = portfolioItem.value;
}
});
chartData = chartData.map((historicalDataItem) => {
return {
...historicalDataItem,
marketPrice: Number((historicalDataItem.value / maxValue).toFixed(2))
};
});
}
return {
hasError,
chart: chartData,
isAllTimeHigh: historicalDataContainer.isAllTimeHigh,
isAllTimeLow: historicalDataContainer.isAllTimeLow
};
}
@Get('chart')
@UseGuards(AuthGuard('jwt'))
@Version('2')
public async getChartV2(
@Headers('impersonation-id') impersonationId: string,
@Query('range') range
): Promise<PortfolioChart> {
const historicalDataContainer = await this.portfolioService.getChartV2(
impersonationId,
range
);
return {
chart: historicalDataContainer.items,
hasError: false,
isAllTimeHigh: false,
isAllTimeLow: false
};
}
@Get('details') @Get('details')
@UseGuards(AuthGuard('jwt')) @UseGuards(AuthGuard('jwt'))
@UseInterceptors(RedactValuesInResponseInterceptor) @UseInterceptors(RedactValuesInResponseInterceptor)
@ -138,35 +69,16 @@ export class PortfolioController {
@Headers('impersonation-id') impersonationId: string, @Headers('impersonation-id') impersonationId: string,
@Query('accounts') filterByAccounts?: string, @Query('accounts') filterByAccounts?: string,
@Query('assetClasses') filterByAssetClasses?: string, @Query('assetClasses') filterByAssetClasses?: string,
@Query('range') range?: DateRange, @Query('range') dateRange: DateRange = 'max',
@Query('tags') filterByTags?: string @Query('tags') filterByTags?: string
): Promise<PortfolioDetails & { hasError: boolean }> { ): Promise<PortfolioDetails & { hasError: boolean }> {
let hasError = false; let hasError = false;
const accountIds = filterByAccounts?.split(',') ?? []; const filters = this.apiService.buildFiltersFromQueryParams({
const assetClasses = filterByAssetClasses?.split(',') ?? []; filterByAccounts,
const tagIds = filterByTags?.split(',') ?? []; filterByAssetClasses,
filterByTags
const filters: Filter[] = [ });
...accountIds.map((accountId) => {
return <Filter>{
id: accountId,
type: 'ACCOUNT'
};
}),
...assetClasses.map((assetClass) => {
return <Filter>{
id: assetClass,
type: 'ASSET_CLASS'
};
}),
...tagIds.map((tagId) => {
return <Filter>{
id: tagId,
type: 'TAG'
};
})
];
const { const {
accounts, accounts,
@ -174,18 +86,21 @@ export class PortfolioController {
filteredValueInPercentage, filteredValueInPercentage,
hasErrors, hasErrors,
holdings, holdings,
summary,
totalValueInBaseCurrency totalValueInBaseCurrency
} = await this.portfolioService.getDetails( } = await this.portfolioService.getDetails({
dateRange,
filters,
impersonationId, impersonationId,
this.request.user.id, userId: this.request.user.id
range, });
filters
);
if (hasErrors || hasNotDefinedValuesInObject(holdings)) { if (hasErrors || hasNotDefinedValuesInObject(holdings)) {
hasError = true; hasError = true;
} }
let portfolioSummary = summary;
if ( if (
impersonationId || impersonationId ||
this.userService.isRestrictedView(this.request.user) this.userService.isRestrictedView(this.request.user)
@ -219,6 +134,22 @@ export class PortfolioController {
accounts[name].current = current / totalValue; accounts[name].current = current / totalValue;
accounts[name].original = original / totalInvestment; accounts[name].original = original / totalInvestment;
} }
portfolioSummary = nullifyValuesInObject(summary, [
'cash',
'committedFunds',
'currentGrossPerformance',
'currentNetPerformance',
'currentValue',
'dividend',
'emergencyFund',
'excludedAccountsAndActivities',
'fees',
'items',
'netWorth',
'totalBuy',
'totalSell'
]);
} }
let hasDetails = true; let hasDetails = true;
@ -244,7 +175,8 @@ export class PortfolioController {
filteredValueInPercentage, filteredValueInPercentage,
hasError, hasError,
holdings, holdings,
totalValueInBaseCurrency totalValueInBaseCurrency,
summary: hasDetails ? portfolioSummary : undefined
}; };
} }
@ -252,6 +184,7 @@ export class PortfolioController {
@UseGuards(AuthGuard('jwt')) @UseGuards(AuthGuard('jwt'))
public async getInvestments( public async getInvestments(
@Headers('impersonation-id') impersonationId: string, @Headers('impersonation-id') impersonationId: string,
@Query('range') dateRange: DateRange = 'max',
@Query('groupBy') groupBy?: GroupBy @Query('groupBy') groupBy?: GroupBy
): Promise<PortfolioInvestments> { ): Promise<PortfolioInvestments> {
if ( if (
@ -267,12 +200,16 @@ export class PortfolioController {
let investments: InvestmentItem[]; let investments: InvestmentItem[];
if (groupBy === 'month') { if (groupBy === 'month') {
investments = await this.portfolioService.getInvestments( investments = await this.portfolioService.getInvestments({
dateRange,
impersonationId, impersonationId,
'month' groupBy: 'month'
); });
} else { } else {
investments = await this.portfolioService.getInvestments(impersonationId); investments = await this.portfolioService.getInvestments({
dateRange,
impersonationId
});
} }
if ( if (
@ -290,19 +227,22 @@ export class PortfolioController {
})); }));
} }
return { firstOrderDate: parseDate(investments[0]?.date), investments }; return { investments };
} }
@Get('performance') @Get('performance')
@UseGuards(AuthGuard('jwt')) @UseGuards(AuthGuard('jwt'))
@UseInterceptors(TransformDataSourceInResponseInterceptor) @UseInterceptors(TransformDataSourceInResponseInterceptor)
public async getPerformance( @Version('2')
public async getPerformanceV2(
@Headers('impersonation-id') impersonationId: string, @Headers('impersonation-id') impersonationId: string,
@Query('range') range @Query('range') dateRange: DateRange = 'max'
): Promise<PortfolioPerformanceResponse> { ): Promise<PortfolioPerformanceResponse> {
const performanceInformation = await this.portfolioService.getPerformance( const performanceInformation = await this.portfolioService.getPerformanceV2(
impersonationId, {
range dateRange,
impersonationId
}
); );
if ( if (
@ -310,9 +250,29 @@ export class PortfolioController {
this.request.user.Settings.settings.viewMode === 'ZEN' || this.request.user.Settings.settings.viewMode === 'ZEN' ||
this.userService.isRestrictedView(this.request.user) this.userService.isRestrictedView(this.request.user)
) { ) {
performanceInformation.chart = performanceInformation.chart.map(
({ date, netPerformanceInPercentage, totalInvestment, value }) => {
return {
date,
netPerformanceInPercentage,
totalInvestment: new Big(totalInvestment)
.div(performanceInformation.performance.totalInvestment)
.toNumber(),
value: new Big(value)
.div(performanceInformation.performance.currentValue)
.toNumber()
};
}
);
performanceInformation.performance = nullifyValuesInObject( performanceInformation.performance = nullifyValuesInObject(
performanceInformation.performance, performanceInformation.performance,
['currentGrossPerformance', 'currentValue'] [
'currentGrossPerformance',
'currentNetPerformance',
'currentValue',
'totalInvestment'
]
); );
} }
@ -324,11 +284,11 @@ export class PortfolioController {
@UseInterceptors(TransformDataSourceInResponseInterceptor) @UseInterceptors(TransformDataSourceInResponseInterceptor)
public async getPositions( public async getPositions(
@Headers('impersonation-id') impersonationId: string, @Headers('impersonation-id') impersonationId: string,
@Query('range') range @Query('range') dateRange: DateRange = 'max'
): Promise<PortfolioPositions> { ): Promise<PortfolioPositions> {
const result = await this.portfolioService.getPositions( const result = await this.portfolioService.getPositions(
impersonationId, impersonationId,
range dateRange
); );
if ( if (
@ -369,12 +329,12 @@ export class PortfolioController {
hasDetails = user.subscription.type === 'Premium'; hasDetails = user.subscription.type === 'Premium';
} }
const { holdings } = await this.portfolioService.getDetails( const { holdings } = await this.portfolioService.getDetails({
access.userId, dateRange: 'max',
access.userId, filters: [{ id: 'EQUITY', type: 'ASSET_CLASS' }],
'max', impersonationId: access.userId,
[{ id: 'EQUITY', type: 'ASSET_CLASS' }] userId: access.userId
); });
const portfolioPublicDetails: PortfolioPublicDetails = { const portfolioPublicDetails: PortfolioPublicDetails = {
hasDetails, hasDetails,
@ -411,46 +371,6 @@ export class PortfolioController {
return portfolioPublicDetails; return portfolioPublicDetails;
} }
@Get('summary')
@UseGuards(AuthGuard('jwt'))
public async getSummary(
@Headers('impersonation-id') impersonationId
): Promise<PortfolioSummary> {
if (
this.configurationService.get('ENABLE_FEATURE_SUBSCRIPTION') &&
this.request.user.subscription.type === 'Basic'
) {
throw new HttpException(
getReasonPhrase(StatusCodes.FORBIDDEN),
StatusCodes.FORBIDDEN
);
}
let summary = await this.portfolioService.getSummary(impersonationId);
if (
impersonationId ||
this.userService.isRestrictedView(this.request.user)
) {
summary = nullifyValuesInObject(summary, [
'cash',
'committedFunds',
'currentGrossPerformance',
'currentNetPerformance',
'currentValue',
'dividend',
'emergencyFund',
'fees',
'items',
'netWorth',
'totalBuy',
'totalSell'
]);
}
return summary;
}
@Get('position/:dataSource/:symbol') @Get('position/:dataSource/:symbol')
@UseInterceptors(TransformDataSourceInRequestInterceptor) @UseInterceptors(TransformDataSourceInRequestInterceptor)
@UseInterceptors(TransformDataSourceInResponseInterceptor) @UseInterceptors(TransformDataSourceInResponseInterceptor)

View File

@ -2,6 +2,7 @@ import { AccessModule } from '@ghostfolio/api/app/access/access.module';
import { AccountService } from '@ghostfolio/api/app/account/account.service'; import { AccountService } from '@ghostfolio/api/app/account/account.service';
import { OrderModule } from '@ghostfolio/api/app/order/order.module'; import { OrderModule } from '@ghostfolio/api/app/order/order.module';
import { UserModule } from '@ghostfolio/api/app/user/user.module'; import { UserModule } from '@ghostfolio/api/app/user/user.module';
import { ApiModule } from '@ghostfolio/api/services/api/api.module';
import { ConfigurationModule } from '@ghostfolio/api/services/configuration.module'; import { ConfigurationModule } from '@ghostfolio/api/services/configuration.module';
import { DataGatheringModule } from '@ghostfolio/api/services/data-gathering.module'; import { DataGatheringModule } from '@ghostfolio/api/services/data-gathering.module';
import { DataProviderModule } from '@ghostfolio/api/services/data-provider/data-provider.module'; import { DataProviderModule } from '@ghostfolio/api/services/data-provider/data-provider.module';
@ -22,6 +23,7 @@ import { RulesService } from './rules.service';
exports: [PortfolioService], exports: [PortfolioService],
imports: [ imports: [
AccessModule, AccessModule,
ApiModule,
ConfigurationModule, ConfigurationModule,
DataGatheringModule, DataGatheringModule,
DataProviderModule, DataProviderModule,

View File

@ -50,8 +50,11 @@ import type {
import { Inject, Injectable } from '@nestjs/common'; import { Inject, Injectable } from '@nestjs/common';
import { REQUEST } from '@nestjs/core'; import { REQUEST } from '@nestjs/core';
import { import {
Account,
AssetClass, AssetClass,
DataSource, DataSource,
Order,
Platform,
Prisma, Prisma,
Tag, Tag,
Type as TypeOfOrder Type as TypeOfOrder
@ -104,14 +107,19 @@ export class PortfolioService {
this.baseCurrency = this.configurationService.get('BASE_CURRENCY'); this.baseCurrency = this.configurationService.get('BASE_CURRENCY');
} }
public async getAccounts( public async getAccounts({
aUserId: string, filters,
aFilters?: Filter[] userId,
): Promise<AccountWithValue[]> { withExcludedAccounts = false
const where: Prisma.AccountWhereInput = { userId: aUserId }; }: {
filters?: Filter[];
userId: string;
withExcludedAccounts?: boolean;
}): Promise<AccountWithValue[]> {
const where: Prisma.AccountWhereInput = { userId: userId };
if (aFilters?.[0].id && aFilters?.[0].type === 'ACCOUNT') { if (filters?.[0].id && filters?.[0].type === 'ACCOUNT') {
where.id = aFilters[0].id; where.id = filters[0].id;
} }
const [accounts, details] = await Promise.all([ const [accounts, details] = await Promise.all([
@ -120,7 +128,12 @@ export class PortfolioService {
include: { Order: true, Platform: true }, include: { Order: true, Platform: true },
orderBy: { name: 'asc' } orderBy: { name: 'asc' }
}), }),
this.getDetails(aUserId, aUserId, undefined, aFilters) this.getDetails({
filters,
userId,
withExcludedAccounts,
impersonationId: userId
})
]); ]);
const userCurrency = this.request.user.Settings.settings.baseCurrency; const userCurrency = this.request.user.Settings.settings.baseCurrency;
@ -158,11 +171,20 @@ export class PortfolioService {
}); });
} }
public async getAccountsWithAggregations( public async getAccountsWithAggregations({
aUserId: string, filters,
aFilters?: Filter[] userId,
): Promise<Accounts> { withExcludedAccounts = false
const accounts = await this.getAccounts(aUserId, aFilters); }: {
filters?: Filter[];
userId: string;
withExcludedAccounts?: boolean;
}): Promise<Accounts> {
const accounts = await this.getAccounts({
filters,
userId,
withExcludedAccounts
});
let totalBalanceInBaseCurrency = new Big(0); let totalBalanceInBaseCurrency = new Big(0);
let totalValueInBaseCurrency = new Big(0); let totalValueInBaseCurrency = new Big(0);
let transactionCount = 0; let transactionCount = 0;
@ -185,11 +207,16 @@ export class PortfolioService {
}; };
} }
public async getInvestments( public async getInvestments({
aImpersonationId: string, dateRange,
groupBy?: GroupBy impersonationId,
): Promise<InvestmentItem[]> { groupBy
const userId = await this.getUserId(aImpersonationId, this.request.user.id); }: {
dateRange: DateRange;
impersonationId: string;
groupBy?: GroupBy;
}): Promise<InvestmentItem[]> {
const userId = await this.getUserId(impersonationId, this.request.user.id);
const { portfolioOrders, transactionPoints } = const { portfolioOrders, transactionPoints } =
await this.getTransactionPoints({ await this.getTransactionPoints({
@ -261,105 +288,28 @@ export class PortfolioService {
} }
} }
return sortBy(investments, (investment) => { investments = sortBy(investments, (investment) => {
return investment.date; return investment.date;
}); });
}
public async getChart( const startDate = this.getStartDate(
aImpersonationId: string, dateRange,
aDateRange: DateRange = 'max' parseDate(investments[0]?.date)
): Promise<HistoricalDataContainer> { );
const userId = await this.getUserId(aImpersonationId, this.request.user.id);
const { portfolioOrders, transactionPoints } = return investments.filter(({ date }) => {
await this.getTransactionPoints({ return !isBefore(parseDate(date), startDate);
userId
});
const portfolioCalculator = new PortfolioCalculator({
currency: this.request.user.Settings.settings.baseCurrency,
currentRateService: this.currentRateService,
orders: portfolioOrders
}); });
portfolioCalculator.setTransactionPoints(transactionPoints);
if (transactionPoints.length === 0) {
return {
isAllTimeHigh: false,
isAllTimeLow: false,
items: []
};
}
let portfolioStart = parse(
transactionPoints[0].date,
DATE_FORMAT,
new Date()
);
// Get start date for the full portfolio because of because of the
// min and max calculation
portfolioStart = this.getStartDate('max', portfolioStart);
const timelineSpecification: TimelineSpecification[] = [
{
start: format(portfolioStart, DATE_FORMAT),
accuracy: 'day'
}
];
const timelineInfo = await portfolioCalculator.calculateTimeline(
timelineSpecification,
format(new Date(), DATE_FORMAT)
);
const timeline = timelineInfo.timelinePeriods;
const items = timeline
.filter((timelineItem) => timelineItem !== null)
.map((timelineItem) => ({
date: timelineItem.date,
value: timelineItem.netPerformance.toNumber()
}));
let lastItem = null;
if (timeline.length > 0) {
lastItem = timeline[timeline.length - 1];
}
let isAllTimeHigh = timelineInfo.maxNetPerformance?.eq(
lastItem?.netPerformance ?? 0
);
let isAllTimeLow = timelineInfo.minNetPerformance?.eq(
lastItem?.netPerformance ?? 0
);
if (isAllTimeHigh && isAllTimeLow) {
isAllTimeHigh = false;
isAllTimeLow = false;
}
portfolioStart = startOfDay(
this.getStartDate(
aDateRange,
parse(transactionPoints[0].date, DATE_FORMAT, new Date())
)
);
return {
isAllTimeHigh,
isAllTimeLow,
items: items.filter((item) => {
// Filter items of date range
return !isAfter(portfolioStart, parseDate(item.date));
})
};
} }
public async getChartV2( public async getChartV2({
aImpersonationId: string, dateRange = 'max',
aDateRange: DateRange = 'max' impersonationId
): Promise<HistoricalDataContainer> { }: {
const userId = await this.getUserId(aImpersonationId, this.request.user.id); dateRange?: DateRange;
impersonationId: string;
}): Promise<HistoricalDataContainer> {
const userId = await this.getUserId(impersonationId, this.request.user.id);
const { portfolioOrders, transactionPoints } = const { portfolioOrders, transactionPoints } =
await this.getTransactionPoints({ await this.getTransactionPoints({
@ -383,7 +333,7 @@ export class PortfolioService {
const endDate = new Date(); const endDate = new Date();
const portfolioStart = parseDate(transactionPoints[0].date); const portfolioStart = parseDate(transactionPoints[0].date);
const startDate = this.getStartDate(aDateRange, portfolioStart); const startDate = this.getStartDate(dateRange, portfolioStart);
const daysInMarket = differenceInDays(new Date(), startDate); const daysInMarket = differenceInDays(new Date(), startDate);
const step = Math.round( const step = Math.round(
@ -403,13 +353,21 @@ export class PortfolioService {
}; };
} }
public async getDetails( public async getDetails({
aImpersonationId: string, impersonationId,
aUserId: string, userId,
aDateRange: DateRange = 'max', dateRange = 'max',
aFilters?: Filter[] filters,
): Promise<PortfolioDetails & { hasErrors: boolean }> { withExcludedAccounts = false
const userId = await this.getUserId(aImpersonationId, aUserId); }: {
impersonationId: string;
userId: string;
dateRange?: DateRange;
filters?: Filter[];
withExcludedAccounts?: boolean;
}): Promise<PortfolioDetails & { hasErrors: boolean }> {
// TODO
userId = await this.getUserId(impersonationId, userId);
const user = await this.userService.user({ id: userId }); const user = await this.userService.user({ id: userId });
const emergencyFund = new Big( const emergencyFund = new Big(
@ -422,8 +380,9 @@ export class PortfolioService {
const { orders, portfolioOrders, transactionPoints } = const { orders, portfolioOrders, transactionPoints } =
await this.getTransactionPoints({ await this.getTransactionPoints({
filters,
userId, userId,
filters: aFilters withExcludedAccounts
}); });
const portfolioCalculator = new PortfolioCalculator({ const portfolioCalculator = new PortfolioCalculator({
@ -437,15 +396,15 @@ export class PortfolioService {
const portfolioStart = parseDate( const portfolioStart = parseDate(
transactionPoints[0]?.date ?? format(new Date(), DATE_FORMAT) transactionPoints[0]?.date ?? format(new Date(), DATE_FORMAT)
); );
const startDate = this.getStartDate(aDateRange, portfolioStart); const startDate = this.getStartDate(dateRange, portfolioStart);
const currentPositions = await portfolioCalculator.getCurrentPositions( const currentPositions = await portfolioCalculator.getCurrentPositions(
startDate startDate
); );
const cashDetails = await this.accountService.getCashDetails({ const cashDetails = await this.accountService.getCashDetails({
filters,
userId, userId,
currency: userCurrency, currency: userCurrency
filters: aFilters
}); });
const holdings: PortfolioDetails['holdings'] = {}; const holdings: PortfolioDetails['holdings'] = {};
@ -455,10 +414,10 @@ export class PortfolioService {
let filteredValueInBaseCurrency = currentPositions.currentValue; let filteredValueInBaseCurrency = currentPositions.currentValue;
if ( if (
aFilters?.length === 0 || filters?.length === 0 ||
(aFilters?.length === 1 && (filters?.length === 1 &&
aFilters[0].type === 'ASSET_CLASS' && filters[0].type === 'ASSET_CLASS' &&
aFilters[0].id === 'CASH') filters[0].id === 'CASH')
) { ) {
filteredValueInBaseCurrency = filteredValueInBaseCurrency.plus( filteredValueInBaseCurrency = filteredValueInBaseCurrency.plus(
cashDetails.balanceInBaseCurrency cashDetails.balanceInBaseCurrency
@ -554,10 +513,10 @@ export class PortfolioService {
} }
if ( if (
aFilters?.length === 0 || filters?.length === 0 ||
(aFilters?.length === 1 && (filters?.length === 1 &&
aFilters[0].type === 'ASSET_CLASS' && filters[0].type === 'ASSET_CLASS' &&
aFilters[0].id === 'CASH') filters[0].id === 'CASH')
) { ) {
const cashPositions = await this.getCashPositions({ const cashPositions = await this.getCashPositions({
cashDetails, cashDetails,
@ -573,18 +532,20 @@ export class PortfolioService {
} }
const accounts = await this.getValueOfAccounts({ const accounts = await this.getValueOfAccounts({
filters,
orders, orders,
portfolioItemsNow, portfolioItemsNow,
userCurrency, userCurrency,
userId, userId,
filters: aFilters withExcludedAccounts
}); });
const summary = await this.getSummary(aImpersonationId); const summary = await this.getSummary({ impersonationId });
return { return {
accounts, accounts,
holdings, holdings,
summary,
filteredValueInBaseCurrency: filteredValueInBaseCurrency.toNumber(), filteredValueInBaseCurrency: filteredValueInBaseCurrency.toNumber(),
filteredValueInPercentage: summary.netWorth filteredValueInPercentage: summary.netWorth
? filteredValueInBaseCurrency.div(summary.netWorth).toNumber() ? filteredValueInBaseCurrency.div(summary.netWorth).toNumber()
@ -603,7 +564,11 @@ export class PortfolioService {
const userId = await this.getUserId(aImpersonationId, this.request.user.id); const userId = await this.getUserId(aImpersonationId, this.request.user.id);
const orders = ( const orders = (
await this.orderService.getOrders({ userCurrency, userId }) await this.orderService.getOrders({
userCurrency,
userId,
withExcludedAccounts: true
})
).filter(({ SymbolProfile }) => { ).filter(({ SymbolProfile }) => {
return ( return (
SymbolProfile.dataSource === aDataSource && SymbolProfile.dataSource === aDataSource &&
@ -916,11 +881,14 @@ export class PortfolioService {
}; };
} }
public async getPerformance( public async getPerformanceV2({
aImpersonationId: string, dateRange = 'max',
aDateRange: DateRange = 'max' impersonationId
): Promise<PortfolioPerformanceResponse> { }: {
const userId = await this.getUserId(aImpersonationId, this.request.user.id); dateRange?: DateRange;
impersonationId: string;
}): Promise<PortfolioPerformanceResponse> {
const userId = await this.getUserId(impersonationId, this.request.user.id);
const { portfolioOrders, transactionPoints } = const { portfolioOrders, transactionPoints } =
await this.getTransactionPoints({ await this.getTransactionPoints({
@ -935,13 +903,16 @@ export class PortfolioService {
if (transactionPoints?.length <= 0) { if (transactionPoints?.length <= 0) {
return { return {
chart: [],
firstOrderDate: undefined,
hasErrors: false, hasErrors: false,
performance: { performance: {
currentGrossPerformance: 0, currentGrossPerformance: 0,
currentGrossPerformancePercent: 0, currentGrossPerformancePercent: 0,
currentNetPerformance: 0, currentNetPerformance: 0,
currentNetPerformancePercent: 0, currentNetPerformancePercent: 0,
currentValue: 0 currentValue: 0,
totalInvestment: 0
} }
}; };
} }
@ -949,7 +920,7 @@ export class PortfolioService {
portfolioCalculator.setTransactionPoints(transactionPoints); portfolioCalculator.setTransactionPoints(transactionPoints);
const portfolioStart = parseDate(transactionPoints[0].date); const portfolioStart = parseDate(transactionPoints[0].date);
const startDate = this.getStartDate(aDateRange, portfolioStart); const startDate = this.getStartDate(dateRange, portfolioStart);
const currentPositions = await portfolioCalculator.getCurrentPositions( const currentPositions = await portfolioCalculator.getCurrentPositions(
startDate startDate
); );
@ -957,24 +928,59 @@ export class PortfolioService {
const hasErrors = currentPositions.hasErrors; const hasErrors = currentPositions.hasErrors;
const currentValue = currentPositions.currentValue.toNumber(); const currentValue = currentPositions.currentValue.toNumber();
const currentGrossPerformance = currentPositions.grossPerformance; const currentGrossPerformance = currentPositions.grossPerformance;
let currentGrossPerformancePercent = const currentGrossPerformancePercent =
currentPositions.grossPerformancePercentage; currentPositions.grossPerformancePercentage;
const currentNetPerformance = currentPositions.netPerformance; let currentNetPerformance = currentPositions.netPerformance;
let currentNetPerformancePercent = let currentNetPerformancePercent =
currentPositions.netPerformancePercentage; currentPositions.netPerformancePercentage;
const totalInvestment = currentPositions.totalInvestment;
if (currentGrossPerformance.mul(currentGrossPerformancePercent).lt(0)) { // if (currentGrossPerformance.mul(currentGrossPerformancePercent).lt(0)) {
// If algebraic sign is different, harmonize it // // If algebraic sign is different, harmonize it
currentGrossPerformancePercent = currentGrossPerformancePercent.mul(-1); // currentGrossPerformancePercent = currentGrossPerformancePercent.mul(-1);
} // }
if (currentNetPerformance.mul(currentNetPerformancePercent).lt(0)) { // if (currentNetPerformance.mul(currentNetPerformancePercent).lt(0)) {
// If algebraic sign is different, harmonize it // // If algebraic sign is different, harmonize it
currentNetPerformancePercent = currentNetPerformancePercent.mul(-1); // currentNetPerformancePercent = currentNetPerformancePercent.mul(-1);
// }
const historicalDataContainer = await this.getChartV2({
dateRange,
impersonationId
});
const itemOfToday = historicalDataContainer.items.find((item) => {
return item.date === format(new Date(), DATE_FORMAT);
});
if (itemOfToday) {
currentNetPerformance = new Big(itemOfToday.netPerformance);
currentNetPerformancePercent = new Big(
itemOfToday.netPerformanceInPercentage
).div(100);
} }
return { return {
chart: historicalDataContainer.items.map(
({
date,
netPerformance,
netPerformanceInPercentage,
totalInvestment,
value
}) => {
return {
date,
netPerformance,
netPerformanceInPercentage,
totalInvestment,
value
};
}
),
errors: currentPositions.errors, errors: currentPositions.errors,
firstOrderDate: parseDate(historicalDataContainer.items[0]?.date),
hasErrors: currentPositions.hasErrors || hasErrors, hasErrors: currentPositions.hasErrors || hasErrors,
performance: { performance: {
currentValue, currentValue,
@ -982,7 +988,8 @@ export class PortfolioService {
currentGrossPerformancePercent: currentGrossPerformancePercent:
currentGrossPerformancePercent.toNumber(), currentGrossPerformancePercent.toNumber(),
currentNetPerformance: currentNetPerformance.toNumber(), currentNetPerformance: currentNetPerformance.toNumber(),
currentNetPerformancePercent: currentNetPerformancePercent.toNumber() currentNetPerformancePercent: currentNetPerformancePercent.toNumber(),
totalInvestment: totalInvestment.toNumber()
} }
}; };
} }
@ -1079,74 +1086,6 @@ export class PortfolioService {
}; };
} }
public async getSummary(aImpersonationId: string): Promise<PortfolioSummary> {
const userCurrency = this.request.user.Settings.settings.baseCurrency;
const userId = await this.getUserId(aImpersonationId, this.request.user.id);
const user = await this.userService.user({ id: userId });
const performanceInformation = await this.getPerformance(aImpersonationId);
const { balanceInBaseCurrency } = await this.accountService.getCashDetails({
userId,
currency: userCurrency
});
const orders = await this.orderService.getOrders({
userCurrency,
userId
});
const dividend = this.getDividend(orders).toNumber();
const emergencyFund = new Big(
(user.Settings?.settings as UserSettings)?.emergencyFund ?? 0
);
const fees = this.getFees(orders).toNumber();
const firstOrderDate = orders[0]?.date;
const items = this.getItems(orders).toNumber();
const totalBuy = this.getTotalByType(orders, userCurrency, 'BUY');
const totalSell = this.getTotalByType(orders, userCurrency, 'SELL');
const cash = new Big(balanceInBaseCurrency).minus(emergencyFund).toNumber();
const committedFunds = new Big(totalBuy).minus(totalSell);
const netWorth = new Big(balanceInBaseCurrency)
.plus(performanceInformation.performance.currentValue)
.plus(items)
.toNumber();
const daysInMarket = differenceInDays(new Date(), firstOrderDate);
const annualizedPerformancePercent = new PortfolioCalculator({
currency: userCurrency,
currentRateService: this.currentRateService,
orders: []
})
.getAnnualizedPerformancePercent({
daysInMarket,
netPerformancePercent: new Big(
performanceInformation.performance.currentNetPerformancePercent
)
})
?.toNumber();
return {
...performanceInformation.performance,
annualizedPerformancePercent,
cash,
dividend,
fees,
firstOrderDate,
items,
netWorth,
totalBuy,
totalSell,
committedFunds: committedFunds.toNumber(),
emergencyFund: emergencyFund.toNumber(),
ordersCount: orders.filter((order) => {
return order.type === 'BUY' || order.type === 'SELL';
}).length
};
}
private async getCashPositions({ private async getCashPositions({
cashDetails, cashDetails,
emergencyFund, emergencyFund,
@ -1322,14 +1261,121 @@ export class PortfolioService {
return portfolioStart; return portfolioStart;
} }
private async getSummary({
impersonationId
}: {
impersonationId: string;
}): Promise<PortfolioSummary> {
const userCurrency = this.request.user.Settings.settings.baseCurrency;
const userId = await this.getUserId(impersonationId, this.request.user.id);
const user = await this.userService.user({ id: userId });
const performanceInformation = await this.getPerformanceV2({
impersonationId
});
const { balanceInBaseCurrency } = await this.accountService.getCashDetails({
userId,
currency: userCurrency
});
const orders = await this.orderService.getOrders({
userCurrency,
userId
});
const excludedActivities = (
await this.orderService.getOrders({
userCurrency,
userId,
withExcludedAccounts: true
})
).filter(({ Account: account }) => {
return account?.isExcluded ?? false;
});
const dividend = this.getDividend(orders).toNumber();
const emergencyFund = new Big(
(user.Settings?.settings as UserSettings)?.emergencyFund ?? 0
);
const fees = this.getFees(orders).toNumber();
const firstOrderDate = orders[0]?.date;
const items = this.getItems(orders).toNumber();
const totalBuy = this.getTotalByType(orders, userCurrency, 'BUY');
const totalSell = this.getTotalByType(orders, userCurrency, 'SELL');
const cash = new Big(balanceInBaseCurrency).minus(emergencyFund).toNumber();
const committedFunds = new Big(totalBuy).minus(totalSell);
const totalOfExcludedActivities = new Big(
this.getTotalByType(excludedActivities, userCurrency, 'BUY')
).minus(this.getTotalByType(excludedActivities, userCurrency, 'SELL'));
const cashDetailsWithExcludedAccounts =
await this.accountService.getCashDetails({
userId,
currency: userCurrency,
withExcludedAccounts: true
});
const excludedBalanceInBaseCurrency = new Big(
cashDetailsWithExcludedAccounts.balanceInBaseCurrency
).minus(balanceInBaseCurrency);
const excludedAccountsAndActivities = excludedBalanceInBaseCurrency
.plus(totalOfExcludedActivities)
.toNumber();
const netWorth = new Big(balanceInBaseCurrency)
.plus(performanceInformation.performance.currentValue)
.plus(items)
.plus(excludedAccountsAndActivities)
.toNumber();
const daysInMarket = differenceInDays(new Date(), firstOrderDate);
const annualizedPerformancePercent = new PortfolioCalculator({
currency: userCurrency,
currentRateService: this.currentRateService,
orders: []
})
.getAnnualizedPerformancePercent({
daysInMarket,
netPerformancePercent: new Big(
performanceInformation.performance.currentNetPerformancePercent
)
})
?.toNumber();
return {
...performanceInformation.performance,
annualizedPerformancePercent,
cash,
dividend,
excludedAccountsAndActivities,
fees,
firstOrderDate,
items,
netWorth,
totalBuy,
totalSell,
committedFunds: committedFunds.toNumber(),
emergencyFund: emergencyFund.toNumber(),
ordersCount: orders.filter((order) => {
return order.type === 'BUY' || order.type === 'SELL';
}).length
};
}
private async getTransactionPoints({ private async getTransactionPoints({
filters, filters,
includeDrafts = false, includeDrafts = false,
userId userId,
withExcludedAccounts
}: { }: {
filters?: Filter[]; filters?: Filter[];
includeDrafts?: boolean; includeDrafts?: boolean;
userId: string; userId: string;
withExcludedAccounts?: boolean;
}): Promise<{ }): Promise<{
transactionPoints: TransactionPoint[]; transactionPoints: TransactionPoint[];
orders: OrderWithAccount[]; orders: OrderWithAccount[];
@ -1343,6 +1389,7 @@ export class PortfolioService {
includeDrafts, includeDrafts,
userCurrency, userCurrency,
userId, userId,
withExcludedAccounts,
types: ['BUY', 'SELL'] types: ['BUY', 'SELL']
}); });
@ -1394,17 +1441,22 @@ export class PortfolioService {
orders, orders,
portfolioItemsNow, portfolioItemsNow,
userCurrency, userCurrency,
userId userId,
withExcludedAccounts
}: { }: {
filters?: Filter[]; filters?: Filter[];
orders: OrderWithAccount[]; orders: OrderWithAccount[];
portfolioItemsNow: { [p: string]: TimelinePosition }; portfolioItemsNow: { [p: string]: TimelinePosition };
userCurrency: string; userCurrency: string;
userId: string; userId: string;
withExcludedAccounts?: boolean;
}) { }) {
const accounts: PortfolioDetails['accounts'] = {}; const accounts: PortfolioDetails['accounts'] = {};
let currentAccounts = []; let currentAccounts: (Account & {
Order?: Order[];
Platform?: Platform;
})[] = [];
if (filters.length === 0) { if (filters.length === 0) {
currentAccounts = await this.accountService.getAccounts(userId); currentAccounts = await this.accountService.getAccounts(userId);
@ -1424,6 +1476,10 @@ export class PortfolioService {
}); });
} }
currentAccounts = currentAccounts.filter((account) => {
return withExcludedAccounts || account.isExcluded === false;
});
for (const account of currentAccounts) { for (const account of currentAccounts) {
const ordersByAccount = orders.filter(({ accountId }) => { const ordersByAccount = orders.filter(({ accountId }) => {
return accountId === account.id; return accountId === account.id;

View File

@ -1,4 +1,4 @@
import type { DateRange, ViewMode } from '@ghostfolio/common/types'; import type { Appearance, DateRange, ViewMode } from '@ghostfolio/common/types';
import { import {
IsBoolean, IsBoolean,
IsIn, IsIn,
@ -8,6 +8,10 @@ import {
} from 'class-validator'; } from 'class-validator';
export class UpdateUserSettingDto { export class UpdateUserSettingDto {
@IsIn(<Appearance[]>['DARK', 'LIGHT'])
@IsOptional()
appearance?: Appearance;
@IsOptional() @IsOptional()
@IsString() @IsString()
baseCurrency?: string; baseCurrency?: string;

View File

@ -0,0 +1,9 @@
import { Module } from '@nestjs/common';
import { ApiService } from './api.service';
@Module({
exports: [ApiService],
providers: [ApiService]
})
export class ApiModule {}

View File

@ -0,0 +1,42 @@
import { Filter } from '@ghostfolio/common/interfaces';
import { Injectable } from '@nestjs/common';
@Injectable()
export class ApiService {
public constructor() {}
public buildFiltersFromQueryParams({
filterByAccounts,
filterByAssetClasses,
filterByTags
}: {
filterByAccounts?: string;
filterByAssetClasses?: string;
filterByTags?: string;
}): Filter[] {
const accountIds = filterByAccounts?.split(',') ?? [];
const assetClasses = filterByAssetClasses?.split(',') ?? [];
const tagIds = filterByTags?.split(',') ?? [];
return [
...accountIds.map((accountId) => {
return <Filter>{
id: accountId,
type: 'ACCOUNT'
};
}),
...assetClasses.map((assetClass) => {
return <Filter>{
id: assetClass,
type: 'ASSET_CLASS'
};
}),
...tagIds.map((tagId) => {
return <Filter>{
id: tagId,
type: 'TAG'
};
})
];
}
}

View File

@ -38,7 +38,7 @@ export class ConfigurationService {
MAX_ACTIVITIES_TO_IMPORT: num({ default: Number.MAX_SAFE_INTEGER }), MAX_ACTIVITIES_TO_IMPORT: num({ default: Number.MAX_SAFE_INTEGER }),
MAX_ITEM_IN_CACHE: num({ default: 9999 }), MAX_ITEM_IN_CACHE: num({ default: 9999 }),
PORT: port({ default: 3333 }), PORT: port({ default: 3333 }),
RAKUTEN_RAPID_API_KEY: str({ default: '' }), RAPID_API_API_KEY: str({ default: '' }),
REDIS_HOST: host({ default: 'localhost' }), REDIS_HOST: host({ default: 'localhost' }),
REDIS_PASSWORD: str({ default: '' }), REDIS_PASSWORD: str({ default: '' }),
REDIS_PORT: port({ default: 6379 }), REDIS_PORT: port({ default: 6379 }),

View File

@ -280,7 +280,7 @@ export class DataGatheringService {
return ( return (
dataSource !== DataSource.GHOSTFOLIO && dataSource !== DataSource.GHOSTFOLIO &&
dataSource !== DataSource.MANUAL && dataSource !== DataSource.MANUAL &&
dataSource !== DataSource.RAKUTEN dataSource !== DataSource.RAPID_API
); );
}) })
.map(({ dataSource, symbol }) => { .map(({ dataSource, symbol }) => {

View File

@ -5,7 +5,7 @@ import { EodHistoricalDataService } from '@ghostfolio/api/services/data-provider
import { GhostfolioScraperApiService } from '@ghostfolio/api/services/data-provider/ghostfolio-scraper-api/ghostfolio-scraper-api.service'; import { GhostfolioScraperApiService } from '@ghostfolio/api/services/data-provider/ghostfolio-scraper-api/ghostfolio-scraper-api.service';
import { GoogleSheetsService } from '@ghostfolio/api/services/data-provider/google-sheets/google-sheets.service'; import { GoogleSheetsService } from '@ghostfolio/api/services/data-provider/google-sheets/google-sheets.service';
import { ManualService } from '@ghostfolio/api/services/data-provider/manual/manual.service'; import { ManualService } from '@ghostfolio/api/services/data-provider/manual/manual.service';
import { RakutenRapidApiService } from '@ghostfolio/api/services/data-provider/rakuten-rapid-api/rakuten-rapid-api.service'; import { RapidApiService } from '@ghostfolio/api/services/data-provider/rapid-api/rapid-api.service';
import { YahooFinanceService } from '@ghostfolio/api/services/data-provider/yahoo-finance/yahoo-finance.service'; import { YahooFinanceService } from '@ghostfolio/api/services/data-provider/yahoo-finance/yahoo-finance.service';
import { PrismaModule } from '@ghostfolio/api/services/prisma.module'; import { PrismaModule } from '@ghostfolio/api/services/prisma.module';
import { SymbolProfileModule } from '@ghostfolio/api/services/symbol-profile.module'; import { SymbolProfileModule } from '@ghostfolio/api/services/symbol-profile.module';
@ -27,7 +27,7 @@ import { DataProviderService } from './data-provider.service';
GhostfolioScraperApiService, GhostfolioScraperApiService,
GoogleSheetsService, GoogleSheetsService,
ManualService, ManualService,
RakutenRapidApiService, RapidApiService,
YahooFinanceService, YahooFinanceService,
{ {
inject: [ inject: [
@ -36,7 +36,7 @@ import { DataProviderService } from './data-provider.service';
GhostfolioScraperApiService, GhostfolioScraperApiService,
GoogleSheetsService, GoogleSheetsService,
ManualService, ManualService,
RakutenRapidApiService, RapidApiService,
YahooFinanceService YahooFinanceService
], ],
provide: 'DataProviderInterfaces', provide: 'DataProviderInterfaces',
@ -46,7 +46,7 @@ import { DataProviderService } from './data-provider.service';
ghostfolioScraperApiService, ghostfolioScraperApiService,
googleSheetsService, googleSheetsService,
manualService, manualService,
rakutenRapidApiService, rapidApiService,
yahooFinanceService yahooFinanceService
) => [ ) => [
alphaVantageService, alphaVantageService,
@ -54,7 +54,7 @@ import { DataProviderService } from './data-provider.service';
ghostfolioScraperApiService, ghostfolioScraperApiService,
googleSheetsService, googleSheetsService,
manualService, manualService,
rakutenRapidApiService, rapidApiService,
yahooFinanceService yahooFinanceService
] ]
} }

View File

@ -1 +0,0 @@
export interface IRakutenRapidApiResponse {}

View File

@ -0,0 +1 @@
export interface IRapidApiResponse {}

View File

@ -15,14 +15,14 @@ import bent from 'bent';
import { format, subMonths, subWeeks, subYears } from 'date-fns'; import { format, subMonths, subWeeks, subYears } from 'date-fns';
@Injectable() @Injectable()
export class RakutenRapidApiService implements DataProviderInterface { export class RapidApiService implements DataProviderInterface {
public constructor( public constructor(
private readonly configurationService: ConfigurationService, private readonly configurationService: ConfigurationService,
private readonly prismaService: PrismaService private readonly prismaService: PrismaService
) {} ) {}
public canHandle(symbol: string) { public canHandle(symbol: string) {
return !!this.configurationService.get('RAKUTEN_RAPID_API_KEY'); return !!this.configurationService.get('RAPID_API_API_KEY');
} }
public async getAssetProfile( public async getAssetProfile(
@ -103,7 +103,7 @@ export class RakutenRapidApiService implements DataProviderInterface {
} }
public getName(): DataSource { public getName(): DataSource {
return DataSource.RAKUTEN; return DataSource.RAPID_API;
} }
public async getQuotes( public async getQuotes(
@ -129,7 +129,7 @@ export class RakutenRapidApiService implements DataProviderInterface {
}; };
} }
} catch (error) { } catch (error) {
Logger.error(error, 'RakutenRapidApiService'); Logger.error(error, 'RapidApiService');
} }
return {}; return {};
@ -155,16 +155,14 @@ export class RakutenRapidApiService implements DataProviderInterface {
{ {
useQueryString: true, useQueryString: true,
'x-rapidapi-host': 'fear-and-greed-index.p.rapidapi.com', 'x-rapidapi-host': 'fear-and-greed-index.p.rapidapi.com',
'x-rapidapi-key': this.configurationService.get( 'x-rapidapi-key': this.configurationService.get('RAPID_API_API_KEY')
'RAKUTEN_RAPID_API_KEY'
)
} }
); );
const { fgi } = await get(); const { fgi } = await get();
return fgi; return fgi;
} catch (error) { } catch (error) {
Logger.error(error, 'RakutenRapidApiService'); Logger.error(error, 'RapidApiService');
return undefined; return undefined;
} }

View File

@ -6,6 +6,7 @@ import {
IDataProviderHistoricalResponse, IDataProviderHistoricalResponse,
IDataProviderResponse IDataProviderResponse
} from '@ghostfolio/api/services/interfaces/interfaces'; } from '@ghostfolio/api/services/interfaces/interfaces';
import { UNKNOWN_KEY } from '@ghostfolio/common/config';
import { DATE_FORMAT, isCurrency } from '@ghostfolio/common/helper'; import { DATE_FORMAT, isCurrency } from '@ghostfolio/common/helper';
import { Granularity } from '@ghostfolio/common/types'; import { Granularity } from '@ghostfolio/common/types';
import { Injectable, Logger } from '@nestjs/common'; import { Injectable, Logger } from '@nestjs/common';
@ -57,8 +58,15 @@ export class YahooFinanceService implements DataProviderInterface {
* DOGEUSD -> DOGE-USD * DOGEUSD -> DOGE-USD
*/ */
public convertToYahooFinanceSymbol(aSymbol: string) { public convertToYahooFinanceSymbol(aSymbol: string) {
if (aSymbol.includes(this.baseCurrency) && aSymbol.length >= 6) { if (
if (isCurrency(aSymbol.substring(0, aSymbol.length - 3))) { aSymbol.includes(this.baseCurrency) &&
aSymbol.length > this.baseCurrency.length
) {
if (
isCurrency(
aSymbol.substring(0, aSymbol.length - this.baseCurrency.length)
)
) {
return `${aSymbol}=X`; return `${aSymbol}=X`;
} else if ( } else if (
this.cryptocurrencyService.isCryptocurrency( this.cryptocurrencyService.isCryptocurrency(
@ -90,7 +98,7 @@ export class YahooFinanceService implements DataProviderInterface {
try { try {
const symbol = this.convertToYahooFinanceSymbol(aSymbol); const symbol = this.convertToYahooFinanceSymbol(aSymbol);
const assetProfile = await yahooFinance.quoteSummary(symbol, { const assetProfile = await yahooFinance.quoteSummary(symbol, {
modules: ['price', 'summaryProfile'] modules: ['price', 'summaryProfile', 'topHoldings']
}); });
const { assetClass, assetSubClass } = this.parseAssetClass( const { assetClass, assetSubClass } = this.parseAssetClass(
@ -109,7 +117,16 @@ export class YahooFinanceService implements DataProviderInterface {
}); });
response.symbol = aSymbol; response.symbol = aSymbol;
if ( if (assetSubClass === AssetSubClass.MUTUALFUND) {
response.sectors = [];
for (const sectorWeighting of assetProfile.topHoldings
?.sectorWeightings ?? []) {
for (const [sector, weight] of Object.entries(sectorWeighting)) {
response.sectors.push({ weight, name: this.parseSector(sector) });
}
}
} else if (
assetSubClass === AssetSubClass.STOCK && assetSubClass === AssetSubClass.STOCK &&
assetProfile.summaryProfile?.country assetProfile.summaryProfile?.country
) { ) {
@ -437,4 +454,46 @@ export class YahooFinanceService implements DataProviderInterface {
return { assetClass, assetSubClass }; return { assetClass, assetSubClass };
} }
private parseSector(aString: string): string {
let sector = UNKNOWN_KEY;
switch (aString) {
case 'basic_materials':
sector = 'Basic Materials';
break;
case 'communication_services':
sector = 'Communication Services';
break;
case 'consumer_cyclical':
sector = 'Consumer Cyclical';
break;
case 'consumer_defensive':
sector = 'Consumer Staples';
break;
case 'energy':
sector = 'Energy';
break;
case 'financial_services':
sector = 'Financial Services';
break;
case 'healthcare':
sector = 'Healthcare';
break;
case 'industrials':
sector = 'Industrials';
break;
case 'realestate':
sector = 'Real Estate';
break;
case 'technology':
sector = 'Technology';
break;
case 'utilities':
sector = 'Utilities';
break;
}
return sector;
}
} }

View File

@ -26,7 +26,7 @@ export interface Environment extends CleanedEnvAccessors {
MAX_ACTIVITIES_TO_IMPORT: number; MAX_ACTIVITIES_TO_IMPORT: number;
MAX_ITEM_IN_CACHE: number; MAX_ITEM_IN_CACHE: number;
PORT: number; PORT: number;
RAKUTEN_RAPID_API_KEY: string; RAPID_API_API_KEY: string;
REDIS_HOST: string; REDIS_HOST: string;
REDIS_PASSWORD: string; REDIS_PASSWORD: string;
REDIS_PORT: number; REDIS_PORT: number;

View File

@ -1,6 +1,7 @@
import { IsString } from 'class-validator'; import { IsOptional, IsString } from 'class-validator';
export class PropertyDto { export class PropertyDto {
@IsOptional()
@IsString() @IsString()
value: string; value: string;
} }

View File

@ -53,13 +53,15 @@ export class TwitterBotService {
symbolItem.marketPrice symbolItem.marketPrice
); );
let status = `Current Market Mood: ${emoji} ${text} (${symbolItem.marketPrice}/100)`; let status = `Current market mood is ${emoji} ${text.toLowerCase()} (${
symbolItem.marketPrice
}/100)`;
const benchmarkListing = await this.getBenchmarkListing(3); const benchmarkListing = await this.getBenchmarkListing(3);
if (benchmarkListing?.length > 1) { if (benchmarkListing?.length > 1) {
status += '\n\n'; status += '\n\n';
status += % from ATH\n'; status += '± from ATH in %\n';
status += benchmarkListing; status += benchmarkListing;
} }

View File

@ -95,6 +95,13 @@ const routes: Routes = [
'./pages/blog/2022/08/500-stars-on-github/500-stars-on-github-page.module' './pages/blog/2022/08/500-stars-on-github/500-stars-on-github-page.module'
).then((m) => m.FiveHundredStarsOnGitHubPageModule) ).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: 'demo', path: 'demo',
loadChildren: () => loadChildren: () =>

View File

@ -13,6 +13,7 @@ import {
} from '@ghostfolio/common/config'; } from '@ghostfolio/common/config';
import { InfoItem, User } from '@ghostfolio/common/interfaces'; import { InfoItem, User } from '@ghostfolio/common/interfaces';
import { hasPermission, permissions } from '@ghostfolio/common/permissions'; import { hasPermission, permissions } from '@ghostfolio/common/permissions';
import { Appearance } from '@ghostfolio/common/types';
import { MaterialCssVarsService } from 'angular-material-css-vars'; import { MaterialCssVarsService } from 'angular-material-css-vars';
import { DeviceDetectorService } from 'ngx-device-detector'; import { DeviceDetectorService } from 'ngx-device-detector';
import { Subject } from 'rxjs'; import { Subject } from 'rxjs';
@ -77,6 +78,8 @@ export class AppComponent implements OnDestroy, OnInit {
permissions.createUserAccount permissions.createUserAccount
); );
this.initializeTheme(this.user?.settings.appearance);
this.changeDetectorRef.markForCheck(); this.changeDetectorRef.markForCheck();
}); });
} }
@ -97,13 +100,17 @@ export class AppComponent implements OnDestroy, OnInit {
this.unsubscribeSubject.complete(); this.unsubscribeSubject.complete();
} }
private initializeTheme() { private initializeTheme(userPreferredAppearance?: Appearance) {
this.materialCssVarsService.setDarkTheme( const isDarkTheme = userPreferredAppearance
window.matchMedia('(prefers-color-scheme: dark)').matches ? userPreferredAppearance === 'DARK'
); : window.matchMedia('(prefers-color-scheme: dark)').matches;
this.materialCssVarsService.setDarkTheme(isDarkTheme);
window.matchMedia('(prefers-color-scheme: dark)').addListener((event) => { window.matchMedia('(prefers-color-scheme: dark)').addListener((event) => {
this.materialCssVarsService.setDarkTheme(event.matches); if (!this.user?.settings.appearance) {
this.materialCssVarsService.setDarkTheme(event.matches);
}
}); });
this.materialCssVarsService.setPrimaryColor(primaryColorHex); this.materialCssVarsService.setPrimaryColor(primaryColorHex);

View File

@ -61,7 +61,7 @@ export class AccountDetailDialog implements OnDestroy, OnInit {
.subscribe(({ accountType, name, Platform, valueInBaseCurrency }) => { .subscribe(({ accountType, name, Platform, valueInBaseCurrency }) => {
this.accountType = accountType; this.accountType = accountType;
this.name = name; this.name = name;
this.platformName = Platform?.name; this.platformName = Platform?.name ?? '-';
this.valueInBaseCurrency = valueInBaseCurrency; this.valueInBaseCurrency = valueInBaseCurrency;
this.changeDetectorRef.markForCheck(); this.changeDetectorRef.markForCheck();

View File

@ -21,10 +21,12 @@
<div class="row"> <div class="row">
<div class="col-6 mb-3"> <div class="col-6 mb-3">
<gf-value size="medium" [value]="accountType">Account Type</gf-value> <gf-value i18n size="medium" [value]="accountType"
>Account Type</gf-value
>
</div> </div>
<div class="col-6 mb-3"> <div class="col-6 mb-3">
<gf-value size="medium" [value]="platformName">Platform</gf-value> <gf-value i18n size="medium" [value]="platformName">Platform</gf-value>
</div> </div>
</div> </div>

View File

@ -2,7 +2,10 @@
<div class="row"> <div class="row">
<div class="col"> <div class="col">
<form class="align-items-center d-flex" [formGroup]="filterForm"> <form class="align-items-center d-flex" [formGroup]="filterForm">
<mat-form-field appearance="outline" class="flex-grow-1"> <mat-form-field
appearance="outline"
class="compact-with-outline flex-grow-1 mr-2 without-hint"
>
<mat-select formControlName="status"> <mat-select formControlName="status">
<mat-option></mat-option> <mat-option></mat-option>
<mat-option <mat-option
@ -13,7 +16,7 @@
</mat-select> </mat-select>
</mat-form-field> </mat-form-field>
<button <button
class="ml-1" class="mt-1"
color="warn" color="warn"
mat-flat-button mat-flat-button
(click)="onDeleteJobs()" (click)="onDeleteJobs()"

View File

@ -2,6 +2,7 @@
<gf-line-chart <gf-line-chart
class="mb-4" class="mb-4"
[historicalDataItems]="historicalDataItems" [historicalDataItems]="historicalDataItems"
[isAnimated]="true"
[locale]="locale" [locale]="locale"
[showXAxis]="true" [showXAxis]="true"
[showYAxis]="true" [showYAxis]="true"

View File

@ -99,7 +99,7 @@ export class AdminOverviewComponent implements OnDestroy, OnInit {
...this.coupons, ...this.coupons,
{ code: this.generateCouponCode(16), duration: this.couponDuration } { code: this.generateCouponCode(16), duration: this.couponDuration }
]; ];
this.putCoupons(coupons); this.putAdminSetting({ key: PROPERTY_COUPONS, value: coupons });
} }
public onAddCurrency() { public onAddCurrency() {
@ -107,7 +107,7 @@ export class AdminOverviewComponent implements OnDestroy, OnInit {
if (currency) { if (currency) {
const currencies = uniq([...this.customCurrencies, currency]); const currencies = uniq([...this.customCurrencies, currency]);
this.putCurrencies(currencies); this.putAdminSetting({ key: PROPERTY_CURRENCIES, value: currencies });
} }
} }
@ -124,7 +124,7 @@ export class AdminOverviewComponent implements OnDestroy, OnInit {
const coupons = this.coupons.filter((coupon) => { const coupons = this.coupons.filter((coupon) => {
return coupon.code !== aCouponCode; return coupon.code !== aCouponCode;
}); });
this.putCoupons(coupons); this.putAdminSetting({ key: PROPERTY_COUPONS, value: coupons });
} }
} }
@ -137,12 +137,12 @@ export class AdminOverviewComponent implements OnDestroy, OnInit {
const currencies = this.customCurrencies.filter((currency) => { const currencies = this.customCurrencies.filter((currency) => {
return currency !== aCurrency; return currency !== aCurrency;
}); });
this.putCurrencies(currencies); this.putAdminSetting({ key: PROPERTY_CURRENCIES, value: currencies });
} }
} }
public onDeleteSystemMessage() { public onDeleteSystemMessage() {
this.putSystemMessage(''); this.putAdminSetting({ key: PROPERTY_SYSTEM_MESSAGE, value: undefined });
} }
public onFlushCache() { public onFlushCache() {
@ -192,14 +192,20 @@ export class AdminOverviewComponent implements OnDestroy, OnInit {
} }
public onReadOnlyModeChange(aEvent: MatSlideToggleChange) { public onReadOnlyModeChange(aEvent: MatSlideToggleChange) {
this.setReadOnlyMode(aEvent.checked); this.putAdminSetting({
key: PROPERTY_IS_READ_ONLY_MODE,
value: aEvent.checked ? true : undefined
});
} }
public onSetSystemMessage() { public onSetSystemMessage() {
const systemMessage = prompt($localize`Please set your system message:`); const systemMessage = prompt($localize`Please set your system message:`);
if (systemMessage) { if (systemMessage) {
this.putSystemMessage(systemMessage); this.putAdminSetting({
key: PROPERTY_SYSTEM_MESSAGE,
value: systemMessage
});
} }
} }
@ -236,49 +242,10 @@ export class AdminOverviewComponent implements OnDestroy, OnInit {
return couponCode; return couponCode;
} }
private putCoupons(aCoupons: Coupon[]) { private putAdminSetting({ key, value }: { key: string; value: any }) {
this.dataService this.dataService
.putAdminSetting(PROPERTY_COUPONS, { .putAdminSetting(key, {
value: JSON.stringify(aCoupons) value: value ? JSON.stringify(value) : undefined
})
.pipe(takeUntil(this.unsubscribeSubject))
.subscribe(() => {
setTimeout(() => {
window.location.reload();
}, 300);
});
}
private putCurrencies(aCurrencies: string[]) {
this.dataService
.putAdminSetting(PROPERTY_CURRENCIES, {
value: JSON.stringify(aCurrencies)
})
.pipe(takeUntil(this.unsubscribeSubject))
.subscribe(() => {
setTimeout(() => {
window.location.reload();
}, 300);
});
}
private putSystemMessage(aSystemMessage: string) {
this.dataService
.putAdminSetting(PROPERTY_SYSTEM_MESSAGE, {
value: aSystemMessage
})
.pipe(takeUntil(this.unsubscribeSubject))
.subscribe(() => {
setTimeout(() => {
window.location.reload();
}, 300);
});
}
private setReadOnlyMode(aValue: boolean) {
this.dataService
.putAdminSetting(PROPERTY_IS_READ_ONLY_MODE, {
value: aValue ? 'true' : ''
}) })
.pipe(takeUntil(this.unsubscribeSubject)) .pipe(takeUntil(this.unsubscribeSubject))
.subscribe(() => { .subscribe(() => {

View File

@ -5,15 +5,26 @@
<mat-card-content> <mat-card-content>
<div class="d-flex my-3"> <div class="d-flex my-3">
<div class="w-50" i18n>User Count</div> <div class="w-50" i18n>User Count</div>
<div class="w-50">{{ userCount }}</div> <div class="w-50">
<gf-value
precision="0"
[locale]="user?.settings?.locale"
[value]="userCount"
></gf-value>
</div>
</div> </div>
<div class="d-flex my-3"> <div class="d-flex my-3">
<div class="w-50" i18n>Activity Count</div> <div class="w-50" i18n>Activity Count</div>
<div class="w-50"> <div class="w-50">
<ng-container *ngIf="transactionCount"> <gf-value
{{ transactionCount }} ({{ transactionCount / userCount | number precision="0"
: '1.2-2' }} <span i18n>per User</span>) [locale]="user?.settings?.locale"
</ng-container> [value]="transactionCount"
></gf-value>
<div *ngIf="transactionCount && userCount">
{{ transactionCount / userCount | number : '1.2-2' }}
<span i18n>per User</span>
</div>
</div> </div>
</div> </div>
<div class="d-flex my-3"> <div class="d-flex my-3">
@ -108,10 +119,20 @@
</div> </div>
</div> </div>
</div> </div>
<div class="align-items-start d-flex my-3">
<div class="w-50" i18n>Benchmarks</div>
<div class="w-50">
<table>
<tr *ngFor="let benchmark of info?.benchmarks">
<td class="pl-1">{{ benchmark.symbol }}</td>
</tr>
</table>
</div>
</div>
<div *ngIf="hasPermissionForSystemMessage" class="d-flex my-3"> <div *ngIf="hasPermissionForSystemMessage" class="d-flex my-3">
<div class="w-50" i18n>System Message</div> <div class="w-50" i18n>System Message</div>
<div class="w-50"> <div class="w-50">
<div *ngIf="info.systemMessage"> <div *ngIf="info?.systemMessage">
<span>{{ info.systemMessage }}</span> <span>{{ info.systemMessage }}</span>
<button <button
class="mini-icon mx-1 no-min-width px-2" class="mini-icon mx-1 no-min-width px-2"
@ -122,7 +143,7 @@
</button> </button>
</div> </div>
<button <button
*ngIf="!info.systemMessage" *ngIf="!info?.systemMessage"
color="accent" color="accent"
mat-flat-button mat-flat-button
(click)="onSetSystemMessage()" (click)="onSetSystemMessage()"
@ -162,8 +183,11 @@
</button> </button>
</div> </div>
<div class="mt-2"> <div class="mt-2">
<form #couponForm="ngForm"> <form #couponForm="ngForm" class="align-items-center d-flex">
<mat-form-field appearance="outline" class="mr-2"> <mat-form-field
appearance="outline"
class="compact-with-outline mr-2 without-hint"
>
<mat-select <mat-select
name="duration" name="duration"
[value]="couponDuration" [value]="couponDuration"
@ -176,6 +200,7 @@
</mat-select> </mat-select>
</mat-form-field> </mat-form-field>
<button <button
class="mt-1"
color="primary" color="primary"
mat-flat-button mat-flat-button
(click)="onAddCoupon()" (click)="onAddCoupon()"

View File

@ -43,23 +43,23 @@
<td class="mat-cell px-1 py-2 text-right"> <td class="mat-cell px-1 py-2 text-right">
{{ formatDistanceToNow(userItem.createdAt) }} {{ formatDistanceToNow(userItem.createdAt) }}
</td> </td>
<td class="mat-cell px-1 py-2"> <td class="mat-cell px-1 py-2 text-right">
<gf-value <gf-value
class="align-items-end" class="d-inline-block justify-content-end"
[locale]="user?.settings?.locale" [locale]="user?.settings?.locale"
[value]="userItem.accountCount" [value]="userItem.accountCount"
></gf-value> ></gf-value>
</td> </td>
<td class="mat-cell px-1 py-2"> <td class="mat-cell px-1 py-2 text-right">
<gf-value <gf-value
class="align-items-end" class="d-inline-block justify-content-end"
[locale]="user?.settings?.locale" [locale]="user?.settings?.locale"
[value]="userItem.transactionCount" [value]="userItem.transactionCount"
></gf-value> ></gf-value>
</td> </td>
<td class="mat-cell px-1 py-2"> <td class="mat-cell px-1 py-2 text-right">
<gf-value <gf-value
class="align-items-end" class="d-inline-block justify-content-end"
[locale]="user?.settings?.locale" [locale]="user?.settings?.locale"
[precision]="0" [precision]="0"
[value]="userItem.engagement" [value]="userItem.engagement"

View File

@ -1,8 +1,7 @@
<div class="row"> <div class="mb-2 row">
<div class="col-md-6 col-xs-12 d-flex"> <div class="col-md-6 col-xs-12 d-flex">
<div class="align-items-center d-flex flex-grow-1 h5 mb-0 text-truncate"> <div class="align-items-center d-flex flex-grow-1 h5 mb-0 text-truncate">
<span i18n>Benchmarks</span> <span i18n>Performance</span>
<sup i18n>Beta</sup>
<gf-premium-indicator <gf-premium-indicator
*ngIf="user?.subscription?.type === 'Basic'" *ngIf="user?.subscription?.type === 'Basic'"
class="ml-1" class="ml-1"
@ -10,13 +9,19 @@
</div> </div>
</div> </div>
<div class="col-md-6 col-xs-12 d-flex justify-content-end"> <div class="col-md-6 col-xs-12 d-flex justify-content-end">
<mat-form-field appearance="outline" class="w-100" color="accent"> <mat-form-field
appearance="outline"
class="w-100 without-hint"
color="accent"
[hidden]="benchmarks?.length === 0"
>
<mat-label i18n>Compare with...</mat-label> <mat-label i18n>Compare with...</mat-label>
<mat-select <mat-select
name="benchmark" name="benchmark"
[value]="benchmark" [value]="benchmark"
(selectionChange)="onChangeBenchmark($event.value)" (selectionChange)="onChangeBenchmark($event.value)"
> >
<mat-option [value]="null"></mat-option>
<mat-option <mat-option
*ngFor="let symbolProfile of benchmarks" *ngFor="let symbolProfile of benchmarks"
[value]="symbolProfile.id" [value]="symbolProfile.id"
@ -26,14 +31,6 @@
</mat-form-field> </mat-form-field>
</div> </div>
</div> </div>
<div *ngIf="user.settings.viewMode !== 'ZEN'" class="mb-3 text-center">
<gf-toggle
[defaultValue]="user?.settings?.dateRange"
[isLoading]="isLoading"
[options]="dateRangeOptions"
(change)="onChangeDateRange($event.value)"
></gf-toggle>
</div>
<div class="chart-container"> <div class="chart-container">
<ngx-skeleton-loader <ngx-skeleton-loader
*ngIf="isLoading" *ngIf="isLoading"

View File

@ -10,7 +10,6 @@ import {
Output, Output,
ViewChild ViewChild
} from '@angular/core'; } from '@angular/core';
import { ToggleComponent } from '@ghostfolio/client/components/toggle/toggle.component';
import { import {
getTooltipOptions, getTooltipOptions,
getTooltipPositionerMapTop, getTooltipPositionerMapTop,
@ -24,7 +23,6 @@ import {
parseDate parseDate
} from '@ghostfolio/common/helper'; } from '@ghostfolio/common/helper';
import { LineChartItem, User } from '@ghostfolio/common/interfaces'; import { LineChartItem, User } from '@ghostfolio/common/interfaces';
import { DateRange } from '@ghostfolio/common/types';
import { import {
Chart, Chart,
LineController, LineController,
@ -54,12 +52,10 @@ export class BenchmarkComparatorComponent implements OnChanges, OnDestroy {
@Input() user: User; @Input() user: User;
@Output() benchmarkChanged = new EventEmitter<string>(); @Output() benchmarkChanged = new EventEmitter<string>();
@Output() dateRangeChanged = new EventEmitter<DateRange>();
@ViewChild('chartCanvas') chartCanvas; @ViewChild('chartCanvas') chartCanvas;
public chart: Chart<any>; public chart: Chart<any>;
public dateRangeOptions = ToggleComponent.DEFAULT_DATE_RANGE_OPTIONS;
public constructor() { public constructor() {
Chart.register( Chart.register(
@ -86,10 +82,6 @@ export class BenchmarkComparatorComponent implements OnChanges, OnDestroy {
this.benchmarkChanged.next(symbolProfileId); this.benchmarkChanged.next(symbolProfileId);
} }
public onChangeDateRange(dateRange: DateRange) {
this.dateRangeChanged.next(dateRange);
}
public ngOnDestroy() { public ngOnDestroy() {
this.chart?.destroy(); this.chart?.destroy();
} }

View File

@ -2,7 +2,6 @@ import { CommonModule } from '@angular/common';
import { NgModule } from '@angular/core'; import { NgModule } from '@angular/core';
import { FormsModule, ReactiveFormsModule } from '@angular/forms'; import { FormsModule, ReactiveFormsModule } from '@angular/forms';
import { MatSelectModule } from '@angular/material/select'; import { MatSelectModule } from '@angular/material/select';
import { GfToggleModule } from '@ghostfolio/client/components/toggle/toggle.module';
import { NgxSkeletonLoaderModule } from 'ngx-skeleton-loader'; import { NgxSkeletonLoaderModule } from 'ngx-skeleton-loader';
import { BenchmarkComparatorComponent } from './benchmark-comparator.component'; import { BenchmarkComparatorComponent } from './benchmark-comparator.component';
@ -13,7 +12,6 @@ import { BenchmarkComparatorComponent } from './benchmark-comparator.component';
imports: [ imports: [
CommonModule, CommonModule,
FormsModule, FormsModule,
GfToggleModule,
MatSelectModule, MatSelectModule,
NgxSkeletonLoaderModule, NgxSkeletonLoaderModule,
ReactiveFormsModule ReactiveFormsModule

View File

@ -24,7 +24,7 @@ export class HomeMarketComponent implements OnDestroy, OnInit {
public fearLabel = $localize`Fear`; public fearLabel = $localize`Fear`;
public greedLabel = $localize`Greed`; public greedLabel = $localize`Greed`;
public hasPermissionToAccessFearAndGreedIndex: boolean; public hasPermissionToAccessFearAndGreedIndex: boolean;
public historicalData: HistoricalDataItem[]; public historicalDataItems: HistoricalDataItem[];
public info: InfoItem; public info: InfoItem;
public isLoading = true; public isLoading = true;
public readonly numberOfDays = 180; public readonly numberOfDays = 180;
@ -67,7 +67,7 @@ export class HomeMarketComponent implements OnDestroy, OnInit {
.pipe(takeUntil(this.unsubscribeSubject)) .pipe(takeUntil(this.unsubscribeSubject))
.subscribe(({ historicalData, marketPrice }) => { .subscribe(({ historicalData, marketPrice }) => {
this.fearAndGreedIndex = marketPrice; this.fearAndGreedIndex = marketPrice;
this.historicalData = [ this.historicalDataItems = [
...historicalData, ...historicalData,
{ {
date: resetHours(new Date()).toISOString(), date: resetHours(new Date()).toISOString(),

View File

@ -10,7 +10,8 @@
symbol="Fear & Greed Index" symbol="Fear & Greed Index"
yMax="100" yMax="100"
yMin="0" yMin="0"
[historicalDataItems]="historicalData" [historicalDataItems]="historicalDataItems"
[isAnimated]="true"
[locale]="user?.settings?.locale" [locale]="user?.settings?.locale"
[showXAxis]="true" [showXAxis]="true"
[showYAxis]="true" [showYAxis]="true"

View File

@ -76,8 +76,6 @@ export class HomeOverviewComponent implements OnDestroy, OnInit {
!this.hasImpersonationId && !this.hasImpersonationId &&
!this.user.settings.isRestrictedView && !this.user.settings.isRestrictedView &&
this.user.settings.viewMode !== 'ZEN'; this.user.settings.viewMode !== 'ZEN';
this.update();
} }
public onChangeDateRange(dateRange: DateRange) { public onChangeDateRange(dateRange: DateRange) {
@ -104,36 +102,29 @@ export class HomeOverviewComponent implements OnDestroy, OnInit {
} }
private update() { private update() {
this.historicalDataItems = null;
this.isLoadingPerformance = true; this.isLoadingPerformance = true;
this.dataService this.dataService
.fetchChart({ .fetchPortfolioPerformance({
range: this.user?.settings?.dateRange, range: this.user?.settings?.dateRange
version: this.user?.settings?.isExperimentalFeatures ? 2 : 1
}) })
.pipe(takeUntil(this.unsubscribeSubject)) .pipe(takeUntil(this.unsubscribeSubject))
.subscribe((chartData) => {
this.historicalDataItems = chartData.chart.map((chartDataItem) => {
return {
date: chartDataItem.date,
value: chartDataItem.value
};
});
this.isAllTimeHigh = chartData.isAllTimeHigh;
this.isAllTimeLow = chartData.isAllTimeLow;
this.changeDetectorRef.markForCheck();
});
this.dataService
.fetchPortfolioPerformance({ range: this.user?.settings?.dateRange })
.pipe(takeUntil(this.unsubscribeSubject))
.subscribe((response) => { .subscribe((response) => {
this.errors = response.errors; this.errors = response.errors;
this.hasError = response.hasErrors; this.hasError = response.hasErrors;
this.performance = response.performance; this.performance = response.performance;
this.isLoadingPerformance = false; this.isLoadingPerformance = false;
this.historicalDataItems = response.chart.map(
({ date, netPerformanceInPercentage }) => {
return {
date,
value: netPerformanceInPercentage
};
}
);
this.changeDetectorRef.markForCheck(); this.changeDetectorRef.markForCheck();
}); });

View File

@ -15,16 +15,16 @@
<gf-line-chart <gf-line-chart
class="position-absolute" class="position-absolute"
symbol="Performance" symbol="Performance"
[currency]="user?.settings?.isExperimentalFeatures ? undefined : user?.settings?.baseCurrency" unit="%"
[historicalDataItems]="historicalDataItems" [historicalDataItems]="historicalDataItems"
[hidden]="historicalDataItems?.length === 0" [hidden]="historicalDataItems?.length === 0"
[isAnimated]="user?.settings?.dateRange === '1d' ? false : true"
[locale]="user?.settings?.locale" [locale]="user?.settings?.locale"
[ngClass]="{ 'pr-3': deviceType === 'mobile' }" [ngClass]="{ 'pr-3': deviceType === 'mobile' }"
[showGradient]="true" [showGradient]="true"
[showLoader]="false" [showLoader]="false"
[showXAxis]="false" [showXAxis]="false"
[showYAxis]="false" [showYAxis]="false"
[unit]="user?.settings?.isExperimentalFeatures ? '%' : undefined"
></gf-line-chart> ></gf-line-chart>
</div> </div>
</div> </div>

View File

@ -1,8 +1,18 @@
import { ChangeDetectorRef, Component, OnDestroy, OnInit } from '@angular/core'; import { ChangeDetectorRef, Component, OnDestroy, OnInit } from '@angular/core';
import {
MatSnackBar,
MatSnackBarRef,
TextOnlySnackBar
} from '@angular/material/snack-bar';
import { Router } from '@angular/router';
import { DataService } from '@ghostfolio/client/services/data.service'; import { DataService } from '@ghostfolio/client/services/data.service';
import { ImpersonationStorageService } from '@ghostfolio/client/services/impersonation-storage.service'; import { ImpersonationStorageService } from '@ghostfolio/client/services/impersonation-storage.service';
import { UserService } from '@ghostfolio/client/services/user/user.service'; import { UserService } from '@ghostfolio/client/services/user/user.service';
import { PortfolioSummary, User } from '@ghostfolio/common/interfaces'; import {
InfoItem,
PortfolioSummary,
User
} from '@ghostfolio/common/interfaces';
import { hasPermission, permissions } from '@ghostfolio/common/permissions'; import { hasPermission, permissions } from '@ghostfolio/common/permissions';
import { Subject } from 'rxjs'; import { Subject } from 'rxjs';
import { takeUntil } from 'rxjs/operators'; import { takeUntil } from 'rxjs/operators';
@ -14,8 +24,11 @@ import { takeUntil } from 'rxjs/operators';
}) })
export class HomeSummaryComponent implements OnDestroy, OnInit { export class HomeSummaryComponent implements OnDestroy, OnInit {
public hasImpersonationId: boolean; public hasImpersonationId: boolean;
public hasPermissionForSubscription: boolean;
public hasPermissionToUpdateUserSettings: boolean; public hasPermissionToUpdateUserSettings: boolean;
public info: InfoItem;
public isLoading = true; public isLoading = true;
public snackBarRef: MatSnackBarRef<TextOnlySnackBar>;
public summary: PortfolioSummary; public summary: PortfolioSummary;
public user: User; public user: User;
@ -25,8 +38,17 @@ export class HomeSummaryComponent implements OnDestroy, OnInit {
private changeDetectorRef: ChangeDetectorRef, private changeDetectorRef: ChangeDetectorRef,
private dataService: DataService, private dataService: DataService,
private impersonationStorageService: ImpersonationStorageService, private impersonationStorageService: ImpersonationStorageService,
private router: Router,
private snackBar: MatSnackBar,
private userService: UserService private userService: UserService
) { ) {
this.info = this.dataService.fetchInfo();
this.hasPermissionForSubscription = hasPermission(
this.info?.globalPermissions,
permissions.enableSubscription
);
this.userService.stateChanged this.userService.stateChanged
.pipe(takeUntil(this.unsubscribeSubject)) .pipe(takeUntil(this.unsubscribeSubject))
.subscribe((state) => { .subscribe((state) => {
@ -50,8 +72,6 @@ export class HomeSummaryComponent implements OnDestroy, OnInit {
.subscribe((aId) => { .subscribe((aId) => {
this.hasImpersonationId = !!aId; this.hasImpersonationId = !!aId;
}); });
this.update();
} }
public onChangeEmergencyFund(emergencyFund: number) { public onChangeEmergencyFund(emergencyFund: number) {
@ -81,12 +101,30 @@ export class HomeSummaryComponent implements OnDestroy, OnInit {
this.isLoading = true; this.isLoading = true;
this.dataService this.dataService
.fetchPortfolioSummary() .fetchPortfolioDetails({})
.pipe(takeUntil(this.unsubscribeSubject)) .pipe(takeUntil(this.unsubscribeSubject))
.subscribe((response) => { .subscribe(({ summary }) => {
this.summary = response; this.summary = summary;
this.isLoading = false; this.isLoading = false;
if (!this.summary) {
this.snackBarRef = this.snackBar.open(
$localize`This feature requires a subscription.`,
this.hasPermissionForSubscription
? $localize`Upgrade Plan`
: undefined,
{ duration: 6000 }
);
this.snackBarRef.afterDismissed().subscribe(() => {
this.snackBarRef = undefined;
});
this.snackBarRef.onAction().subscribe(() => {
this.router.navigate(['/pricing']);
});
}
this.changeDetectorRef.markForCheck(); this.changeDetectorRef.markForCheck();
}); });

View File

@ -15,14 +15,16 @@ import {
} from '@ghostfolio/common/chart-helper'; } from '@ghostfolio/common/chart-helper';
import { primaryColorRgb, secondaryColorRgb } from '@ghostfolio/common/config'; import { primaryColorRgb, secondaryColorRgb } from '@ghostfolio/common/config';
import { import {
DATE_FORMAT,
getBackgroundColor, getBackgroundColor,
getDateFormatString, getDateFormatString,
getTextColor, getTextColor,
parseDate, parseDate,
transformTickToAbbreviation transformTickToAbbreviation
} from '@ghostfolio/common/helper'; } from '@ghostfolio/common/helper';
import { LineChartItem } from '@ghostfolio/common/interfaces';
import { InvestmentItem } from '@ghostfolio/common/interfaces/investment-item.interface'; import { InvestmentItem } from '@ghostfolio/common/interfaces/investment-item.interface';
import { GroupBy } from '@ghostfolio/common/types'; import { DateRange, GroupBy } from '@ghostfolio/common/types';
import { import {
BarController, BarController,
BarElement, BarElement,
@ -35,7 +37,7 @@ import {
Tooltip Tooltip
} from 'chart.js'; } from 'chart.js';
import annotationPlugin from 'chartjs-plugin-annotation'; import annotationPlugin from 'chartjs-plugin-annotation';
import { addDays, isAfter, parseISO, subDays } from 'date-fns'; import { addDays, format, isAfter, parseISO, subDays } from 'date-fns';
@Component({ @Component({
selector: 'gf-investment-chart', selector: 'gf-investment-chart',
@ -44,17 +46,19 @@ import { addDays, isAfter, parseISO, subDays } from 'date-fns';
styleUrls: ['./investment-chart.component.scss'] styleUrls: ['./investment-chart.component.scss']
}) })
export class InvestmentChartComponent implements OnChanges, OnDestroy { export class InvestmentChartComponent implements OnChanges, OnDestroy {
@Input() benchmarkDataItems: LineChartItem[] = [];
@Input() currency: string; @Input() currency: string;
@Input() daysInMarket: number; @Input() daysInMarket: number;
@Input() groupBy: GroupBy; @Input() groupBy: GroupBy;
@Input() investments: InvestmentItem[]; @Input() investments: InvestmentItem[];
@Input() isInPercent = false; @Input() isInPercent = false;
@Input() locale: string; @Input() locale: string;
@Input() range: DateRange = 'max';
@Input() savingsRate = 0; @Input() savingsRate = 0;
@ViewChild('chartCanvas') chartCanvas; @ViewChild('chartCanvas') chartCanvas;
public chart: Chart; public chart: Chart<any>;
public isLoading = true; public isLoading = true;
private data: InvestmentItem[]; private data: InvestmentItem[];
@ -77,7 +81,7 @@ export class InvestmentChartComponent implements OnChanges, OnDestroy {
} }
public ngOnChanges() { public ngOnChanges() {
if (this.investments) { if (this.benchmarkDataItems && this.investments) {
this.initialize(); this.initialize();
} }
} }
@ -93,41 +97,44 @@ export class InvestmentChartComponent implements OnChanges, OnDestroy {
this.data = this.investments.map((a) => Object.assign({}, a)); this.data = this.investments.map((a) => Object.assign({}, a));
if (!this.groupBy && this.data?.length > 0) { if (!this.groupBy && this.data?.length > 0) {
// Extend chart by 5% of days in market (before) if (this.range === 'max') {
const firstItem = this.data[0]; // Extend chart by 5% of days in market (before)
this.data.unshift({ const firstItem = this.data[0];
...firstItem, this.data.unshift({
date: subDays( ...firstItem,
parseISO(firstItem.date), date: format(
this.daysInMarket * 0.05 || 90 subDays(parseISO(firstItem.date), this.daysInMarket * 0.05 || 90),
).toISOString(), DATE_FORMAT
investment: 0 ),
}); investment: 0
});
}
// Extend chart by 5% of days in market (after) // Extend chart by 5% of days in market (after)
const lastItem = this.data[this.data.length - 1]; const lastItem = this.data[this.data.length - 1];
this.data.push({ this.data.push({
...lastItem, ...lastItem,
date: addDays( date: format(
parseDate(lastItem.date), addDays(parseDate(lastItem.date), this.daysInMarket * 0.05 || 90),
this.daysInMarket * 0.05 || 90 DATE_FORMAT
).toISOString() )
}); });
} }
const data = { const data = {
labels: this.data.map((investmentItem) => { labels: this.benchmarkDataItems.map(({ date }) => {
return investmentItem.date; return parseDate(date);
}), }),
datasets: [ datasets: [
{ {
backgroundColor: `rgb(${primaryColorRgb.r}, ${primaryColorRgb.g}, ${primaryColorRgb.b})`, backgroundColor: `rgb(${primaryColorRgb.r}, ${primaryColorRgb.g}, ${primaryColorRgb.b})`,
borderColor: `rgb(${primaryColorRgb.r}, ${primaryColorRgb.g}, ${primaryColorRgb.b})`, borderColor: `rgb(${primaryColorRgb.r}, ${primaryColorRgb.g}, ${primaryColorRgb.b})`,
borderWidth: this.groupBy ? 0 : 2, borderWidth: this.groupBy ? 0 : 2,
data: this.data.map((position) => { data: this.data.map(({ date, investment }) => {
return this.isInPercent return {
? position.investment * 100 x: parseDate(date),
: position.investment; y: this.isInPercent ? investment * 100 : investment
};
}), }),
label: $localize`Deposit`, label: $localize`Deposit`,
segment: { segment: {
@ -139,6 +146,19 @@ export class InvestmentChartComponent implements OnChanges, OnDestroy {
borderDash: (context: unknown) => this.isInFuture(context, [2, 2]) borderDash: (context: unknown) => this.isInFuture(context, [2, 2])
}, },
stepped: true stepped: true
},
{
borderColor: `rgb(${secondaryColorRgb.r}, ${secondaryColorRgb.g}, ${secondaryColorRgb.b})`,
borderWidth: 1,
data: this.benchmarkDataItems.map(({ date, value }) => {
return {
x: parseDate(date),
y: this.isInPercent ? value * 100 : value
};
}),
fill: false,
label: $localize`Total Amount`,
pointRadius: 0
} }
] ]
}; };

View File

@ -172,6 +172,17 @@
></gf-value> ></gf-value>
</div> </div>
</div> </div>
<div class="row px-3 py-1">
<div class="d-flex flex-grow-1" i18n>Excluded from Analysis</div>
<div class="d-flex justify-content-end">
<gf-value
class="justify-content-end"
[currency]="baseCurrency"
[locale]="locale"
[value]="isLoading ? undefined : summary?.excludedAccountsAndActivities"
></gf-value>
</div>
</div>
<div class="row"> <div class="row">
<div class="col"><hr /></div> <div class="col"><hr /></div>
</div> </div>

View File

@ -25,6 +25,7 @@
[benchmarkDataItems]="benchmarkDataItems" [benchmarkDataItems]="benchmarkDataItems"
[currency]="SymbolProfile?.currency" [currency]="SymbolProfile?.currency"
[historicalDataItems]="historicalDataItems" [historicalDataItems]="historicalDataItems"
[isAnimated]="true"
[locale]="data.locale" [locale]="data.locale"
[showGradient]="true" [showGradient]="true"
[showXAxis]="true" [showXAxis]="true"

View File

@ -42,6 +42,7 @@ export class AccountPageComponent implements OnDestroy, OnInit {
signInWithFingerprintElement: MatSlideToggle; signInWithFingerprintElement: MatSlideToggle;
public accesses: Access[]; public accesses: Access[];
public appearancePlaceholder = $localize`Auto`;
public baseCurrency: string; public baseCurrency: string;
public coupon: number; public coupon: number;
public couponId: string; public couponId: string;
@ -54,7 +55,7 @@ export class AccountPageComponent implements OnDestroy, OnInit {
public hasPermissionToUpdateViewMode: boolean; public hasPermissionToUpdateViewMode: boolean;
public hasPermissionToUpdateUserSettings: boolean; public hasPermissionToUpdateUserSettings: boolean;
public language = document.documentElement.lang; public language = document.documentElement.lang;
public locales = ['de', 'de-CH', 'en-GB', 'en-US']; public locales = ['de', 'de-CH', 'en-GB', 'en-US', 'es', 'it', 'nl'];
public price: number; public price: number;
public priceId: string; public priceId: string;
public snackBarRef: MatSnackBarRef<TextOnlySnackBar>; public snackBarRef: MatSnackBarRef<TextOnlySnackBar>;

View File

@ -94,7 +94,10 @@
<ng-container i18n>Base Currency</ng-container> <ng-container i18n>Base Currency</ng-container>
</div> </div>
<div class="pl-1 w-50"> <div class="pl-1 w-50">
<mat-form-field appearance="outline" class="w-100"> <mat-form-field
appearance="outline"
class="compact-with-outline w-100 without-hint"
>
<mat-select <mat-select
name="baseCurrency" name="baseCurrency"
[disabled]="!hasPermissionToUpdateUserSettings" [disabled]="!hasPermissionToUpdateUserSettings"
@ -116,7 +119,10 @@
<div class="hint-text text-muted" i18n>Beta</div> <div class="hint-text text-muted" i18n>Beta</div>
</div> </div>
<div class="pl-1 w-50"> <div class="pl-1 w-50">
<mat-form-field appearance="outline" class="w-100"> <mat-form-field
appearance="outline"
class="compact-with-outline w-100 without-hint"
>
<mat-select <mat-select
name="language" name="language"
[disabled]="!hasPermissionToUpdateUserSettings" [disabled]="!hasPermissionToUpdateUserSettings"
@ -126,6 +132,9 @@
<mat-option [value]="null"></mat-option> <mat-option [value]="null"></mat-option>
<mat-option value="de">Deutsch</mat-option> <mat-option value="de">Deutsch</mat-option>
<mat-option value="en">English</mat-option> <mat-option value="en">English</mat-option>
<mat-option value="es">Español</mat-option>
<mat-option value="it">Italiano</mat-option>
<mat-option value="nl">Nederlands</mat-option>
</mat-select> </mat-select>
</mat-form-field> </mat-form-field>
</div> </div>
@ -138,7 +147,10 @@
</div> </div>
</div> </div>
<div class="pl-1 w-50"> <div class="pl-1 w-50">
<mat-form-field appearance="outline" class="w-100"> <mat-form-field
appearance="outline"
class="compact-with-outline w-100 without-hint"
>
<mat-select <mat-select
name="locale" name="locale"
[disabled]="!hasPermissionToUpdateUserSettings" [disabled]="!hasPermissionToUpdateUserSettings"
@ -155,13 +167,16 @@
</mat-form-field> </mat-form-field>
</div> </div>
</div> </div>
<div class="d-flex"> <div class="d-flex mb-2">
<div class="align-items-center d-flex pr-1 pt-1 w-50"> <div class="align-items-center d-flex pr-1 pt-1 w-50">
<ng-container i18n>View Mode</ng-container> <ng-container i18n>View Mode</ng-container>
</div> </div>
<div class="pl-1 w-50"> <div class="pl-1 w-50">
<div class="align-items-center d-flex overflow-hidden"> <div class="align-items-center d-flex overflow-hidden">
<mat-form-field appearance="outline" class="w-100"> <mat-form-field
appearance="outline"
class="compact-with-outline w-100 without-hint"
>
<mat-select <mat-select
name="viewMode" name="viewMode"
[disabled]="!hasPermissionToUpdateViewMode" [disabled]="!hasPermissionToUpdateViewMode"
@ -175,6 +190,30 @@
</div> </div>
</div> </div>
</div> </div>
<div class="d-flex">
<div class="align-items-center d-flex pr-1 pt-1 w-50">
<ng-container i18n>Appearance</ng-container>
</div>
<div class="pl-1 w-50">
<mat-form-field
appearance="outline"
class="compact-with-outline w-100 without-hint"
>
<mat-select
name="appearance"
class="with-placeholder-as-option"
[disabled]="!hasPermissionToUpdateUserSettings"
[placeholder]="appearancePlaceholder"
[value]="user.settings.appearance"
(selectionChange)="onChangeUserSetting('appearance', $event.value)"
>
<mat-option i18n [value]="null">Auto</mat-option>
<mat-option i18n value="LIGHT">Light</mat-option>
<mat-option i18n value="DARK">Dark</mat-option>
</mat-select>
</mat-form-field>
</div>
</div>
</form> </form>
</div> </div>
<div class="align-items-center d-flex mt-4 py-1"> <div class="align-items-center d-flex mt-4 py-1">

View File

@ -59,8 +59,8 @@ export class AccountsPageComponent implements OnDestroy, OnInit {
this.openCreateAccountDialog(); this.openCreateAccountDialog();
} else if (params['editDialog']) { } else if (params['editDialog']) {
if (this.accounts) { if (this.accounts) {
const account = this.accounts.find((account) => { const account = this.accounts.find(({ id }) => {
return account.id === params['accountId']; return id === params['accountId'];
}); });
this.openUpdateAccountDialog(account); this.openUpdateAccountDialog(account);
@ -155,6 +155,7 @@ export class AccountsPageComponent implements OnDestroy, OnInit {
balance, balance,
currency, currency,
id, id,
isExcluded,
name, name,
platformId platformId
}: AccountModel): void { }: AccountModel): void {
@ -165,6 +166,7 @@ export class AccountsPageComponent implements OnDestroy, OnInit {
balance, balance,
currency, currency,
id, id,
isExcluded,
name, name,
platformId platformId
} }
@ -231,6 +233,7 @@ export class AccountsPageComponent implements OnDestroy, OnInit {
accountType: AccountType.SECURITIES, accountType: AccountType.SECURITIES,
balance: 0, balance: 0,
currency: this.user?.settings?.baseCurrency, currency: this.user?.settings?.baseCurrency,
isExcluded: false,
name: null, name: null,
platformId: null platformId: null
} }

View File

@ -50,6 +50,14 @@
</mat-select> </mat-select>
</mat-form-field> </mat-form-field>
</div> </div>
<div class="mb-3 px-2">
<mat-checkbox
color="primary"
name="isExcluded"
[(ngModel)]="data.account.isExcluded"
>Exclude from Analysis</mat-checkbox
>
</div>
<div *ngIf="data.account.id"> <div *ngIf="data.account.id">
<mat-form-field appearance="outline" class="w-100"> <mat-form-field appearance="outline" class="w-100">
<mat-label i18n>Account ID</mat-label> <mat-label i18n>Account ID</mat-label>

View File

@ -2,6 +2,7 @@ import { CommonModule } from '@angular/common';
import { NgModule } from '@angular/core'; import { NgModule } from '@angular/core';
import { FormsModule, ReactiveFormsModule } from '@angular/forms'; import { FormsModule, ReactiveFormsModule } from '@angular/forms';
import { MatButtonModule } from '@angular/material/button'; import { MatButtonModule } from '@angular/material/button';
import { MatCheckboxModule } from '@angular/material/checkbox';
import { MatDialogModule } from '@angular/material/dialog'; import { MatDialogModule } from '@angular/material/dialog';
import { MatFormFieldModule } from '@angular/material/form-field'; import { MatFormFieldModule } from '@angular/material/form-field';
import { MatInputModule } from '@angular/material/input'; import { MatInputModule } from '@angular/material/input';
@ -15,6 +16,7 @@ import { CreateOrUpdateAccountDialog } from './create-or-update-account-dialog.c
CommonModule, CommonModule,
FormsModule, FormsModule,
MatButtonModule, MatButtonModule,
MatCheckboxModule,
MatDialogModule, MatDialogModule,
MatFormFieldModule, MatFormFieldModule,
MatInputModule, MatInputModule,

View File

@ -119,7 +119,7 @@
Anlagestrategie? Ich freue mich über alle, die Ghostfolio Anlagestrategie? Ich freue mich über alle, die Ghostfolio
ausprobieren. Bist du überzeugt vom Potential der Software? Jede ausprobieren. Bist du überzeugt vom Potential der Software? Jede
Unterstützung für Ghostfolio ist willkommen. Sei es mit einer Unterstützung für Ghostfolio ist willkommen. Sei es mit einer
<a href="https://ghostfol.io/pricing">Ghostfolio Premium</a> <a [routerLink]="['/pricing']">Ghostfolio Premium</a>
Subscription zur Finanzierung des Hostings, einem positiven Rating Subscription zur Finanzierung des Hostings, einem positiven Rating
im im
<a <a

View File

@ -115,7 +115,7 @@
strategy? I'm happy for everyone who tries Ghostfolio. Are you strategy? I'm happy for everyone who tries Ghostfolio. Are you
convinced of its potential? Any support for Ghostfolio is welcome. convinced of its potential? Any support for Ghostfolio is welcome.
Be it with a Be it with a
<a href="https://ghostfol.io/pricing">Ghostfolio Premium</a> <a [routerLink]="['/pricing']">Ghostfolio Premium</a>
Subscription to finance the hosting, a positive rating in the Subscription to finance the hosting, a positive rating in the
<a <a
href="https://play.google.com/store/apps/details?id=ch.dotsilver.ghostfolio.twa" href="https://play.google.com/store/apps/details?id=ch.dotsilver.ghostfolio.twa"

View File

@ -74,7 +74,7 @@
<a [routerLink]="['/markets']">economic situation</a> at this time, <a [routerLink]="['/markets']">economic situation</a> at this time,
the goal set at the beginning of the year to build a sustainable the goal set at the beginning of the year to build a sustainable
business and reach break-even with the SaaS offering (<a business and reach break-even with the SaaS offering (<a
[routerLink]="['/markets']" [routerLink]="['/pricing']"
>Ghostfolio Premium</a >Ghostfolio Premium</a
>) has been achieved. We will continue to leverage the revenue to >) has been achieved. We will continue to leverage the revenue to
further improve the fully managed cloud offering for our paying further improve the fully managed cloud offering for our paying

View File

@ -0,0 +1,20 @@
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

@ -0,0 +1,9 @@
import { Component } from '@angular/core';
@Component({
host: { class: 'page' },
selector: 'gf-hacktoberfest-2022-page',
styleUrls: ['./hacktoberfest-2022-page.scss'],
templateUrl: './hacktoberfest-2022-page.html'
})
export class Hacktoberfest2022PageComponent {}

View File

@ -0,0 +1,178 @@
<div class="blog container">
<div class="row">
<div class="col-md-8 offset-md-2">
<article>
<div class="mb-4 text-center">
<h1 class="mb-1">Hacktoberfest 2022</h1>
<div class="mb-3 text-muted"><small>2022-10-01</small></div>
<img
alt="Hacktoberfest 2022 with Ghostfolio Teaser"
class="rounded w-100"
src="../assets/images/blog/hacktoberfest-2022.png"
title="Hacktoberfest 2022 with Ghostfolio"
/>
</div>
<section class="mb-4">
<p>
We are very excited to join
<a href="https://hacktoberfest.com">Hacktoberfest</a> for the first
time with <a href="https://ghostfol.io">Ghostfolio</a> and meet new
and ambitious open-source contributors. Hacktoberfest is a
month-long celebration of open-source projects, their maintainers,
and the entire community of contributors. Each October, open source
maintainers from all over the world give extra attention to new
contributors while guiding them through their first pull requests on
<a href="https://github.com/ghostfolio/ghostfolio">GitHub</a>.
</p>
</section>
<section class="mb-4">
<h2 class="h4">About Ghostfolio</h2>
<p>
Ghostfolio is a modern web application to manage your personal
finance. The software presents the current assets in real time and
supports the decision making of future investments. Whether
rebalancing the asset classes (stocks, ETFs, cryptocurrencies, etc.)
of your portfolio or financing an apartment, Ghostfolio offers
solid, data-driven decision support.
</p>
<p>
Ghostfolio is written in
<a href="https://www.typescriptlang.org">TypeScript</a> and
organized as an <a href="https://nx.dev">Nx</a> workspace. The
backend is based on <a href="https://nestjs.com">NestJS</a> using
<a href="https://www.postgresql.org">PostgreSQL</a> as a database
together with <a href="https://www.prisma.io">Prisma</a> and
<a href="https://redis.io">Redis</a> for caching. The frontend is
built with <a href="https://angular.io">Angular</a>.
</p>
</section>
<section class="mb-4">
<h2 class="h4">How to contribute?</h2>
<p>
Every contribution matters. This can be implementing new features,
fixing bugs, refactoring the code, improving the documentation,
adding more unit tests, or translating into another language.
</p>
<p>
Are you not yet familiar with our code base? That is not a problem.
We have applied the label <code>hacktoberfest</code> to a few
<a
href="https://github.com/ghostfolio/ghostfolio/issues?q=is%3Aissue+is%3Aopen+label%3Ahacktoberfest"
>issues</a
>
and
<a
href="https://github.com/ghostfolio/ghostfolio/discussions?discussions_q=label%3Ahacktoberfest"
>ideas</a
>
that are well suited for newcomers.
</p>
<p>
The official Hacktoberfest website provides some valuable
<a
href="https://hacktoberfest.com/participation/#beginner-resources"
>resources for beginners</a
>
to start contributing in open source.
</p>
</section>
<section class="mb-4">
<h2 class="h4">Get support</h2>
<p>
If you have further questions or ideas, please join our growing
<a href="https://ghostfolio.slack.com">Slack community</a> or get in
touch on Twitter
<a href="https://twitter.com/ghostfolio_">@ghostfolio_</a> or by
email via <a href="mailto:hi@ghostfol.io">hi@ghostfol.io</a>.
</p>
<p>
We look forward to hearing from you.<br />
Thomas from Ghostfolio
</p>
</section>
<section class="mb-4">
<ul class="list-inline">
<li class="list-inline-item">
<span class="badge badge-light">Angular</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">Cryptocurrency</span>
</li>
<li class="list-inline-item">
<span class="badge badge-light">ETF</span>
</li>
<li class="list-inline-item">
<span class="badge badge-light">Finance</span>
</li>
<li class="list-inline-item">
<span class="badge badge-light">Fintech</span>
</li>
<li class="list-inline-item">
<span class="badge badge-light">Ghostfolio</span>
</li>
<li class="list-inline-item">
<span class="badge badge-light">GitHub</span>
</li>
<li class="list-inline-item">
<span class="badge badge-light">Hacktoberfest</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">NestJS</span>
</li>
<li class="list-inline-item">
<span class="badge badge-light">Nx</span>
</li>
<li class="list-inline-item">
<span class="badge badge-light">October</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">Portfolio</span>
</li>
<li class="list-inline-item">
<span class="badge badge-light">Portfolio Tracker</span>
</li>
<li class="list-inline-item">
<span class="badge badge-light">Prisma</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">Stock</span>
</li>
<li class="list-inline-item">
<span class="badge badge-light">TypeScript</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>
<li class="list-inline-item">
<span class="badge badge-light">Web3</span>
</li>
<li class="list-inline-item">
<span class="badge badge-light">Web 3.0</span>
</li>
</ul>
</section>
</article>
</div>
</div>
</div>

View File

@ -0,0 +1,13 @@
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

@ -0,0 +1,3 @@
:host {
display: block;
}

View File

@ -2,6 +2,30 @@
<div class="mb-5 row"> <div class="mb-5 row">
<div class="col"> <div class="col">
<h3 class="mb-3 text-center" i18n>Blog</h3> <h3 class="mb-3 text-center" i18n>Blog</h3>
<mat-card class="mb-3">
<mat-card-content>
<div class="container p-0">
<div class="flex-nowrap no-gutters row">
<a
class="d-flex w-100"
href="../en/blog/2022/10/hacktoberfest-2022"
>
<div class="flex-grow-1">
<div class="h6 m-0 text-truncate">Hacktoberfest 2022</div>
<div class="d-flex text-muted">2022-10-01</div>
</div>
<div class="align-items-center d-flex">
<ion-icon
class="chevron text-muted"
name="chevron-forward-outline"
size="small"
></ion-icon>
</div>
</a>
</div>
</div>
</mat-card-content>
</mat-card>
<mat-card class="mb-3"> <mat-card class="mb-3">
<mat-card-content> <mat-card-content>
<div class="container p-0"> <div class="container p-0">

View File

@ -192,6 +192,17 @@
</div> </div>
</mat-card> </mat-card>
</div> </div>
<div class="col-xs-12 col-md-4 mb-3">
<mat-card class="d-flex flex-column h-100">
<div class="flex-grow-1">
<h4>Multi-Language</h4>
<p class="m-0">
Use Ghostfolio in multiple languages: English, Dutch, German,
Italian and Spanish are currently supported.
</p>
</div>
</mat-card>
</div>
<div class="col-xs-12 col-md-4 mb-3"> <div class="col-xs-12 col-md-4 mb-3">
<mat-card class="d-flex flex-column h-100"> <mat-card class="d-flex flex-column h-100">
<div class="flex-grow-1"> <div class="flex-grow-1">

View File

@ -1,4 +1,7 @@
import { Component, OnDestroy, OnInit } from '@angular/core'; import { Component, OnDestroy, OnInit } from '@angular/core';
import { DataService } from '@ghostfolio/client/services/data.service';
import { Statistics } from '@ghostfolio/common/interfaces/statistics.interface';
import { hasPermission, permissions } from '@ghostfolio/common/permissions';
import { format } from 'date-fns'; import { format } from 'date-fns';
import { Subject } from 'rxjs'; import { Subject } from 'rxjs';
@ -11,6 +14,8 @@ import { Subject } from 'rxjs';
export class LandingPageComponent implements OnDestroy, OnInit { export class LandingPageComponent implements OnDestroy, OnInit {
public currentYear = format(new Date(), 'yyyy'); public currentYear = format(new Date(), 'yyyy');
public demoAuthToken: string; public demoAuthToken: string;
public hasPermissionForStatistics: boolean;
public statistics: Statistics;
public testimonials = [ public testimonials = [
{ {
author: 'Philipp', author: 'Philipp',
@ -36,7 +41,16 @@ export class LandingPageComponent implements OnDestroy, OnInit {
private unsubscribeSubject = new Subject<void>(); private unsubscribeSubject = new Subject<void>();
public constructor() {} public constructor(private dataService: DataService) {
const { globalPermissions, statistics } = this.dataService.fetchInfo();
this.hasPermissionForStatistics = hasPermission(
globalPermissions,
permissions.enableStatistics
);
this.statistics = statistics;
}
public ngOnInit() {} public ngOnInit() {}

View File

@ -42,6 +42,103 @@
</div> </div>
</div> </div>
<div *ngIf="hasPermissionForStatistics" class="row mb-5">
<div class="col-md-4 d-flex my-1">
<a
class="d-block"
title="Ghostfolio in Numbers: Monthly Active Users (MAU)"
[routerLink]="['/about']"
>
<gf-value
icon="people-outline"
size="large"
[value]="statistics?.activeUsers30d ?? '-'"
>Monthly Active Users</gf-value
>
</a>
</div>
<div class="col-md-4 d-flex my-1">
<a
class="d-block"
title="Ghostfolio in Numbers: Stars on GitHub"
[routerLink]="['/about']"
>
<gf-value
icon="star-outline"
size="large"
[value]="statistics?.gitHubStargazers ?? '-'"
>Stars on GitHub</gf-value
>
</a>
</div>
<div class="col-md-4 d-flex my-1">
<a
class="d-block"
title="Ghostfolio in Numbers: Pulls on Docker Hub"
[routerLink]="['/about']"
>
<gf-value
icon="cloud-download-outline"
size="large"
[value]="statistics?.dockerHubPulls ?? '-'"
>Pulls on Docker Hub</gf-value
>
</a>
</div>
</div>
<div class="row mb-5">
<div class="col-12 text-center text-muted"><small>As seen in</small></div>
<div class="col-md-2 d-flex justify-content-center my-1">
<a
class="d-block logo logo-alternative-to mask"
href="https://alternativeto.net"
target="_blank"
title="AlternativeTo - Crowdsourced software recommendations"
></a>
</div>
<div class="col-md-2 d-flex justify-content-center my-1">
<a
class="d-block logo logo-awesome"
href="https://github.com/awesome-selfhosted/awesome-selfhosted"
target="_blank"
title="Awesome-Selfhosted: A list of Free Software network services and web applications which can be hosted on your own servers"
></a>
</div>
<div class="col-md-2 d-flex justify-content-center my-1">
<a
class="d-block logo logo-openstartup"
href="https://openstartup.tm"
target="_blank"
title="Open Startup: The most complete list of open startups"
></a>
</div>
<div class="col-md-2 d-flex justify-content-center my-1">
<a
class="d-block logo logo-privacy-tools mask"
href="https://www.privacytools.io"
target="_blank"
title="Privacy Tools: Software Alternatives and Encryption"
></a>
</div>
<div class="col-md-2 d-flex justify-content-center my-1">
<a
class="d-block logo logo-product-hunt"
href="https://www.producthunt.com"
target="_blank"
title="Product Hunt The best new products in tech."
></a>
</div>
<div class="col-md-2 d-flex justify-content-center my-1">
<a
class="d-block logo logo-unraid mask"
href="https://unraid.net"
target="_blank"
title="Unraid | Unleash Your Hardware"
></a>
</div>
</div>
<div class="row my-5"> <div class="row my-5">
<div class="col text-center"> <div class="col text-center">
<h2 class="h4 mb-1 text-center"> <h2 class="h4 mb-1 text-center">
@ -55,6 +152,28 @@
</div> </div>
</div> </div>
<div class="row my-3">
<div class="col-md-4 my-2">
<mat-card>
<mat-card-title>360° View</mat-card-title>
Get the full picture of your personal finances across multiple
platforms.
</mat-card>
</div>
<div class="col-md-4 my-2">
<mat-card>
<mat-card-title>Web3 Ready</mat-card-title>
Use Ghostfolio anonymously and own your financial data.
</mat-card>
</div>
<div class="col-md-4 my-2">
<mat-card>
<mat-card-title>Open Source</mat-card-title>
Benefit from continuous improvements through a strong community.
</mat-card>
</div>
</div>
<div class="row my-5"> <div class="row my-5">
<div class="col-md-6 offset-md-3"> <div class="col-md-6 offset-md-3">
<h2 class="h4 mb-1 text-center">Why <strong>Ghostfolio</strong>?</h2> <h2 class="h4 mb-1 text-center">Why <strong>Ghostfolio</strong>?</h2>
@ -133,24 +252,48 @@
</div> </div>
</div> </div>
<div class="row my-5"> <div class="row my-3">
<div class="col-md-6 offset-md-3"> <div class="col-12">
<h2 class="h4 mb-1 text-center"> <h2 class="h4 mb-1 text-center">
How does <strong>Ghostfolio</strong> work? How does <strong>Ghostfolio</strong> work?
</h2> </h2>
<p class="lead mb-3 text-center">Get started in only 3 steps</p> <p class="lead mb-3 text-center">Get started in only 3 steps</p>
<ol class="m-0 pl-3"> </div>
<li class="mb-2"> <div class="col-md-4 my-2">
Sign up anonymously<br />(no e-mail address nor credit card required) <mat-card class="d-flex flex-row h-100">
</li> <div class="flex-grow-1">
<li class="mb-2">Add any of your historical transactions</li> <div class="font-weight-bold">Sign up anonymously*</div>
<li>Get valuable insights of your portfolio composition</li> <div class="text-muted">
</ol> <small>* no e-mail address nor credit card required</small>
</div>
</div>
<div class="pl-2 text-muted text-right">1</div>
</mat-card>
</div>
<div class="col-md-4 my-2">
<mat-card class="d-flex flex-row h-100">
<div class="flex-grow-1">
<div class="font-weight-bold">
Add any of your historical transactions
</div>
</div>
<div class="pl-2 text-muted text-right">2</div>
</mat-card>
</div>
<div class="col-md-4 my-2">
<mat-card class="d-flex flex-row h-100">
<div class="flex-grow-1">
<div class="font-weight-bold">
Get valuable insights of your portfolio composition
</div>
</div>
<div class="pl-2 text-muted text-right">3</div>
</mat-card>
</div> </div>
</div> </div>
<div class="row my-5"> <div class="row my-5">
<div class="col-md-6 offset-md-3"> <div class="col">
<h2 class="h4 mb-1 text-center">Are <strong>you</strong> ready?</h2> <h2 class="h4 mb-1 text-center">Are <strong>you</strong> ready?</h2>
<p class="lead mb-3 text-center"> <p class="lead mb-3 text-center">
Join now or check out the example account Join now or check out the example account
@ -194,7 +337,7 @@
<div class="row"> <div class="row">
<div class="align-items-center d-flex flex-column w-100"> <div class="align-items-center d-flex flex-column w-100">
<a <a
class="agplv3-logo" class="logo logo-agplv3 mask"
href="https://www.gnu.org/licenses/agpl-3.0.html" href="https://www.gnu.org/licenses/agpl-3.0.html"
target="_blank" target="_blank"
title="GNU Affero General Public License Version 3" title="GNU Affero General Public License Version 3"

View File

@ -1,8 +1,10 @@
import { CommonModule } from '@angular/common'; import { CommonModule } from '@angular/common';
import { CUSTOM_ELEMENTS_SCHEMA, NgModule } from '@angular/core'; import { CUSTOM_ELEMENTS_SCHEMA, NgModule } from '@angular/core';
import { MatButtonModule } from '@angular/material/button'; import { MatButtonModule } from '@angular/material/button';
import { MatCardModule } from '@angular/material/card';
import { RouterModule } from '@angular/router'; import { RouterModule } from '@angular/router';
import { GfLogoModule } from '@ghostfolio/ui/logo'; import { GfLogoModule } from '@ghostfolio/ui/logo';
import { GfValueModule } from '@ghostfolio/ui/value';
import { LandingPageRoutingModule } from './landing-page-routing.module'; import { LandingPageRoutingModule } from './landing-page-routing.module';
import { LandingPageComponent } from './landing-page.component'; import { LandingPageComponent } from './landing-page.component';
@ -12,8 +14,10 @@ import { LandingPageComponent } from './landing-page.component';
imports: [ imports: [
CommonModule, CommonModule,
GfLogoModule, GfLogoModule,
GfValueModule,
LandingPageRoutingModule, LandingPageRoutingModule,
MatButtonModule, MatButtonModule,
MatCardModule,
RouterModule RouterModule
], ],
schemas: [CUSTOM_ELEMENTS_SCHEMA] schemas: [CUSTOM_ELEMENTS_SCHEMA]

View File

@ -3,16 +3,6 @@
:host { :host {
display: block; display: block;
.agplv3-logo {
background-color: rgba(var(--dark-primary-text));
height: 3rem;
mask-image: url('/assets/images/AGPLv3-logo.svg');
mask-position: center;
mask-repeat: no-repeat;
mask-size: contain;
width: 7.5rem;
}
.button-container { .button-container {
.mat-stroked-button { .mat-stroked-button {
background-color: var(--light-background); background-color: var(--light-background);
@ -34,6 +24,58 @@
} }
} }
.logo {
height: 3rem;
width: 7.5rem;
&.mask {
background-color: rgba(var(--dark-secondary-text));
mask-position: center;
mask-repeat: no-repeat;
mask-size: contain;
}
&.logo-agplv3 {
mask-image: url('/assets/images/logo-AGPLv3.svg');
}
&.logo-alternative-to {
mask-image: url('/assets/images/logo-alternative-to.svg');
}
&.logo-awesome {
background-image: url('/assets/images/logo-awesome.png');
background-position: center;
background-repeat: no-repeat;
background-size: contain;
filter: grayscale(1);
}
&.logo-openstartup {
background-image: url('/assets/images/logo-openstartup.png');
background-position: center;
background-repeat: no-repeat;
background-size: contain;
filter: grayscale(1);
}
&.logo-privacy-tools {
mask-image: url('/assets/images/logo-privacy-tools.svg');
}
&.logo-product-hunt {
background-image: url('/assets/images/logo-product-hunt.png');
background-position: center;
background-repeat: no-repeat;
background-size: contain;
filter: grayscale(1);
}
&.logo-unraid {
mask-image: url('/assets/images/logo-unraid.svg');
}
}
.outro-inner-container { .outro-inner-container {
aspect-ratio: 16 / 9; aspect-ratio: 16 / 9;
max-height: 66vh; max-height: 66vh;
@ -56,16 +98,21 @@
} }
:host-context(.is-dark-theme) { :host-context(.is-dark-theme) {
.agplv3-logo {
background-color: rgba(var(--light-primary-text));
}
.button-container { .button-container {
.mat-stroked-button { .mat-stroked-button {
background-color: var(--dark-background); background-color: var(--dark-background);
} }
} }
.logo {
&.logo-agplv3,
&.logo-alternative-to,
&.logo-privacy-tools,
&.logo-unraid {
background-color: rgba(var(--light-primary-text));
}
}
.outro-inner-container { .outro-inner-container {
div { div {
background-image: url('/assets/intro-dark.jpg') !important; background-image: url('/assets/intro-dark.jpg') !important;

View File

@ -13,8 +13,16 @@
<div class="row"> <div class="row">
<div class="col"> <div class="col">
<mat-card class="mb-3"> <mat-card class="mb-3">
<mat-card-header> <mat-card-header class="mb-2 overflow-hidden w-100">
<mat-card-title i18n>Proportion of Net Worth</mat-card-title> <mat-card-title class="m-0 text-truncate" i18n
>Proportion of Net Worth</mat-card-title
>
<gf-value
class="justify-content-end l-2"
size="medium"
[isPercent]="true"
[value]="isLoading ? undefined : portfolioDetails?.filteredValueInPercentage"
></gf-value>
</mat-card-header> </mat-card-header>
<mat-card-content> <mat-card-content>
<mat-progress-bar <mat-progress-bar

View File

@ -20,6 +20,7 @@
::ng-deep { ::ng-deep {
.mat-card-header-text { .mat-card-header-text {
flex: 1 1 auto; flex: 1 1 auto;
overflow: hidden;
} }
} }

View File

@ -1,4 +1,5 @@
import { ChangeDetectorRef, Component, OnDestroy, OnInit } from '@angular/core'; import { ChangeDetectorRef, Component, OnDestroy, OnInit } from '@angular/core';
import { ToggleComponent } from '@ghostfolio/client/components/toggle/toggle.component';
import { DataService } from '@ghostfolio/client/services/data.service'; import { DataService } from '@ghostfolio/client/services/data.service';
import { ImpersonationStorageService } from '@ghostfolio/client/services/impersonation-storage.service'; import { ImpersonationStorageService } from '@ghostfolio/client/services/impersonation-storage.service';
import { UserService } from '@ghostfolio/client/services/user/user.service'; import { UserService } from '@ghostfolio/client/services/user/user.service';
@ -26,6 +27,7 @@ export class AnalysisPageComponent implements OnDestroy, OnInit {
public benchmarkDataItems: HistoricalDataItem[] = []; public benchmarkDataItems: HistoricalDataItem[] = [];
public benchmarks: Partial<SymbolProfile>[]; public benchmarks: Partial<SymbolProfile>[];
public bottom3: Position[]; public bottom3: Position[];
public dateRangeOptions = ToggleComponent.DEFAULT_DATE_RANGE_OPTIONS;
public daysInMarket: number; public daysInMarket: number;
public deviceType: string; public deviceType: string;
public firstOrderDate: Date; public firstOrderDate: Date;
@ -33,12 +35,12 @@ export class AnalysisPageComponent implements OnDestroy, OnInit {
public investments: InvestmentItem[]; public investments: InvestmentItem[];
public investmentsByMonth: InvestmentItem[]; public investmentsByMonth: InvestmentItem[];
public isLoadingBenchmarkComparator: boolean; public isLoadingBenchmarkComparator: boolean;
public mode: GroupBy; public mode: GroupBy = 'month';
public modeOptions: ToggleOption[] = [ public modeOptions: ToggleOption[] = [
{ label: $localize`Monthly`, value: 'month' }, { label: $localize`Monthly`, value: 'month' }
{ label: $localize`Accumulating`, value: undefined }
]; ];
public performanceDataItems: HistoricalDataItem[]; public performanceDataItems: HistoricalDataItem[];
public performanceDataItemsInPercentage: HistoricalDataItem[];
public top3: Position[]; public top3: Position[];
public user: User; public user: User;
@ -122,34 +124,48 @@ export class AnalysisPageComponent implements OnDestroy, OnInit {
} }
private update() { private update() {
if (this.user.settings.isExperimentalFeatures) { this.isLoadingBenchmarkComparator = true;
this.isLoadingBenchmarkComparator = true;
this.dataService
.fetchChart({ range: this.user?.settings?.dateRange, version: 2 })
.pipe(takeUntil(this.unsubscribeSubject))
.subscribe(({ chart }) => {
this.firstOrderDate = new Date(chart?.[0]?.date ?? new Date());
this.performanceDataItems = chart;
this.updateBenchmarkDataItems();
this.changeDetectorRef.markForCheck();
});
}
this.dataService this.dataService
.fetchInvestments() .fetchPortfolioPerformance({
range: this.user?.settings?.dateRange
})
.pipe(takeUntil(this.unsubscribeSubject)) .pipe(takeUntil(this.unsubscribeSubject))
.subscribe(({ firstOrderDate, investments }) => { .subscribe(({ chart, firstOrderDate }) => {
this.firstOrderDate = firstOrderDate ?? new Date();
this.daysInMarket = differenceInDays(new Date(), firstOrderDate); this.daysInMarket = differenceInDays(new Date(), firstOrderDate);
this.investments = investments;
this.investments = [];
this.performanceDataItems = [];
this.performanceDataItemsInPercentage = [];
for (const {
date,
netPerformanceInPercentage,
totalInvestment,
value
} of chart) {
this.investments.push({ date, investment: totalInvestment });
this.performanceDataItems.push({
date,
value
});
this.performanceDataItemsInPercentage.push({
date,
value: netPerformanceInPercentage
});
}
this.updateBenchmarkDataItems();
this.changeDetectorRef.markForCheck(); this.changeDetectorRef.markForCheck();
}); });
this.dataService this.dataService
.fetchInvestmentsByMonth() .fetchInvestments({
groupBy: 'month',
range: this.user?.settings?.dateRange
})
.pipe(takeUntil(this.unsubscribeSubject)) .pipe(takeUntil(this.unsubscribeSubject))
.subscribe(({ investments }) => { .subscribe(({ investments }) => {
this.investmentsByMonth = investments; this.investmentsByMonth = investments;
@ -158,7 +174,7 @@ export class AnalysisPageComponent implements OnDestroy, OnInit {
}); });
this.dataService this.dataService
.fetchPositions({ range: 'max' }) .fetchPositions({ range: this.user?.settings?.dateRange })
.pipe(takeUntil(this.unsubscribeSubject)) .pipe(takeUntil(this.unsubscribeSubject))
.subscribe(({ positions }) => { .subscribe(({ positions }) => {
const positionsSorted = sortBy( const positionsSorted = sortBy(
@ -207,6 +223,8 @@ export class AnalysisPageComponent implements OnDestroy, OnInit {
this.changeDetectorRef.markForCheck(); this.changeDetectorRef.markForCheck();
}); });
} else { } else {
this.benchmarkDataItems = [];
this.isLoadingBenchmarkComparator = false; this.isLoadingBenchmarkComparator = false;
} }
} }

View File

@ -1,6 +1,14 @@
<div class="container"> <div class="container">
<h3 class="d-flex justify-content-center mb-3" i18n>Analysis</h3> <h3 class="d-flex justify-content-center" i18n>Analysis</h3>
<div *ngIf="user?.settings?.isExperimentalFeatures" class="mb-5 row"> <div *ngIf="user?.settings?.viewMode !== 'ZEN'" class="my-4 text-center">
<gf-toggle
[defaultValue]="user?.settings?.dateRange"
[isLoading]="isLoadingBenchmarkComparator"
[options]="dateRangeOptions"
(change)="onChangeDateRange($event.value)"
></gf-toggle>
</div>
<div class="mb-5 row">
<div class="col-lg"> <div class="col-lg">
<gf-benchmark-comparator <gf-benchmark-comparator
class="h-100" class="h-100"
@ -10,10 +18,9 @@
[daysInMarket]="daysInMarket" [daysInMarket]="daysInMarket"
[isLoading]="isLoadingBenchmarkComparator" [isLoading]="isLoadingBenchmarkComparator"
[locale]="user?.settings?.locale" [locale]="user?.settings?.locale"
[performanceDataItems]="performanceDataItems" [performanceDataItems]="performanceDataItemsInPercentage"
[user]="user" [user]="user"
(benchmarkChanged)="onChangeBenchmark($event)" (benchmarkChanged)="onChangeBenchmark($event)"
(dateRangeChanged)="onChangeDateRange($event)"
></gf-benchmark-comparator> ></gf-benchmark-comparator>
</div> </div>
</div> </div>
@ -96,6 +103,34 @@
</div> </div>
</div> </div>
<div class="mb-5 row">
<div class="col-lg">
<div class="align-items-center d-flex mb-4">
<div
class="align-items-center d-flex flex-grow-1 h5 mb-0 text-truncate"
>
<span i18n>Portfolio Evolution</span>
<gf-premium-indicator
*ngIf="user?.subscription?.type === 'Basic'"
class="ml-1"
></gf-premium-indicator>
</div>
</div>
<div class="chart-container">
<gf-investment-chart
class="h-100"
[benchmarkDataItems]="performanceDataItems"
[currency]="user?.settings?.baseCurrency"
[daysInMarket]="daysInMarket"
[investments]="investments"
[isInPercent]="hasImpersonationId || user.settings.isRestrictedView"
[locale]="user?.settings?.locale"
[range]="user?.settings?.dateRange"
></gf-investment-chart>
</div>
</div>
</div>
<div class="row"> <div class="row">
<div class="col-lg"> <div class="col-lg">
<div class="align-items-center d-flex mb-4"> <div class="align-items-center d-flex mb-4">
@ -117,24 +152,15 @@
></gf-toggle> ></gf-toggle>
</div> </div>
<div class="chart-container"> <div class="chart-container">
<gf-investment-chart
class="h-100"
[currency]="user?.settings?.baseCurrency"
[daysInMarket]="daysInMarket"
[isInPercent]="hasImpersonationId || user.settings.isRestrictedView"
[investments]="investments"
[locale]="user?.settings?.locale"
[ngClass]="{ 'd-none': mode }"
></gf-investment-chart>
<gf-investment-chart <gf-investment-chart
class="h-100" class="h-100"
groupBy="month" groupBy="month"
[currency]="user?.settings?.baseCurrency" [currency]="user?.settings?.baseCurrency"
[daysInMarket]="daysInMarket" [daysInMarket]="daysInMarket"
[isInPercent]="hasImpersonationId || user.settings.isRestrictedView"
[investments]="investmentsByMonth" [investments]="investmentsByMonth"
[isInPercent]="hasImpersonationId || user.settings.isRestrictedView"
[locale]="user?.settings?.locale" [locale]="user?.settings?.locale"
[ngClass]="{ 'd-none': !mode }" [range]="user?.settings?.dateRange"
[savingsRate]="(hasImpersonationId || user.settings.isRestrictedView) ? undefined : user?.settings?.savingsRate" [savingsRate]="(hasImpersonationId || user.settings.isRestrictedView) ? undefined : user?.settings?.savingsRate"
></gf-investment-chart> ></gf-investment-chart>
</div> </div>

View File

@ -37,14 +37,14 @@ export class FirePageComponent implements OnDestroy, OnInit {
this.deviceType = this.deviceService.getDeviceInfo().deviceType; this.deviceType = this.deviceService.getDeviceInfo().deviceType;
this.dataService this.dataService
.fetchPortfolioSummary() .fetchPortfolioDetails({})
.pipe(takeUntil(this.unsubscribeSubject)) .pipe(takeUntil(this.unsubscribeSubject))
.subscribe(({ cash, currentValue }) => { .subscribe(({ summary }) => {
if (cash === null || currentValue === null) { if (summary.cash === null || summary.currentValue === null) {
return; return;
} }
this.fireWealth = new Big(currentValue); this.fireWealth = new Big(summary.currentValue);
this.withdrawalRatePerYear = this.fireWealth.mul(4).div(100); this.withdrawalRatePerYear = this.fireWealth.mul(4).div(100);
this.withdrawalRatePerMonth = this.withdrawalRatePerYear.div(12); this.withdrawalRatePerMonth = this.withdrawalRatePerYear.div(12);

View File

@ -35,7 +35,7 @@
}" }"
></ngx-skeleton-loader> ></ngx-skeleton-loader>
</div> </div>
<div *ngIf="!isLoading"> <div *ngIf="!isLoading" i18n>
If you retire today, you would be able to withdraw If you retire today, you would be able to withdraw
<span class="font-weight-bold" <span class="font-weight-bold"
><gf-value ><gf-value

View File

@ -25,13 +25,11 @@ import {
Filter, Filter,
InfoItem, InfoItem,
OAuthResponse, OAuthResponse,
PortfolioChart,
PortfolioDetails, PortfolioDetails,
PortfolioInvestments, PortfolioInvestments,
PortfolioPerformanceResponse, PortfolioPerformanceResponse,
PortfolioPublicDetails, PortfolioPublicDetails,
PortfolioReport, PortfolioReport,
PortfolioSummary,
UniqueAsset, UniqueAsset,
User User
} from '@ghostfolio/common/interfaces'; } from '@ghostfolio/common/interfaces';
@ -75,60 +73,19 @@ export class DataService {
}: { }: {
filters?: Filter[]; filters?: Filter[];
}): Observable<Activities> { }): Observable<Activities> {
let params = new HttpParams(); return this.http
.get<any>('/api/v1/order', {
if (filters?.length > 0) { params: this.buildFiltersAsQueryParams({ filters })
const {
ACCOUNT: filtersByAccount,
ASSET_CLASS: filtersByAssetClass,
TAG: filtersByTag
} = groupBy(filters, (filter) => {
return filter.type;
});
if (filtersByAccount) {
params = params.append(
'accounts',
filtersByAccount
.map(({ id }) => {
return id;
})
.join(',')
);
}
if (filtersByAssetClass) {
params = params.append(
'assetClasses',
filtersByAssetClass
.map(({ id }) => {
return id;
})
.join(',')
);
}
if (filtersByTag) {
params = params.append(
'tags',
filtersByTag
.map(({ id }) => {
return id;
})
.join(',')
);
}
}
return this.http.get<any>('/api/v1/order', { params }).pipe(
map(({ activities }) => {
for (const activity of activities) {
activity.createdAt = parseISO(activity.createdAt);
activity.date = parseISO(activity.date);
}
return { activities };
}) })
); .pipe(
map(({ activities }) => {
for (const activity of activities) {
activity.createdAt = parseISO(activity.createdAt);
activity.date = parseISO(activity.date);
}
return { activities };
})
);
} }
public fetchAdminData() { public fetchAdminData() {
@ -136,30 +93,8 @@ export class DataService {
} }
public fetchAdminMarketData({ filters }: { filters?: Filter[] }) { public fetchAdminMarketData({ filters }: { filters?: Filter[] }) {
let params = new HttpParams();
if (filters?.length > 0) {
const { ASSET_SUB_CLASS: filtersByAssetSubClass } = groupBy(
filters,
(filter) => {
return filter.type;
}
);
if (filtersByAssetSubClass) {
params = params.append(
'assetSubClasses',
filtersByAssetSubClass
.map(({ id }) => {
return id;
})
.join(',')
);
}
}
return this.http.get<AdminMarketData>('/api/v1/admin/market-data', { return this.http.get<AdminMarketData>('/api/v1/admin/market-data', {
params params: this.buildFiltersAsQueryParams({ filters })
}); });
} }
@ -202,12 +137,6 @@ export class DataService {
return this.http.get<BenchmarkResponse>('/api/v1/benchmark'); return this.http.get<BenchmarkResponse>('/api/v1/benchmark');
} }
public fetchChart({ range, version }: { range: DateRange; version: number }) {
return this.http.get<PortfolioChart>(`/api/v${version}/portfolio/chart`, {
params: { range }
});
}
public fetchExport(activityIds?: string[]) { public fetchExport(activityIds?: string[]) {
let params = new HttpParams(); let params = new HttpParams();
@ -234,34 +163,19 @@ export class DataService {
return info; return info;
} }
public fetchInvestments(): Observable<PortfolioInvestments> { public fetchInvestments({
return this.http.get<any>('/api/v1/portfolio/investments').pipe( groupBy,
map((response) => { range
if (response.firstOrderDate) { }: {
response.firstOrderDate = parseISO(response.firstOrderDate); groupBy?: 'month';
} range: DateRange;
}) {
return response; return this.http.get<PortfolioInvestments>(
}) '/api/v1/portfolio/investments',
{ params: { groupBy, range } }
); );
} }
public fetchInvestmentsByMonth(): Observable<PortfolioInvestments> {
return this.http
.get<any>('/api/v1/portfolio/investments', {
params: { groupBy: 'month' }
})
.pipe(
map((response) => {
if (response.firstOrderDate) {
response.firstOrderDate = parseISO(response.firstOrderDate);
}
return response;
})
);
}
public fetchSymbolItem({ public fetchSymbolItem({
dataSource, dataSource,
includeHistoricalData, includeHistoricalData,
@ -302,64 +216,43 @@ export class DataService {
); );
} }
public fetchPortfolioDetails({ filters }: { filters?: Filter[] }) { public fetchPortfolioDetails({
let params = new HttpParams(); filters
}: {
if (filters?.length > 0) { filters?: Filter[];
const { }): Observable<PortfolioDetails> {
ACCOUNT: filtersByAccount, return this.http
ASSET_CLASS: filtersByAssetClass, .get<any>('/api/v1/portfolio/details', {
TAG: filtersByTag params: this.buildFiltersAsQueryParams({ filters })
} = groupBy(filters, (filter) => { })
return filter.type; .pipe(
}); map((response) => {
if (response.summary?.firstOrderDate) {
if (filtersByAccount) { response.summary.firstOrderDate = parseISO(
params = params.append( response.summary.firstOrderDate
'accounts', );
filtersByAccount }
.map(({ id }) => { return response;
return id; })
}) );
.join(',')
);
}
if (filtersByAssetClass) {
params = params.append(
'assetClasses',
filtersByAssetClass
.map(({ id }) => {
return id;
})
.join(',')
);
}
if (filtersByTag) {
params = params.append(
'tags',
filtersByTag
.map(({ id }) => {
return id;
})
.join(',')
);
}
}
return this.http.get<PortfolioDetails>('/api/v1/portfolio/details', {
params
});
} }
public fetchPortfolioPerformance(params: { [param: string]: any }) { public fetchPortfolioPerformance({
return this.http.get<PortfolioPerformanceResponse>( range
'/api/v1/portfolio/performance', }: {
{ range: DateRange;
params }): Observable<PortfolioPerformanceResponse> {
} return this.http
); .get<any>(`/api/v2/portfolio/performance`, { params: { range } })
.pipe(
map((response) => {
if (response.firstOrderDate) {
response.firstOrderDate = parseISO(response.firstOrderDate);
}
return response;
})
);
} }
public fetchPortfolioPublic(aId: string) { public fetchPortfolioPublic(aId: string) {
@ -372,18 +265,6 @@ export class DataService {
return this.http.get<PortfolioReport>('/api/v1/portfolio/report'); return this.http.get<PortfolioReport>('/api/v1/portfolio/report');
} }
public fetchPortfolioSummary(): Observable<PortfolioSummary> {
return this.http.get<any>('/api/v1/portfolio/summary').pipe(
map((summary) => {
if (summary.firstOrderDate) {
summary.firstOrderDate = parseISO(summary.firstOrderDate);
}
return summary;
})
);
}
public fetchPositionDetail({ public fetchPositionDetail({
dataSource, dataSource,
symbol symbol
@ -452,4 +333,53 @@ export class DataService {
couponCode couponCode
}); });
} }
private buildFiltersAsQueryParams({ filters }: { filters?: Filter[] }) {
let params = new HttpParams();
if (filters?.length > 0) {
const {
ACCOUNT: filtersByAccount,
ASSET_CLASS: filtersByAssetClass,
TAG: filtersByTag
} = groupBy(filters, (filter) => {
return filter.type;
});
if (filtersByAccount) {
params = params.append(
'accounts',
filtersByAccount
.map(({ id }) => {
return id;
})
.join(',')
);
}
if (filtersByAssetClass) {
params = params.append(
'assetClasses',
filtersByAssetClass
.map(({ id }) => {
return id;
})
.join(',')
);
}
if (filtersByTag) {
params = params.append(
'tags',
filtersByTag
.map(({ id }) => {
return id;
})
.join(',')
);
}
}
return params;
}
} }

Binary file not shown.

After

Width:  |  Height:  |  Size: 42 KiB

View File

Before

Width:  |  Height:  |  Size: 26 KiB

After

Width:  |  Height:  |  Size: 26 KiB

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 16 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 17 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 56 KiB

View File

@ -0,0 +1,35 @@
<svg version="1.2" baseProfile="tiny-ps" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 508 101" width="508" height="101">
<title>horizontal-svg</title>
<style>
tspan { white-space:pre }
.shp0 { fill: #3498db }
.shp1 { fill: transparent }
.shp2 { fill: #8a9595 }
</style>
<g id="Logo">
<g id="Shield">
<g id="Layer">
<path id="Layer" class="shp0" d="M284.81 0C279.86 0.24 254.33 8.51 242.56 14.89C240.87 15.81 239.01 18.96 239 20.34C239 33 242.05 51.67 250.66 68.26L267.5 51.42C264.25 43.19 266.19 33.81 272.44 27.56C276.25 23.94 283.41 21.26 289.64 21.15C292.47 21.1 295.11 21.58 297.15 22.74C298.34 23.43 297.78 24.32 297.42 24.68L284.52 37.05C282.89 38.8 284.3 43.29 285.36 46.15C288.21 47.2 292.7 48.61 294.45 46.98L306.83 34.08C307.18 33.73 308.07 33.17 308.76 34.36C312.47 40.88 309.21 53.53 303.95 59.06C297.69 65.32 288.31 67.26 280.09 64L260.76 83.33C267.04 90.61 274.95 96.69 284.81 100.54C320.46 86.63 330.62 43.46 330.62 20.34C330.61 18.96 328.76 15.81 327.06 14.89C315.29 8.51 289.76 0.24 284.81 0L284.81 0Z" />
<g id="Layer">
<path id="Layer" class="shp1" d="M250.69 68.23C250.93 68.68 251.17 69.13 251.41 69.58C252.43 71.46 253.52 73.31 254.69 75.12C255.86 76.93 257.1 78.69 258.43 80.4C259.19 81.38 259.98 82.35 260.8 83.3L280.12 63.97C288.35 67.23 297.73 65.29 303.98 59.03C309.25 53.5 312.51 40.85 308.8 34.33C308.11 33.14 307.22 33.7 306.86 34.05L294.49 46.95C292.74 48.58 288.25 47.17 285.39 46.12C284.34 43.26 282.93 38.77 284.56 37.02L297.46 24.65C297.81 24.29 298.37 23.4 297.18 22.71C295.14 21.55 292.51 21.07 289.68 21.12C283.45 21.23 276.28 23.91 272.48 27.53C266.22 33.79 264.28 43.16 267.54 51.39L250.69 68.23Z" />
</g>
</g>
</g>
<g id="Tools">
<path id="Layer" class="shp2" d="M343.3 31.55L343.3 24.61L387.83 24.61L387.83 31.55L369.48 31.55L369.48 76.83L361.65 76.83L361.65 31.55L343.3 31.55Z" />
<path id="Layer" fill-rule="evenodd" class="shp2" d="M381.33 50.12C382.38 47.68 383.79 45.59 385.58 43.85C387.37 42.06 389.46 40.67 391.85 39.67C394.29 38.63 396.87 38.11 399.61 38.11C402.34 38.11 404.9 38.63 407.29 39.67C409.73 40.67 411.82 42.06 413.56 43.85C415.35 45.59 416.74 47.68 417.73 50.12C418.78 52.5 419.3 55.06 419.3 57.8C419.3 60.58 418.78 63.2 417.73 65.63C416.74 68.02 415.35 70.11 413.56 71.9C411.82 73.64 409.73 75.03 407.29 76.08C404.9 77.07 402.34 77.57 399.61 77.57C396.87 77.57 394.29 77.07 391.85 76.08C389.46 75.03 387.37 73.64 385.58 71.9C383.79 70.11 382.38 68.02 381.33 65.63C380.34 63.2 379.84 60.58 379.84 57.8C379.84 55.06 380.34 52.5 381.33 50.12ZM408.33 67.2C410.77 64.61 411.99 61.48 411.99 57.8C411.99 54.12 410.77 51.01 408.33 48.48C405.95 45.94 403.04 44.67 399.61 44.67C396.18 44.67 393.24 45.94 390.8 48.48C388.37 51.01 387.15 54.12 387.15 57.8C387.15 61.48 388.34 64.61 390.73 67.2C393.17 69.73 396.13 71 399.61 71C403.04 71 405.95 69.73 408.33 67.2Z" />
<path id="Layer" fill-rule="evenodd" class="shp2" d="M423.45 50.12C424.5 47.68 425.91 45.59 427.7 43.85C429.49 42.06 431.58 40.67 433.97 39.67C436.41 38.63 438.99 38.11 441.73 38.11C444.46 38.11 447.02 38.63 449.41 39.67C451.85 40.67 453.94 42.06 455.68 43.85C457.47 45.59 458.86 47.68 459.85 50.12C460.9 52.5 461.42 55.06 461.42 57.8C461.42 60.58 460.9 63.2 459.85 65.63C458.86 68.02 457.47 70.11 455.68 71.9C453.94 73.64 451.85 75.03 449.41 76.08C447.02 77.07 444.46 77.57 441.73 77.57C438.99 77.57 436.41 77.07 433.97 76.08C431.58 75.03 429.49 73.64 427.7 71.9C425.91 70.11 424.5 68.02 423.45 65.63C422.46 63.2 421.96 60.58 421.96 57.8C421.96 55.06 422.46 52.5 423.45 50.12ZM450.45 67.2C452.89 64.61 454.11 61.48 454.11 57.8C454.11 54.12 452.89 51.01 450.45 48.48C448.07 45.94 445.16 44.67 441.73 44.67C438.3 44.67 435.36 45.94 432.92 48.48C430.49 51.01 429.27 54.12 429.27 57.8C429.27 61.48 430.46 64.61 432.85 67.2C435.29 69.73 438.25 71 441.73 71C445.16 71 448.07 69.73 450.45 67.2Z" />
<path id="Layer" class="shp2" d="M473.1 22.97L473.1 76.83L465.64 76.83L465.64 24.61L473.1 22.97Z" />
<path id="Layer" class="shp2" d="M475.48 71.9L479.51 66.98C481.5 68.52 483.51 69.71 485.55 70.56C487.64 71.35 489.75 71.75 491.89 71.75C494.58 71.75 496.76 71.23 498.45 70.18C500.15 69.14 500.99 67.77 500.99 66.08C500.99 64.74 500.49 63.67 499.5 62.87C498.5 62.08 496.96 61.53 494.87 61.23L488.01 60.26C484.28 59.72 481.45 58.57 479.51 56.83C477.62 55.04 476.67 52.68 476.67 49.75C476.67 46.31 478.04 43.55 480.78 41.47C483.51 39.33 487.09 38.26 491.52 38.26C494.3 38.26 496.94 38.66 499.42 39.45C501.96 40.25 504.42 41.47 506.81 43.11L503 48.03C500.92 46.64 498.88 45.64 496.89 45.05C494.95 44.4 492.98 44.08 490.99 44.08C488.71 44.08 486.87 44.55 485.48 45.49C484.08 46.44 483.39 47.68 483.39 49.22C483.39 50.62 483.86 51.69 484.8 52.43C485.8 53.13 487.41 53.65 489.65 54L496.51 54.97C500.24 55.51 503.08 56.68 505.02 58.47C507.01 60.26 508 62.63 508 65.56C508 67.25 507.58 68.84 506.73 70.33C505.94 71.78 504.82 73.02 503.38 74.06C501.99 75.11 500.32 75.95 498.38 76.6C496.44 77.2 494.35 77.49 492.11 77.49C488.83 77.49 485.75 77.02 482.86 76.08C480.03 75.13 477.57 73.74 475.48 71.9L475.48 71.9Z" />
</g>
<g id="Privacy">
<path id="Layer" fill-rule="evenodd" class="shp2" d="M1.46 76.82L1.46 24.6L25.63 24.6C30.7 24.6 34.8 26.07 37.94 29.01C41.12 31.89 42.71 35.64 42.71 40.27C42.71 44.85 41.12 48.57 37.94 51.46C34.75 54.34 30.65 55.79 25.63 55.79L9.29 55.79L9.29 76.82L1.46 76.82ZM24.81 31.47L9.29 31.47L9.29 49.15L24.81 49.15C27.84 49.15 30.25 48.35 32.04 46.76C33.88 45.12 34.8 42.95 34.8 40.27C34.8 37.58 33.88 35.45 32.04 33.85C30.25 32.26 27.84 31.47 24.81 31.47L24.81 31.47Z" />
<path id="Layer" class="shp2" d="M46.91 76.82L46.91 38.85L54.37 38.85L54.37 43.7C55.51 41.86 56.98 40.47 58.77 39.52C60.56 38.53 62.57 38.03 64.81 38.03C65.61 38.03 66.3 38.08 66.9 38.18C67.5 38.28 68.07 38.43 68.62 38.63L68.62 45.34C67.92 45.09 67.2 44.89 66.45 44.74C65.71 44.6 64.96 44.52 64.22 44.52C62.03 44.52 60.06 45.12 58.32 46.31C56.63 47.46 55.31 49.17 54.37 51.46L54.37 76.82L46.91 76.82Z" />
<path id="Layer" class="shp2" d="M75.41 32.74C74.17 32.74 73.1 32.29 72.2 31.4C71.31 30.45 70.86 29.36 70.86 28.12C70.86 26.87 71.31 25.8 72.2 24.91C73.1 23.96 74.17 23.49 75.41 23.49C76.65 23.49 77.72 23.96 78.62 24.91C79.56 25.8 80.03 26.87 80.03 28.12C80.03 29.36 79.56 30.45 78.62 31.4C77.72 32.29 76.65 32.74 75.41 32.74L75.41 32.74ZM79.14 38.86L79.14 76.82L71.68 76.82L71.68 38.86L79.14 38.86Z" />
<path id="Layer" class="shp2" d="M97.33 76.82L80.92 38.85L89.12 38.85L100.98 67.27L112.84 38.85L120.83 38.85L104.41 76.82L97.33 76.82Z" />
<path id="Layer" fill-rule="evenodd" class="shp2" d="M134.56 77.5C130.43 77.5 127.08 76.43 124.49 74.29C121.9 72.15 120.61 69.37 120.61 65.94C120.61 62.36 121.98 59.55 124.71 57.51C127.45 55.42 131.2 54.38 135.98 54.38C137.82 54.38 139.61 54.57 141.35 54.97C143.09 55.32 144.75 55.82 146.35 56.46L146.35 52.44C146.35 49.75 145.55 47.74 143.96 46.39C142.37 45.05 140.08 44.38 137.1 44.38C135.36 44.38 133.54 44.65 131.65 45.2C129.76 45.7 127.65 46.49 125.31 47.59L122.55 41.99C125.39 40.65 128.07 39.68 130.61 39.08C133.14 38.44 135.65 38.11 138.14 38.11C143.06 38.11 146.87 39.31 149.55 41.7C152.29 44.03 153.66 47.36 153.66 51.69L153.66 76.83L146.35 76.83L146.35 73.55C144.66 74.89 142.84 75.88 140.9 76.53C138.96 77.18 136.85 77.5 134.56 77.5L134.56 77.5ZM127.77 65.79C127.77 67.63 128.54 69.12 130.08 70.26C131.68 71.41 133.74 71.98 136.28 71.98C138.26 71.98 140.1 71.68 141.8 71.09C143.49 70.49 145 69.59 146.35 68.4L146.35 61.84C144.9 61.04 143.39 60.47 141.8 60.12C140.2 59.72 138.46 59.52 136.57 59.52C133.89 59.52 131.75 60.09 130.16 61.24C128.57 62.38 127.77 63.9 127.77 65.79L127.77 65.79Z" />
<path id="Layer" class="shp2" d="M177.4 70.93C179.29 70.93 181.08 70.56 182.77 69.81C184.46 69.02 186.08 67.85 187.62 66.31L192.09 71.15C190.11 73.14 187.82 74.71 185.23 75.85C182.65 76.95 179.94 77.49 177.1 77.49C174.37 77.49 171.81 77 169.42 76C167.03 74.96 164.97 73.57 163.23 71.83C161.49 70.08 160.09 68.02 159.05 65.63C158.06 63.2 157.56 60.59 157.56 57.8C157.56 55.07 158.06 52.51 159.05 50.12C160.09 47.68 161.49 45.59 163.23 43.85C164.97 42.06 167.03 40.67 169.42 39.68C171.81 38.63 174.37 38.11 177.1 38.11C179.94 38.11 182.7 38.68 185.38 39.82C188.07 40.92 190.4 42.46 192.39 44.45L187.69 49.52C186.25 47.98 184.64 46.81 182.85 46.02C181.06 45.17 179.19 44.75 177.25 44.75C173.82 44.75 170.91 46.02 168.52 48.55C166.14 51.09 164.94 54.17 164.94 57.8C164.94 61.53 166.14 64.66 168.52 67.2C170.96 69.69 173.92 70.93 177.4 70.93L177.4 70.93Z" />
<path id="Layer" class="shp2" d="M204.75 80.4L206.24 76.97L191.1 38.85L199.31 38.85L210.42 67.87L222.65 38.85L230.71 38.85L212.36 81.37C210.62 85.4 208.63 88.28 206.39 90.02C204.15 91.81 201.29 92.71 197.81 92.71C197.02 92.71 196.27 92.66 195.57 92.56C194.93 92.51 194.36 92.41 193.86 92.26L193.86 85.7C194.36 85.8 194.85 85.87 195.35 85.92C195.9 85.97 196.52 85.99 197.22 85.99C198.91 85.99 200.37 85.52 201.62 84.58C202.91 83.68 203.95 82.29 204.75 80.4L204.75 80.4Z" />
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 8.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 21 KiB

View File

@ -0,0 +1,9 @@
<?xml version="1.0" encoding="UTF-8"?>
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 222.4 139.8">
<path fill="#ffffff" d="M146.70000000000002 130.2H135l-3 9h-6.5l13.4-38.5h8l13.4 38.5h-7.1l-10.6-31.6-5.8 16.9h8.2l1.7 5.7zM29.7 100.8v25.4c0 8.9-5.8 13.6-14.9 13.6-9 0-14.8-4.7-14.8-13.6v-25.4h6.5v25.4c0 5.2 3.2 7.9 8.2 7.9 5.2 0 8.4-2.7 8.4-7.9v-25.4h6.6zM50.9 112.7v26.5h-6.5v-38.5h6.1l17 26.5v-26.5H74v38.5h-6.1l-17-26.5zM171.3 100.8h6.5v38.5h-6.5v-38.5zM222.4 125.4c0 9-5.9 13.8-15.2 13.8h-14.5v-38.5h14.6c9.2 0 15.1 4.8 15.1 13.8v10.9zm-6.6-10.8c0-5.3-3.3-8.1-8.5-8.1h-8.1v27.1h8c5.3 0 8.6-2.8 8.6-8.1v-10.9zM108.3 124.7c4.3-1.6 6.9-5.3 6.9-11.5 0-8.7-5.1-12.4-12.8-12.4H88.8v38.5h6.5v-32.8h6.9c3.8 0 6.2 1.8 6.2 6.7s-2.4 6.8-6.2 6.8h-3.4l9.2 19.4h7.5l-7.2-14.7z" class="st0"></path>
<linearGradient id="SVGID_1_" x1="68.07" x2="154.073" y1="81.489" y2="-4.514" gradientUnits="userSpaceOnUse">
<stop offset="0" stop-color="#e32929"></stop>
<stop offset="1" stop-color="#ff8d30"></stop>
</linearGradient>
<path fill="url(#SVGID_1_)" d="M107.8 19.2h6.5v38.5h-6.5V19.2zM50.9 57.7h-6.5V19.2h6.5v38.5zm25.2 4.6h6.5V77h-6.5V62.3zM60.2 45.8h6.5v23.8h-6.5V45.8zm31.7 0h6.5v23.8h-6.5V45.8zm79.4-26.6h6.5v38.5h-6.5V19.2zm-25.2-4.5h-6.5V0h6.5v14.7zM162 31.1h-6.5V7.4h6.5v23.7zm-31.8 0h-6.5V7.4h6.5v23.7z"></path>
</svg>

After

Width:  |  Height:  |  Size: 1.3 KiB

View File

@ -6,70 +6,74 @@
http://www.sitemaps.org/schemas/sitemap/0.9/sitemap.xsd"> http://www.sitemaps.org/schemas/sitemap/0.9/sitemap.xsd">
<url> <url>
<loc>https://ghostfol.io</loc> <loc>https://ghostfol.io</loc>
<lastmod>2022-09-01T00:00:00+00:00</lastmod> <lastmod>2022-10-01T00:00:00+00:00</lastmod>
</url> </url>
<url> <url>
<loc>https://ghostfol.io/de/blog/2021/07/hallo-ghostfolio</loc> <loc>https://ghostfol.io/de/blog/2021/07/hallo-ghostfolio</loc>
<lastmod>2022-09-01T00:00:00+00:00</lastmod> <lastmod>2022-10-01T00:00:00+00:00</lastmod>
</url> </url>
<url> <url>
<loc>https://ghostfol.io/en/about</loc> <loc>https://ghostfol.io/en/about</loc>
<lastmod>2022-09-01T00:00:00+00:00</lastmod> <lastmod>2022-10-01T00:00:00+00:00</lastmod>
</url> </url>
<url> <url>
<loc>https://ghostfol.io/en/about/changelog</loc> <loc>https://ghostfol.io/en/about/changelog</loc>
<lastmod>2022-09-01T00:00:00+00:00</lastmod> <lastmod>2022-10-01T00:00:00+00:00</lastmod>
</url> </url>
<url> <url>
<loc>https://ghostfol.io/en/blog</loc> <loc>https://ghostfol.io/en/blog</loc>
<lastmod>2022-09-01T00:00:00+00:00</lastmod> <lastmod>2022-10-01T00:00:00+00:00</lastmod>
</url> </url>
<url> <url>
<loc>https://ghostfol.io/en/blog/2021/07/hello-ghostfolio</loc> <loc>https://ghostfol.io/en/blog/2021/07/hello-ghostfolio</loc>
<lastmod>2022-09-01T00:00:00+00:00</lastmod> <lastmod>2022-10-01T00:00:00+00:00</lastmod>
</url> </url>
<url> <url>
<loc>https://ghostfol.io/en/blog/2022/01/ghostfolio-first-months-in-open-source</loc> <loc>https://ghostfol.io/en/blog/2022/01/ghostfolio-first-months-in-open-source</loc>
<lastmod>2022-09-01T00:00:00+00:00</lastmod> <lastmod>2022-10-01T00:00:00+00:00</lastmod>
</url> </url>
<url> <url>
<loc>https://ghostfol.io/en/blog/2022/07/ghostfolio-meets-internet-identity</loc> <loc>https://ghostfol.io/en/blog/2022/07/ghostfolio-meets-internet-identity</loc>
<lastmod>2022-09-01T00:00:00+00:00</lastmod> <lastmod>2022-10-01T00:00:00+00:00</lastmod>
</url> </url>
<url> <url>
<loc>https://ghostfol.io/en/blog/2022/07/how-do-i-get-my-finances-in-order</loc> <loc>https://ghostfol.io/en/blog/2022/07/how-do-i-get-my-finances-in-order</loc>
<lastmod>2022-09-01T00:00:00+00:00</lastmod> <lastmod>2022-10-01T00:00:00+00:00</lastmod>
</url> </url>
<url> <url>
<loc>https://ghostfol.io/en/blog/2022/08/500-stars-on-github</loc> <loc>https://ghostfol.io/en/blog/2022/08/500-stars-on-github</loc>
<lastmod>2022-09-01T00:00:00+00:00</lastmod> <lastmod>2022-10-01T00:00:00+00:00</lastmod>
</url>
<url>
<loc>https://ghostfol.io/en/blog/2022/10/hacktoberfest-2022</loc>
<lastmod>2022-10-01T00:00:00+00:00</lastmod>
</url> </url>
<url> <url>
<loc>https://ghostfol.io/en/demo</loc> <loc>https://ghostfol.io/en/demo</loc>
<lastmod>2022-09-01T00:00:00+00:00</lastmod> <lastmod>2022-10-01T00:00:00+00:00</lastmod>
</url> </url>
<url> <url>
<loc>https://ghostfol.io/en/faq</loc> <loc>https://ghostfol.io/en/faq</loc>
<lastmod>2022-09-01T00:00:00+00:00</lastmod> <lastmod>2022-10-01T00:00:00+00:00</lastmod>
</url> </url>
<url> <url>
<loc>https://ghostfol.io/en/features</loc> <loc>https://ghostfol.io/en/features</loc>
<lastmod>2022-09-01T00:00:00+00:00</lastmod> <lastmod>2022-10-01T00:00:00+00:00</lastmod>
</url> </url>
<url> <url>
<loc>https://ghostfol.io/en/markets</loc> <loc>https://ghostfol.io/en/markets</loc>
<lastmod>2022-09-01T00:00:00+00:00</lastmod> <lastmod>2022-10-01T00:00:00+00:00</lastmod>
</url> </url>
<url> <url>
<loc>https://ghostfol.io/en/pricing</loc> <loc>https://ghostfol.io/en/pricing</loc>
<lastmod>2022-09-01T00:00:00+00:00</lastmod> <lastmod>2022-10-01T00:00:00+00:00</lastmod>
</url> </url>
<url> <url>
<loc>https://ghostfol.io/en/register</loc> <loc>https://ghostfol.io/en/register</loc>
<lastmod>2022-09-01T00:00:00+00:00</lastmod> <lastmod>2022-10-01T00:00:00+00:00</lastmod>
</url> </url>
<url> <url>
<loc>https://ghostfol.io/en/resources</loc> <loc>https://ghostfol.io/en/resources</loc>
<lastmod>2022-09-01T00:00:00+00:00</lastmod> <lastmod>2022-10-01T00:00:00+00:00</lastmod>
</url> </url>
</urlset> </urlset>

View File

@ -15,7 +15,7 @@
</trans-unit> </trans-unit>
<trans-unit id="ccb2a809018b32a96c813ae69126ce05976109ce" datatype="html"> <trans-unit id="ccb2a809018b32a96c813ae69126ce05976109ce" datatype="html">
<source>The risk of loss in trading can be substantial. It is not advisable to invest money you may need in the short term.</source> <source>The risk of loss in trading can be substantial. It is not advisable to invest money you may need in the short term.</source>
<target state="translated">Das Ausfallrisiko beim Börsenhandel kann erheblich sein. Es ist nicht ratsam, Geld zu investieren, welches Sie kurzfristig benötigen.</target> <target state="translated">Das Ausfallrisiko beim Börsenhandel kann erheblich sein. Es ist nicht ratsam, Geld zu investieren, welches du kurzfristig benötigst.</target>
<context-group purpose="location"> <context-group purpose="location">
<context context-type="sourcefile">apps/client/src/app/app.component.html</context> <context context-type="sourcefile">apps/client/src/app/app.component.html</context>
<context context-type="linenumber">55,56</context> <context context-type="linenumber">55,56</context>
@ -38,7 +38,7 @@
</context-group> </context-group>
<context-group purpose="location"> <context-group purpose="location">
<context context-type="sourcefile">apps/client/src/app/components/admin-jobs/admin-jobs.html</context> <context context-type="sourcefile">apps/client/src/app/components/admin-jobs/admin-jobs.html</context>
<context context-type="linenumber">28</context> <context context-type="linenumber">31</context>
</context-group> </context-group>
<context-group purpose="location"> <context-group purpose="location">
<context context-type="sourcefile">apps/client/src/app/pages/account/create-or-update-access-dialog/create-or-update-access-dialog.html</context> <context context-type="sourcefile">apps/client/src/app/pages/account/create-or-update-access-dialog/create-or-update-access-dialog.html</context>
@ -86,7 +86,7 @@
<target state="translated">Aktivitäten</target> <target state="translated">Aktivitäten</target>
<context-group purpose="location"> <context-group purpose="location">
<context context-type="sourcefile">apps/client/src/app/components/account-detail-dialog/account-detail-dialog.html</context> <context context-type="sourcefile">apps/client/src/app/components/account-detail-dialog/account-detail-dialog.html</context>
<context context-type="linenumber">33</context> <context context-type="linenumber">35</context>
</context-group> </context-group>
<context-group purpose="location"> <context-group purpose="location">
<context context-type="sourcefile">apps/client/src/app/components/accounts-table/accounts-table.component.html</context> <context context-type="sourcefile">apps/client/src/app/components/accounts-table/accounts-table.component.html</context>
@ -98,7 +98,7 @@
</context-group> </context-group>
<context-group purpose="location"> <context-group purpose="location">
<context context-type="sourcefile">apps/client/src/app/components/position/position-detail-dialog/position-detail-dialog.html</context> <context context-type="sourcefile">apps/client/src/app/components/position/position-detail-dialog/position-detail-dialog.html</context>
<context context-type="linenumber">214</context> <context context-type="linenumber">215</context>
</context-group> </context-group>
<context-group purpose="location"> <context-group purpose="location">
<context context-type="sourcefile">apps/client/src/app/pages/portfolio/portfolio-page.html</context> <context context-type="sourcefile">apps/client/src/app/pages/portfolio/portfolio-page.html</context>
@ -210,7 +210,7 @@
<target state="translated">Jobs löschen</target> <target state="translated">Jobs löschen</target>
<context-group purpose="location"> <context-group purpose="location">
<context context-type="sourcefile">apps/client/src/app/components/admin-jobs/admin-jobs.html</context> <context context-type="sourcefile">apps/client/src/app/components/admin-jobs/admin-jobs.html</context>
<context context-type="linenumber">21</context> <context context-type="linenumber">24</context>
</context-group> </context-group>
</trans-unit> </trans-unit>
<trans-unit id="7cd2168068d1fd50772c493d493f83e4e412ebc8" datatype="html"> <trans-unit id="7cd2168068d1fd50772c493d493f83e4e412ebc8" datatype="html">
@ -218,7 +218,7 @@
<target state="translated">Symbol</target> <target state="translated">Symbol</target>
<context-group purpose="location"> <context-group purpose="location">
<context context-type="sourcefile">apps/client/src/app/components/admin-jobs/admin-jobs.html</context> <context context-type="sourcefile">apps/client/src/app/components/admin-jobs/admin-jobs.html</context>
<context context-type="linenumber">29</context> <context context-type="linenumber">32</context>
</context-group> </context-group>
<context-group purpose="location"> <context-group purpose="location">
<context context-type="sourcefile">apps/client/src/app/components/admin-market-data/admin-market-data.html</context> <context context-type="sourcefile">apps/client/src/app/components/admin-market-data/admin-market-data.html</context>
@ -238,7 +238,7 @@
<target state="translated">Datenquelle</target> <target state="translated">Datenquelle</target>
<context-group purpose="location"> <context-group purpose="location">
<context context-type="sourcefile">apps/client/src/app/components/admin-jobs/admin-jobs.html</context> <context context-type="sourcefile">apps/client/src/app/components/admin-jobs/admin-jobs.html</context>
<context context-type="linenumber">30</context> <context context-type="linenumber">33</context>
</context-group> </context-group>
<context-group purpose="location"> <context-group purpose="location">
<context context-type="sourcefile">apps/client/src/app/components/admin-market-data/admin-market-data.html</context> <context context-type="sourcefile">apps/client/src/app/components/admin-market-data/admin-market-data.html</context>
@ -254,7 +254,7 @@
<target state="translated">Versuche</target> <target state="translated">Versuche</target>
<context-group purpose="location"> <context-group purpose="location">
<context context-type="sourcefile">apps/client/src/app/components/admin-jobs/admin-jobs.html</context> <context context-type="sourcefile">apps/client/src/app/components/admin-jobs/admin-jobs.html</context>
<context context-type="linenumber">31</context> <context context-type="linenumber">34</context>
</context-group> </context-group>
</trans-unit> </trans-unit>
<trans-unit id="1b051734b0ee9021991c91b3ed4e81c244322462" datatype="html"> <trans-unit id="1b051734b0ee9021991c91b3ed4e81c244322462" datatype="html">
@ -262,7 +262,7 @@
<target state="translated">Erstellt</target> <target state="translated">Erstellt</target>
<context-group purpose="location"> <context-group purpose="location">
<context context-type="sourcefile">apps/client/src/app/components/admin-jobs/admin-jobs.html</context> <context context-type="sourcefile">apps/client/src/app/components/admin-jobs/admin-jobs.html</context>
<context context-type="linenumber">32</context> <context context-type="linenumber">35</context>
</context-group> </context-group>
</trans-unit> </trans-unit>
<trans-unit id="edcc19a49c950289ffe5d38be4843cdf194e5622" datatype="html"> <trans-unit id="edcc19a49c950289ffe5d38be4843cdf194e5622" datatype="html">
@ -270,7 +270,7 @@
<target state="translated">Abgeschlossen</target> <target state="translated">Abgeschlossen</target>
<context-group purpose="location"> <context-group purpose="location">
<context context-type="sourcefile">apps/client/src/app/components/admin-jobs/admin-jobs.html</context> <context context-type="sourcefile">apps/client/src/app/components/admin-jobs/admin-jobs.html</context>
<context context-type="linenumber">33</context> <context context-type="linenumber">36</context>
</context-group> </context-group>
</trans-unit> </trans-unit>
<trans-unit id="81b97b8ea996ad1e4f9fca8415021850214884b1" datatype="html"> <trans-unit id="81b97b8ea996ad1e4f9fca8415021850214884b1" datatype="html">
@ -278,7 +278,7 @@
<target state="translated">Status</target> <target state="translated">Status</target>
<context-group purpose="location"> <context-group purpose="location">
<context context-type="sourcefile">apps/client/src/app/components/admin-jobs/admin-jobs.html</context> <context context-type="sourcefile">apps/client/src/app/components/admin-jobs/admin-jobs.html</context>
<context context-type="linenumber">34</context> <context context-type="linenumber">37</context>
</context-group> </context-group>
</trans-unit> </trans-unit>
<trans-unit id="779aa6949e9d62c58ad44357d11a3157ef6780f5" datatype="html"> <trans-unit id="779aa6949e9d62c58ad44357d11a3157ef6780f5" datatype="html">
@ -286,7 +286,7 @@
<target state="translated">Anlageprofil</target> <target state="translated">Anlageprofil</target>
<context-group purpose="location"> <context-group purpose="location">
<context context-type="sourcefile">apps/client/src/app/components/admin-jobs/admin-jobs.html</context> <context context-type="sourcefile">apps/client/src/app/components/admin-jobs/admin-jobs.html</context>
<context context-type="linenumber">49</context> <context context-type="linenumber">52</context>
</context-group> </context-group>
</trans-unit> </trans-unit>
<trans-unit id="8ea23a2cc3e9549fa71e7724870038a17216b210" datatype="html"> <trans-unit id="8ea23a2cc3e9549fa71e7724870038a17216b210" datatype="html">
@ -294,7 +294,7 @@
<target state="translated">Historische Marktdaten</target> <target state="translated">Historische Marktdaten</target>
<context-group purpose="location"> <context-group purpose="location">
<context context-type="sourcefile">apps/client/src/app/components/admin-jobs/admin-jobs.html</context> <context context-type="sourcefile">apps/client/src/app/components/admin-jobs/admin-jobs.html</context>
<context context-type="linenumber">54</context> <context context-type="linenumber">57</context>
</context-group> </context-group>
</trans-unit> </trans-unit>
<trans-unit id="30afc50625f30e4ac97acc23fd7e77031a341a8f" datatype="html"> <trans-unit id="30afc50625f30e4ac97acc23fd7e77031a341a8f" datatype="html">
@ -302,7 +302,7 @@
<target state="translated">Daten anzeigen</target> <target state="translated">Daten anzeigen</target>
<context-group purpose="location"> <context-group purpose="location">
<context context-type="sourcefile">apps/client/src/app/components/admin-jobs/admin-jobs.html</context> <context context-type="sourcefile">apps/client/src/app/components/admin-jobs/admin-jobs.html</context>
<context context-type="linenumber">109</context> <context context-type="linenumber">112</context>
</context-group> </context-group>
</trans-unit> </trans-unit>
<trans-unit id="94e6ec0f0d021b88dfa4ef191447315fc1898f00" datatype="html"> <trans-unit id="94e6ec0f0d021b88dfa4ef191447315fc1898f00" datatype="html">
@ -310,7 +310,7 @@
<target state="translated">Stacktrace anzeigen</target> <target state="translated">Stacktrace anzeigen</target>
<context-group purpose="location"> <context-group purpose="location">
<context context-type="sourcefile">apps/client/src/app/components/admin-jobs/admin-jobs.html</context> <context context-type="sourcefile">apps/client/src/app/components/admin-jobs/admin-jobs.html</context>
<context context-type="linenumber">116</context> <context context-type="linenumber">119</context>
</context-group> </context-group>
</trans-unit> </trans-unit>
<trans-unit id="de50f7bcb18ed16c00012741202155acb5c61acf" datatype="html"> <trans-unit id="de50f7bcb18ed16c00012741202155acb5c61acf" datatype="html">
@ -318,7 +318,7 @@
<target state="translated">Job löschen</target> <target state="translated">Job löschen</target>
<context-group purpose="location"> <context-group purpose="location">
<context context-type="sourcefile">apps/client/src/app/components/admin-jobs/admin-jobs.html</context> <context context-type="sourcefile">apps/client/src/app/components/admin-jobs/admin-jobs.html</context>
<context context-type="linenumber">119</context> <context context-type="linenumber">122</context>
</context-group> </context-group>
</trans-unit> </trans-unit>
<trans-unit id="bfb6a28329c452254e363723ef9718b5178dfd1d" datatype="html"> <trans-unit id="bfb6a28329c452254e363723ef9718b5178dfd1d" datatype="html">
@ -354,7 +354,7 @@
</context-group> </context-group>
<context-group purpose="location"> <context-group purpose="location">
<context context-type="sourcefile">apps/client/src/app/components/position/position-detail-dialog/position-detail-dialog.html</context> <context context-type="sourcefile">apps/client/src/app/components/position/position-detail-dialog/position-detail-dialog.html</context>
<context context-type="linenumber">75</context> <context context-type="linenumber">76</context>
</context-group> </context-group>
</trans-unit> </trans-unit>
<trans-unit id="d7b35c384aecd25a516200d6921836374613dfe7" datatype="html"> <trans-unit id="d7b35c384aecd25a516200d6921836374613dfe7" datatype="html">
@ -370,7 +370,7 @@
</context-group> </context-group>
<context-group purpose="location"> <context-group purpose="location">
<context context-type="sourcefile">apps/client/src/app/pages/accounts/create-or-update-account-dialog/create-or-update-account-dialog.html</context> <context context-type="sourcefile">apps/client/src/app/pages/accounts/create-or-update-account-dialog/create-or-update-account-dialog.html</context>
<context context-type="linenumber">66</context> <context context-type="linenumber">74</context>
</context-group> </context-group>
<context-group purpose="location"> <context-group purpose="location">
<context context-type="sourcefile">apps/client/src/app/pages/portfolio/transactions/create-or-update-transaction-dialog/create-or-update-transaction-dialog.html</context> <context context-type="sourcefile">apps/client/src/app/pages/portfolio/transactions/create-or-update-transaction-dialog/create-or-update-transaction-dialog.html</context>
@ -394,7 +394,7 @@
</context-group> </context-group>
<context-group purpose="location"> <context-group purpose="location">
<context context-type="sourcefile">apps/client/src/app/pages/accounts/create-or-update-account-dialog/create-or-update-account-dialog.html</context> <context context-type="sourcefile">apps/client/src/app/pages/accounts/create-or-update-account-dialog/create-or-update-account-dialog.html</context>
<context context-type="linenumber">73</context> <context context-type="linenumber">81</context>
</context-group> </context-group>
<context-group purpose="location"> <context-group purpose="location">
<context context-type="sourcefile">apps/client/src/app/pages/portfolio/transactions/create-or-update-transaction-dialog/create-or-update-transaction-dialog.html</context> <context context-type="sourcefile">apps/client/src/app/pages/portfolio/transactions/create-or-update-transaction-dialog/create-or-update-transaction-dialog.html</context>
@ -418,7 +418,7 @@
</context-group> </context-group>
<context-group purpose="location"> <context-group purpose="location">
<context context-type="sourcefile">apps/client/src/app/components/admin-overview/admin-overview.html</context> <context context-type="sourcefile">apps/client/src/app/components/admin-overview/admin-overview.html</context>
<context context-type="linenumber">11</context> <context context-type="linenumber">17</context>
</context-group> </context-group>
</trans-unit> </trans-unit>
<trans-unit id="5eebcc824c2d34b17abba8c9ef718c5ce118a5c8" datatype="html"> <trans-unit id="5eebcc824c2d34b17abba8c9ef718c5ce118a5c8" datatype="html">
@ -474,7 +474,7 @@
<target state="translated">Bitte gebe deine Systemmeldung ein:</target> <target state="translated">Bitte gebe deine Systemmeldung ein:</target>
<context-group purpose="location"> <context-group purpose="location">
<context context-type="sourcefile">apps/client/src/app/components/admin-overview/admin-overview.component.ts</context> <context context-type="sourcefile">apps/client/src/app/components/admin-overview/admin-overview.component.ts</context>
<context context-type="linenumber">199</context> <context context-type="linenumber">202</context>
</context-group> </context-group>
</trans-unit> </trans-unit>
<trans-unit id="ec03f5c28b327fc7ecfc4b20a0a7cf14a75843ff" datatype="html"> <trans-unit id="ec03f5c28b327fc7ecfc4b20a0a7cf14a75843ff" datatype="html">
@ -490,7 +490,7 @@
<target state="translated">pro Benutzer</target> <target state="translated">pro Benutzer</target>
<context-group purpose="location"> <context-group purpose="location">
<context context-type="sourcefile">apps/client/src/app/components/admin-overview/admin-overview.html</context> <context context-type="sourcefile">apps/client/src/app/components/admin-overview/admin-overview.html</context>
<context context-type="linenumber">15</context> <context context-type="linenumber">26</context>
</context-group> </context-group>
</trans-unit> </trans-unit>
<trans-unit id="5067ede95804e0e0e2b078747848367a7373cc9b" datatype="html"> <trans-unit id="5067ede95804e0e0e2b078747848367a7373cc9b" datatype="html">
@ -498,7 +498,7 @@
<target state="translated">Letzte Daten einholen</target> <target state="translated">Letzte Daten einholen</target>
<context-group purpose="location"> <context-group purpose="location">
<context context-type="sourcefile">apps/client/src/app/components/admin-overview/admin-overview.html</context> <context context-type="sourcefile">apps/client/src/app/components/admin-overview/admin-overview.html</context>
<context context-type="linenumber">33</context> <context context-type="linenumber">44</context>
</context-group> </context-group>
</trans-unit> </trans-unit>
<trans-unit id="27e27f3f7338baefacfd5668bad03ece9c5955df" datatype="html"> <trans-unit id="27e27f3f7338baefacfd5668bad03ece9c5955df" datatype="html">
@ -506,7 +506,7 @@
<target state="translated">Alle Daten einholen</target> <target state="translated">Alle Daten einholen</target>
<context-group purpose="location"> <context-group purpose="location">
<context context-type="sourcefile">apps/client/src/app/components/admin-overview/admin-overview.html</context> <context context-type="sourcefile">apps/client/src/app/components/admin-overview/admin-overview.html</context>
<context context-type="linenumber">46</context> <context context-type="linenumber">57</context>
</context-group> </context-group>
</trans-unit> </trans-unit>
<trans-unit id="cc65b67b46b69cf06ff1f16a909e61612c9d57b8" datatype="html"> <trans-unit id="cc65b67b46b69cf06ff1f16a909e61612c9d57b8" datatype="html">
@ -518,7 +518,7 @@
</context-group> </context-group>
<context-group purpose="location"> <context-group purpose="location">
<context context-type="sourcefile">apps/client/src/app/components/admin-overview/admin-overview.html</context> <context context-type="sourcefile">apps/client/src/app/components/admin-overview/admin-overview.html</context>
<context context-type="linenumber">60</context> <context context-type="linenumber">71</context>
</context-group> </context-group>
</trans-unit> </trans-unit>
<trans-unit id="37ff0697aa81b2777250c483d2ce1ebdaa5ab042" datatype="html"> <trans-unit id="37ff0697aa81b2777250c483d2ce1ebdaa5ab042" datatype="html">
@ -526,7 +526,7 @@
<target state="translated">Wechselkurse</target> <target state="translated">Wechselkurse</target>
<context-group purpose="location"> <context-group purpose="location">
<context context-type="sourcefile">apps/client/src/app/components/admin-overview/admin-overview.html</context> <context context-type="sourcefile">apps/client/src/app/components/admin-overview/admin-overview.html</context>
<context context-type="linenumber">67</context> <context context-type="linenumber">78</context>
</context-group> </context-group>
</trans-unit> </trans-unit>
<trans-unit id="4567a660a7f67e254bbf13219906843e34d9f807" datatype="html"> <trans-unit id="4567a660a7f67e254bbf13219906843e34d9f807" datatype="html">
@ -534,7 +534,7 @@
<target state="translated">Währung hinzufügen</target> <target state="translated">Währung hinzufügen</target>
<context-group purpose="location"> <context-group purpose="location">
<context context-type="sourcefile">apps/client/src/app/components/admin-overview/admin-overview.html</context> <context context-type="sourcefile">apps/client/src/app/components/admin-overview/admin-overview.html</context>
<context context-type="linenumber">106</context> <context context-type="linenumber">117</context>
</context-group> </context-group>
</trans-unit> </trans-unit>
<trans-unit id="860e5f056b59410ec8db65cb53955505c6931752" datatype="html"> <trans-unit id="860e5f056b59410ec8db65cb53955505c6931752" datatype="html">
@ -542,7 +542,7 @@
<target state="translated">Systemmeldung</target> <target state="translated">Systemmeldung</target>
<context-group purpose="location"> <context-group purpose="location">
<context context-type="sourcefile">apps/client/src/app/components/admin-overview/admin-overview.html</context> <context context-type="sourcefile">apps/client/src/app/components/admin-overview/admin-overview.html</context>
<context context-type="linenumber">112</context> <context context-type="linenumber">133</context>
</context-group> </context-group>
</trans-unit> </trans-unit>
<trans-unit id="657028d5fc9c3da8f2d667b6b15cd0df8b9a3729" datatype="html"> <trans-unit id="657028d5fc9c3da8f2d667b6b15cd0df8b9a3729" datatype="html">
@ -550,7 +550,7 @@
<target state="translated">Systemmeldung setzen</target> <target state="translated">Systemmeldung setzen</target>
<context-group purpose="location"> <context-group purpose="location">
<context context-type="sourcefile">apps/client/src/app/components/admin-overview/admin-overview.html</context> <context context-type="sourcefile">apps/client/src/app/components/admin-overview/admin-overview.html</context>
<context context-type="linenumber">134</context> <context context-type="linenumber">155</context>
</context-group> </context-group>
</trans-unit> </trans-unit>
<trans-unit id="7fd64c34428887e4cd56d05534b89c100b8544ad" datatype="html"> <trans-unit id="7fd64c34428887e4cd56d05534b89c100b8544ad" datatype="html">
@ -558,7 +558,7 @@
<target state="translated">Lese-Modus</target> <target state="translated">Lese-Modus</target>
<context-group purpose="location"> <context-group purpose="location">
<context context-type="sourcefile">apps/client/src/app/components/admin-overview/admin-overview.html</context> <context context-type="sourcefile">apps/client/src/app/components/admin-overview/admin-overview.html</context>
<context context-type="linenumber">139</context> <context context-type="linenumber">160</context>
</context-group> </context-group>
</trans-unit> </trans-unit>
<trans-unit id="e698b03c34b459b1b006d7f0473a49b9fcf5dfc1" datatype="html"> <trans-unit id="e698b03c34b459b1b006d7f0473a49b9fcf5dfc1" datatype="html">
@ -566,7 +566,7 @@
<target state="translated">Gutscheincodes</target> <target state="translated">Gutscheincodes</target>
<context-group purpose="location"> <context-group purpose="location">
<context context-type="sourcefile">apps/client/src/app/components/admin-overview/admin-overview.html</context> <context context-type="sourcefile">apps/client/src/app/components/admin-overview/admin-overview.html</context>
<context context-type="linenumber">152</context> <context context-type="linenumber">173</context>
</context-group> </context-group>
</trans-unit> </trans-unit>
<trans-unit id="f6755cff4957d5c3c89bafce5651f1b6fa2b1fd9" datatype="html"> <trans-unit id="f6755cff4957d5c3c89bafce5651f1b6fa2b1fd9" datatype="html">
@ -574,7 +574,7 @@
<target state="translated">Hinzufügen</target> <target state="translated">Hinzufügen</target>
<context-group purpose="location"> <context-group purpose="location">
<context context-type="sourcefile">apps/client/src/app/components/admin-overview/admin-overview.html</context> <context context-type="sourcefile">apps/client/src/app/components/admin-overview/admin-overview.html</context>
<context context-type="linenumber">183</context> <context context-type="linenumber">208</context>
</context-group> </context-group>
</trans-unit> </trans-unit>
<trans-unit id="e799e6b926557f0098f41888cdf8df868eff3d47" datatype="html"> <trans-unit id="e799e6b926557f0098f41888cdf8df868eff3d47" datatype="html">
@ -582,7 +582,7 @@
<target state="translated">Verwaltung</target> <target state="translated">Verwaltung</target>
<context-group purpose="location"> <context-group purpose="location">
<context context-type="sourcefile">apps/client/src/app/components/admin-overview/admin-overview.html</context> <context context-type="sourcefile">apps/client/src/app/components/admin-overview/admin-overview.html</context>
<context context-type="linenumber">190</context> <context context-type="linenumber">215</context>
</context-group> </context-group>
</trans-unit> </trans-unit>
<trans-unit id="c7ac907e52a7ce2ac70b1786eb5f403ce306ce1f" datatype="html"> <trans-unit id="c7ac907e52a7ce2ac70b1786eb5f403ce306ce1f" datatype="html">
@ -590,7 +590,7 @@
<target state="translated">Cache leeren</target> <target state="translated">Cache leeren</target>
<context-group purpose="location"> <context-group purpose="location">
<context context-type="sourcefile">apps/client/src/app/components/admin-overview/admin-overview.html</context> <context context-type="sourcefile">apps/client/src/app/components/admin-overview/admin-overview.html</context>
<context context-type="linenumber">194</context> <context context-type="linenumber">219</context>
</context-group> </context-group>
</trans-unit> </trans-unit>
<trans-unit id="2817099043823177227" datatype="html"> <trans-unit id="2817099043823177227" datatype="html">
@ -946,7 +946,7 @@
</context-group> </context-group>
<context-group purpose="location"> <context-group purpose="location">
<context context-type="sourcefile">apps/client/src/app/components/position/position-detail-dialog/position-detail-dialog.html</context> <context context-type="sourcefile">apps/client/src/app/components/position/position-detail-dialog/position-detail-dialog.html</context>
<context context-type="linenumber">117</context> <context context-type="linenumber">118</context>
</context-group> </context-group>
</trans-unit> </trans-unit>
<trans-unit id="a64cd8d0131a583e3de081c9e6458af7aecdafbc" datatype="html"> <trans-unit id="a64cd8d0131a583e3de081c9e6458af7aecdafbc" datatype="html">
@ -1034,7 +1034,7 @@
<target state="translated">Gesamtvermögen</target> <target state="translated">Gesamtvermögen</target>
<context-group purpose="location"> <context-group purpose="location">
<context context-type="sourcefile">apps/client/src/app/components/portfolio-summary/portfolio-summary.component.html</context> <context context-type="sourcefile">apps/client/src/app/components/portfolio-summary/portfolio-summary.component.html</context>
<context context-type="linenumber">179</context> <context context-type="linenumber">190</context>
</context-group> </context-group>
</trans-unit> </trans-unit>
<trans-unit id="e1b20ce1622d86ae0a75a3555a1a9ae7c2c60e58" datatype="html"> <trans-unit id="e1b20ce1622d86ae0a75a3555a1a9ae7c2c60e58" datatype="html">
@ -1042,7 +1042,7 @@
<target state="translated">Performance pro Jahr</target> <target state="translated">Performance pro Jahr</target>
<context-group purpose="location"> <context-group purpose="location">
<context context-type="sourcefile">apps/client/src/app/components/portfolio-summary/portfolio-summary.component.html</context> <context context-type="sourcefile">apps/client/src/app/components/portfolio-summary/portfolio-summary.component.html</context>
<context context-type="linenumber">190</context> <context context-type="linenumber">201</context>
</context-group> </context-group>
</trans-unit> </trans-unit>
<trans-unit id="d3aa83bd247983dd056a62f56ffb25269bd98d47" datatype="html"> <trans-unit id="d3aa83bd247983dd056a62f56ffb25269bd98d47" datatype="html">
@ -1050,7 +1050,7 @@
<target state="translated">Dividenden</target> <target state="translated">Dividenden</target>
<context-group purpose="location"> <context-group purpose="location">
<context context-type="sourcefile">apps/client/src/app/components/portfolio-summary/portfolio-summary.component.html</context> <context context-type="sourcefile">apps/client/src/app/components/portfolio-summary/portfolio-summary.component.html</context>
<context context-type="linenumber">206</context> <context context-type="linenumber">217</context>
</context-group> </context-group>
</trans-unit> </trans-unit>
<trans-unit id="6785405835169448749" datatype="html"> <trans-unit id="6785405835169448749" datatype="html">
@ -1066,7 +1066,7 @@
<target state="translated">Sektoren</target> <target state="translated">Sektoren</target>
<context-group purpose="location"> <context-group purpose="location">
<context context-type="sourcefile">apps/client/src/app/components/position/position-detail-dialog/position-detail-dialog.html</context> <context context-type="sourcefile">apps/client/src/app/components/position/position-detail-dialog/position-detail-dialog.html</context>
<context context-type="linenumber">187</context> <context context-type="linenumber">188</context>
</context-group> </context-group>
<context-group purpose="location"> <context-group purpose="location">
<context context-type="sourcefile">apps/client/src/app/pages/public/public-page.html</context> <context context-type="sourcefile">apps/client/src/app/pages/public/public-page.html</context>
@ -1078,7 +1078,7 @@
<target state="translated">Länder</target> <target state="translated">Länder</target>
<context-group purpose="location"> <context-group purpose="location">
<context context-type="sourcefile">apps/client/src/app/components/position/position-detail-dialog/position-detail-dialog.html</context> <context context-type="sourcefile">apps/client/src/app/components/position/position-detail-dialog/position-detail-dialog.html</context>
<context context-type="linenumber">198</context> <context context-type="linenumber">199</context>
</context-group> </context-group>
</trans-unit> </trans-unit>
<trans-unit id="cafc87479686947e2590b9f588a88040aeaf660b" datatype="html"> <trans-unit id="cafc87479686947e2590b9f588a88040aeaf660b" datatype="html">
@ -1086,7 +1086,7 @@
<target state="translated">Tags</target> <target state="translated">Tags</target>
<context-group purpose="location"> <context-group purpose="location">
<context context-type="sourcefile">apps/client/src/app/components/position/position-detail-dialog/position-detail-dialog.html</context> <context context-type="sourcefile">apps/client/src/app/components/position/position-detail-dialog/position-detail-dialog.html</context>
<context context-type="linenumber">234</context> <context context-type="linenumber">235</context>
</context-group> </context-group>
<context-group purpose="location"> <context-group purpose="location">
<context context-type="sourcefile">apps/client/src/app/pages/portfolio/transactions/create-or-update-transaction-dialog/create-or-update-transaction-dialog.html</context> <context context-type="sourcefile">apps/client/src/app/pages/portfolio/transactions/create-or-update-transaction-dialog/create-or-update-transaction-dialog.html</context>
@ -1098,7 +1098,7 @@
<target state="translated">Datenfehler melden</target> <target state="translated">Datenfehler melden</target>
<context-group purpose="location"> <context-group purpose="location">
<context context-type="sourcefile">apps/client/src/app/components/position/position-detail-dialog/position-detail-dialog.html</context> <context context-type="sourcefile">apps/client/src/app/components/position/position-detail-dialog/position-detail-dialog.html</context>
<context context-type="linenumber">249</context> <context context-type="linenumber">250</context>
</context-group> </context-group>
</trans-unit> </trans-unit>
<trans-unit id="2ee26d58f2707416e636887111d5603b35346c4a" datatype="html"> <trans-unit id="2ee26d58f2707416e636887111d5603b35346c4a" datatype="html">
@ -1112,9 +1112,13 @@
<trans-unit id="3cc9c2ae277393b3946b38c088dabff671b1ee1b" datatype="html"> <trans-unit id="3cc9c2ae277393b3946b38c088dabff671b1ee1b" datatype="html">
<source>Performance</source> <source>Performance</source>
<target state="translated">Performance</target> <target state="translated">Performance</target>
<context-group purpose="location">
<context context-type="sourcefile">apps/client/src/app/components/benchmark-comparator/benchmark-comparator.component.html</context>
<context context-type="linenumber">4</context>
</context-group>
<context-group purpose="location"> <context-group purpose="location">
<context context-type="sourcefile">apps/client/src/app/components/position/position-detail-dialog/position-detail-dialog.html</context> <context context-type="sourcefile">apps/client/src/app/components/position/position-detail-dialog/position-detail-dialog.html</context>
<context context-type="linenumber">55</context> <context context-type="linenumber">56</context>
</context-group> </context-group>
<context-group purpose="location"> <context-group purpose="location">
<context context-type="sourcefile">apps/client/src/app/components/positions-table/positions-table.component.html</context> <context context-type="sourcefile">apps/client/src/app/components/positions-table/positions-table.component.html</context>
@ -1172,6 +1176,10 @@
<trans-unit id="3041670542776846470" datatype="html"> <trans-unit id="3041670542776846470" datatype="html">
<source>This feature requires a subscription.</source> <source>This feature requires a subscription.</source>
<target state="translated">Diese Funktion erfordert ein Abonnement.</target> <target state="translated">Diese Funktion erfordert ein Abonnement.</target>
<context-group purpose="location">
<context context-type="sourcefile">apps/client/src/app/components/home-summary/home-summary.component.ts</context>
<context context-type="linenumber">112</context>
</context-group>
<context-group purpose="location"> <context-group purpose="location">
<context context-type="sourcefile">apps/client/src/app/core/http-response.interceptor.ts</context> <context context-type="sourcefile">apps/client/src/app/core/http-response.interceptor.ts</context>
<context context-type="linenumber">67</context> <context context-type="linenumber">67</context>
@ -1180,6 +1188,10 @@
<trans-unit id="5499742151525073097" datatype="html"> <trans-unit id="5499742151525073097" datatype="html">
<source>Upgrade Plan</source> <source>Upgrade Plan</source>
<target state="translated">Abonnement abschliessen</target> <target state="translated">Abonnement abschliessen</target>
<context-group purpose="location">
<context context-type="sourcefile">apps/client/src/app/components/home-summary/home-summary.component.ts</context>
<context context-type="linenumber">114</context>
</context-group>
<context-group purpose="location"> <context-group purpose="location">
<context context-type="sourcefile">apps/client/src/app/core/http-response.interceptor.ts</context> <context context-type="sourcefile">apps/client/src/app/core/http-response.interceptor.ts</context>
<context context-type="linenumber">69</context> <context context-type="linenumber">69</context>
@ -1262,7 +1274,7 @@
<target state="translated">Bitte gebe deinen Gutscheincode ein:</target> <target state="translated">Bitte gebe deinen Gutscheincode ein:</target>
<context-group purpose="location"> <context-group purpose="location">
<context context-type="sourcefile">apps/client/src/app/pages/account/account-page.component.ts</context> <context context-type="sourcefile">apps/client/src/app/pages/account/account-page.component.ts</context>
<context context-type="linenumber">225</context> <context context-type="linenumber">226</context>
</context-group> </context-group>
</trans-unit> </trans-unit>
<trans-unit id="4420880039966769543" datatype="html"> <trans-unit id="4420880039966769543" datatype="html">
@ -1270,7 +1282,7 @@
<target state="translated">Gutscheincode konnte nicht eingelöst werden</target> <target state="translated">Gutscheincode konnte nicht eingelöst werden</target>
<context-group purpose="location"> <context-group purpose="location">
<context context-type="sourcefile">apps/client/src/app/pages/account/account-page.component.ts</context> <context context-type="sourcefile">apps/client/src/app/pages/account/account-page.component.ts</context>
<context context-type="linenumber">235</context> <context context-type="linenumber">236</context>
</context-group> </context-group>
</trans-unit> </trans-unit>
<trans-unit id="4819099731531004979" datatype="html"> <trans-unit id="4819099731531004979" datatype="html">
@ -1278,7 +1290,7 @@
<target state="translated">Gutscheincode wurde eingelöst</target> <target state="translated">Gutscheincode wurde eingelöst</target>
<context-group purpose="location"> <context-group purpose="location">
<context context-type="sourcefile">apps/client/src/app/pages/account/account-page.component.ts</context> <context context-type="sourcefile">apps/client/src/app/pages/account/account-page.component.ts</context>
<context context-type="linenumber">247</context> <context context-type="linenumber">248</context>
</context-group> </context-group>
</trans-unit> </trans-unit>
<trans-unit id="7967484035994732534" datatype="html"> <trans-unit id="7967484035994732534" datatype="html">
@ -1286,7 +1298,7 @@
<target state="translated">Neu laden</target> <target state="translated">Neu laden</target>
<context-group purpose="location"> <context-group purpose="location">
<context context-type="sourcefile">apps/client/src/app/pages/account/account-page.component.ts</context> <context context-type="sourcefile">apps/client/src/app/pages/account/account-page.component.ts</context>
<context context-type="linenumber">248</context> <context context-type="linenumber">249</context>
</context-group> </context-group>
</trans-unit> </trans-unit>
<trans-unit id="7963559562180316948" datatype="html"> <trans-unit id="7963559562180316948" datatype="html">
@ -1294,7 +1306,7 @@
<target state="translated">Möchtest du diese Anmeldemethode wirklich löschen?</target> <target state="translated">Möchtest du diese Anmeldemethode wirklich löschen?</target>
<context-group purpose="location"> <context-group purpose="location">
<context context-type="sourcefile">apps/client/src/app/pages/account/account-page.component.ts</context> <context context-type="sourcefile">apps/client/src/app/pages/account/account-page.component.ts</context>
<context context-type="linenumber">294</context> <context context-type="linenumber">295</context>
</context-group> </context-group>
</trans-unit> </trans-unit>
<trans-unit id="29881a45dafbe5aa05cd9d0441a4c0c2fb06df92" datatype="html"> <trans-unit id="29881a45dafbe5aa05cd9d0441a4c0c2fb06df92" datatype="html">
@ -1382,7 +1394,7 @@
<target state="translated">Lokalität</target> <target state="translated">Lokalität</target>
<context-group purpose="location"> <context-group purpose="location">
<context context-type="sourcefile">apps/client/src/app/pages/account/account-page.html</context> <context context-type="sourcefile">apps/client/src/app/pages/account/account-page.html</context>
<context context-type="linenumber">135</context> <context context-type="linenumber">144</context>
</context-group> </context-group>
</trans-unit> </trans-unit>
<trans-unit id="4402006eb2c97591dd8c87a5bd8f721fe9e4dc00" datatype="html"> <trans-unit id="4402006eb2c97591dd8c87a5bd8f721fe9e4dc00" datatype="html">
@ -1390,7 +1402,7 @@
<target state="translated">Datums- und Zahlenformat</target> <target state="translated">Datums- und Zahlenformat</target>
<context-group purpose="location"> <context-group purpose="location">
<context context-type="sourcefile">apps/client/src/app/pages/account/account-page.html</context> <context context-type="sourcefile">apps/client/src/app/pages/account/account-page.html</context>
<context context-type="linenumber">137</context> <context context-type="linenumber">146</context>
</context-group> </context-group>
</trans-unit> </trans-unit>
<trans-unit id="234d001ccf20d47ac6a2846bb029eebb61444d15" datatype="html"> <trans-unit id="234d001ccf20d47ac6a2846bb029eebb61444d15" datatype="html">
@ -1398,7 +1410,7 @@
<target state="translated">Ansicht</target> <target state="translated">Ansicht</target>
<context-group purpose="location"> <context-group purpose="location">
<context context-type="sourcefile">apps/client/src/app/pages/account/account-page.html</context> <context context-type="sourcefile">apps/client/src/app/pages/account/account-page.html</context>
<context context-type="linenumber">160</context> <context context-type="linenumber">172</context>
</context-group> </context-group>
</trans-unit> </trans-unit>
<trans-unit id="9ae348ee3a7319c2fc4794fa8bc425999d355f8f" datatype="html"> <trans-unit id="9ae348ee3a7319c2fc4794fa8bc425999d355f8f" datatype="html">
@ -1406,7 +1418,7 @@
<target state="translated">Einloggen mit Fingerabdruck</target> <target state="translated">Einloggen mit Fingerabdruck</target>
<context-group purpose="location"> <context-group purpose="location">
<context context-type="sourcefile">apps/client/src/app/pages/account/account-page.html</context> <context context-type="sourcefile">apps/client/src/app/pages/account/account-page.html</context>
<context context-type="linenumber">181</context> <context context-type="linenumber">220</context>
</context-group> </context-group>
</trans-unit> </trans-unit>
<trans-unit id="83c4d4d764d2e2725ab8e919ec16ac400e1f290a" datatype="html"> <trans-unit id="83c4d4d764d2e2725ab8e919ec16ac400e1f290a" datatype="html">
@ -1414,7 +1426,7 @@
<target state="translated">Benutzer ID</target> <target state="translated">Benutzer ID</target>
<context-group purpose="location"> <context-group purpose="location">
<context context-type="sourcefile">apps/client/src/app/pages/account/account-page.html</context> <context context-type="sourcefile">apps/client/src/app/pages/account/account-page.html</context>
<context context-type="linenumber">208</context> <context context-type="linenumber">247</context>
</context-group> </context-group>
</trans-unit> </trans-unit>
<trans-unit id="9021c579c084e68d9db06a569d76f024111c6c54" datatype="html"> <trans-unit id="9021c579c084e68d9db06a569d76f024111c6c54" datatype="html">
@ -1422,7 +1434,7 @@
<target state="translated">Zugangsberechtigung</target> <target state="translated">Zugangsberechtigung</target>
<context-group purpose="location"> <context-group purpose="location">
<context context-type="sourcefile">apps/client/src/app/pages/account/account-page.html</context> <context context-type="sourcefile">apps/client/src/app/pages/account/account-page.html</context>
<context context-type="linenumber">217</context> <context context-type="linenumber">256</context>
</context-group> </context-group>
</trans-unit> </trans-unit>
<trans-unit id="5e41f1b4c46ad9e0a9bc83fa36445483aa5cc324" datatype="html"> <trans-unit id="5e41f1b4c46ad9e0a9bc83fa36445483aa5cc324" datatype="html">
@ -1516,6 +1528,10 @@
<trans-unit id="f53dff66901984e217d461bf10fde4e26612c3d3" datatype="html"> <trans-unit id="f53dff66901984e217d461bf10fde4e26612c3d3" datatype="html">
<source>Platform</source> <source>Platform</source>
<target state="translated">Plattform</target> <target state="translated">Plattform</target>
<context-group purpose="location">
<context context-type="sourcefile">apps/client/src/app/components/account-detail-dialog/account-detail-dialog.html</context>
<context context-type="linenumber">29</context>
</context-group>
<context-group purpose="location"> <context-group purpose="location">
<context context-type="sourcefile">apps/client/src/app/components/accounts-table/accounts-table.component.html</context> <context context-type="sourcefile">apps/client/src/app/components/accounts-table/accounts-table.component.html</context>
<context context-type="linenumber">35</context> <context context-type="linenumber">35</context>
@ -1530,7 +1546,7 @@
<target state="translated">Konto ID</target> <target state="translated">Konto ID</target>
<context-group purpose="location"> <context-group purpose="location">
<context context-type="sourcefile">apps/client/src/app/pages/accounts/create-or-update-account-dialog/create-or-update-account-dialog.html</context> <context context-type="sourcefile">apps/client/src/app/pages/accounts/create-or-update-account-dialog/create-or-update-account-dialog.html</context>
<context context-type="linenumber">55</context> <context context-type="linenumber">63</context>
</context-group> </context-group>
</trans-unit> </trans-unit>
<trans-unit id="4979019387603946865" datatype="html"> <trans-unit id="4979019387603946865" datatype="html">
@ -1618,7 +1634,7 @@
<target state="translated">Nach Konto</target> <target state="translated">Nach Konto</target>
<context-group purpose="location"> <context-group purpose="location">
<context context-type="sourcefile">apps/client/src/app/pages/portfolio/allocations/allocations-page.html</context> <context context-type="sourcefile">apps/client/src/app/pages/portfolio/allocations/allocations-page.html</context>
<context context-type="linenumber">33</context> <context context-type="linenumber">41</context>
</context-group> </context-group>
</trans-unit> </trans-unit>
<trans-unit id="b79f5520c0cb9a00bd589e8a4c86ffcf5ae439d7" datatype="html"> <trans-unit id="b79f5520c0cb9a00bd589e8a4c86ffcf5ae439d7" datatype="html">
@ -1626,7 +1642,7 @@
<target state="translated">Nach Währung</target> <target state="translated">Nach Währung</target>
<context-group purpose="location"> <context-group purpose="location">
<context context-type="sourcefile">apps/client/src/app/pages/portfolio/allocations/allocations-page.html</context> <context context-type="sourcefile">apps/client/src/app/pages/portfolio/allocations/allocations-page.html</context>
<context context-type="linenumber">58</context> <context context-type="linenumber">66</context>
</context-group> </context-group>
</trans-unit> </trans-unit>
<trans-unit id="8288ff761f2d259625d2e5a3d96db727926d9cd4" datatype="html"> <trans-unit id="8288ff761f2d259625d2e5a3d96db727926d9cd4" datatype="html">
@ -1634,7 +1650,7 @@
<target state="translated">Nach Asset Class</target> <target state="translated">Nach Asset Class</target>
<context-group purpose="location"> <context-group purpose="location">
<context context-type="sourcefile">apps/client/src/app/pages/portfolio/allocations/allocations-page.html</context> <context context-type="sourcefile">apps/client/src/app/pages/portfolio/allocations/allocations-page.html</context>
<context context-type="linenumber">86</context> <context context-type="linenumber">94</context>
</context-group> </context-group>
</trans-unit> </trans-unit>
<trans-unit id="b64539bb7815eb3275b55ad723d3897fc6ba8d23" datatype="html"> <trans-unit id="b64539bb7815eb3275b55ad723d3897fc6ba8d23" datatype="html">
@ -1642,7 +1658,7 @@
<target state="translated">Nach Position</target> <target state="translated">Nach Position</target>
<context-group purpose="location"> <context-group purpose="location">
<context context-type="sourcefile">apps/client/src/app/pages/portfolio/allocations/allocations-page.html</context> <context context-type="sourcefile">apps/client/src/app/pages/portfolio/allocations/allocations-page.html</context>
<context context-type="linenumber">114</context> <context context-type="linenumber">122</context>
</context-group> </context-group>
</trans-unit> </trans-unit>
<trans-unit id="9f86714c9a6b74e13c96ab02102ce40c34fe13b9" datatype="html"> <trans-unit id="9f86714c9a6b74e13c96ab02102ce40c34fe13b9" datatype="html">
@ -1650,7 +1666,7 @@
<target state="translated">Nach Sektor</target> <target state="translated">Nach Sektor</target>
<context-group purpose="location"> <context-group purpose="location">
<context context-type="sourcefile">apps/client/src/app/pages/portfolio/allocations/allocations-page.html</context> <context context-type="sourcefile">apps/client/src/app/pages/portfolio/allocations/allocations-page.html</context>
<context context-type="linenumber">142</context> <context context-type="linenumber">150</context>
</context-group> </context-group>
</trans-unit> </trans-unit>
<trans-unit id="7017e0e26b53ef322c3e3bbf95f06a85487a12b2" datatype="html"> <trans-unit id="7017e0e26b53ef322c3e3bbf95f06a85487a12b2" datatype="html">
@ -1658,7 +1674,7 @@
<target state="translated">Nach Kontinent</target> <target state="translated">Nach Kontinent</target>
<context-group purpose="location"> <context-group purpose="location">
<context context-type="sourcefile">apps/client/src/app/pages/portfolio/allocations/allocations-page.html</context> <context context-type="sourcefile">apps/client/src/app/pages/portfolio/allocations/allocations-page.html</context>
<context context-type="linenumber">171</context> <context context-type="linenumber">179</context>
</context-group> </context-group>
</trans-unit> </trans-unit>
<trans-unit id="f27e9dd8de80176286e02312e694cb8d1e485a5d" datatype="html"> <trans-unit id="f27e9dd8de80176286e02312e694cb8d1e485a5d" datatype="html">
@ -1666,7 +1682,7 @@
<target state="translated">Nach Land</target> <target state="translated">Nach Land</target>
<context-group purpose="location"> <context-group purpose="location">
<context context-type="sourcefile">apps/client/src/app/pages/portfolio/allocations/allocations-page.html</context> <context context-type="sourcefile">apps/client/src/app/pages/portfolio/allocations/allocations-page.html</context>
<context context-type="linenumber">199</context> <context context-type="linenumber">207</context>
</context-group> </context-group>
</trans-unit> </trans-unit>
<trans-unit id="85780db87ac6c9f202615ac63754551c061e7236" datatype="html"> <trans-unit id="85780db87ac6c9f202615ac63754551c061e7236" datatype="html">
@ -1674,7 +1690,7 @@
<target state="translated">Regionen</target> <target state="translated">Regionen</target>
<context-group purpose="location"> <context-group purpose="location">
<context context-type="sourcefile">apps/client/src/app/pages/portfolio/allocations/allocations-page.html</context> <context context-type="sourcefile">apps/client/src/app/pages/portfolio/allocations/allocations-page.html</context>
<context context-type="linenumber">230</context> <context context-type="linenumber">238</context>
</context-group> </context-group>
<context-group purpose="location"> <context-group purpose="location">
<context context-type="sourcefile">apps/client/src/app/pages/public/public-page.html</context> <context context-type="sourcefile">apps/client/src/app/pages/public/public-page.html</context>
@ -1706,7 +1722,7 @@
<target state="translated">Zeitstrahl der Investitionen</target> <target state="translated">Zeitstrahl der Investitionen</target>
<context-group purpose="location"> <context-group purpose="location">
<context context-type="sourcefile">apps/client/src/app/pages/portfolio/analysis/analysis-page.html</context> <context context-type="sourcefile">apps/client/src/app/pages/portfolio/analysis/analysis-page.html</context>
<context context-type="linenumber">105</context> <context context-type="linenumber">140</context>
</context-group> </context-group>
</trans-unit> </trans-unit>
<trans-unit id="6ae1c94f6bad274424f97e9bc8766242c1577447" datatype="html"> <trans-unit id="6ae1c94f6bad274424f97e9bc8766242c1577447" datatype="html">
@ -1714,7 +1730,7 @@
<target state="translated">Gewinner</target> <target state="translated">Gewinner</target>
<context-group purpose="location"> <context-group purpose="location">
<context context-type="sourcefile">apps/client/src/app/pages/portfolio/analysis/analysis-page.html</context> <context context-type="sourcefile">apps/client/src/app/pages/portfolio/analysis/analysis-page.html</context>
<context context-type="linenumber">26</context> <context context-type="linenumber">33</context>
</context-group> </context-group>
</trans-unit> </trans-unit>
<trans-unit id="6723d5c967329a3ac75524cf0c1af5ced022b9a3" datatype="html"> <trans-unit id="6723d5c967329a3ac75524cf0c1af5ced022b9a3" datatype="html">
@ -1722,7 +1738,7 @@
<target state="translated">Verlierer</target> <target state="translated">Verlierer</target>
<context-group purpose="location"> <context-group purpose="location">
<context context-type="sourcefile">apps/client/src/app/pages/portfolio/analysis/analysis-page.html</context> <context context-type="sourcefile">apps/client/src/app/pages/portfolio/analysis/analysis-page.html</context>
<context context-type="linenumber">62</context> <context context-type="linenumber">69</context>
</context-group> </context-group>
</trans-unit> </trans-unit>
<trans-unit id="5857197365507636437" datatype="html"> <trans-unit id="5857197365507636437" datatype="html">
@ -1942,7 +1958,7 @@
<target state="translated">Anzahl</target> <target state="translated">Anzahl</target>
<context-group purpose="location"> <context-group purpose="location">
<context context-type="sourcefile">apps/client/src/app/components/position/position-detail-dialog/position-detail-dialog.html</context> <context context-type="sourcefile">apps/client/src/app/components/position/position-detail-dialog/position-detail-dialog.html</context>
<context context-type="linenumber">107</context> <context context-type="linenumber">108</context>
</context-group> </context-group>
<context-group purpose="location"> <context-group purpose="location">
<context context-type="sourcefile">apps/client/src/app/pages/portfolio/transactions/create-or-update-transaction-dialog/create-or-update-transaction-dialog.html</context> <context context-type="sourcefile">apps/client/src/app/pages/portfolio/transactions/create-or-update-transaction-dialog/create-or-update-transaction-dialog.html</context>
@ -1994,7 +2010,7 @@
</context-group> </context-group>
<context-group purpose="location"> <context-group purpose="location">
<context context-type="sourcefile">apps/client/src/app/components/position/position-detail-dialog/position-detail-dialog.html</context> <context context-type="sourcefile">apps/client/src/app/components/position/position-detail-dialog/position-detail-dialog.html</context>
<context context-type="linenumber">145</context> <context context-type="linenumber">146</context>
</context-group> </context-group>
<context-group purpose="location"> <context-group purpose="location">
<context context-type="sourcefile">apps/client/src/app/pages/portfolio/transactions/create-or-update-transaction-dialog/create-or-update-transaction-dialog.html</context> <context context-type="sourcefile">apps/client/src/app/pages/portfolio/transactions/create-or-update-transaction-dialog/create-or-update-transaction-dialog.html</context>
@ -2038,7 +2054,7 @@
<target state="translated">Portfolio</target> <target state="translated">Portfolio</target>
<context-group purpose="location"> <context-group purpose="location">
<context context-type="sourcefile">apps/client/src/app/components/benchmark-comparator/benchmark-comparator.component.ts</context> <context context-type="sourcefile">apps/client/src/app/components/benchmark-comparator/benchmark-comparator.component.ts</context>
<context context-type="linenumber">111</context> <context context-type="linenumber">99</context>
</context-group> </context-group>
<context-group purpose="location"> <context-group purpose="location">
<context context-type="sourcefile">apps/client/src/app/pages/public/public-page-routing.module.ts</context> <context context-type="sourcefile">apps/client/src/app/pages/public/public-page-routing.module.ts</context>
@ -2272,13 +2288,9 @@
<trans-unit id="313fcf0f8dac5ff5800a3e6bd67cb1955089ccca" datatype="html"> <trans-unit id="313fcf0f8dac5ff5800a3e6bd67cb1955089ccca" datatype="html">
<source>Beta</source> <source>Beta</source>
<target state="translated">Beta</target> <target state="translated">Beta</target>
<context-group purpose="location">
<context context-type="sourcefile">apps/client/src/app/components/benchmark-comparator/benchmark-comparator.component.html</context>
<context context-type="linenumber">5</context>
</context-group>
<context-group purpose="location"> <context-group purpose="location">
<context context-type="sourcefile">apps/client/src/app/pages/account/account-page.html</context> <context context-type="sourcefile">apps/client/src/app/pages/account/account-page.html</context>
<context context-type="linenumber">116</context> <context context-type="linenumber">119</context>
</context-group> </context-group>
</trans-unit> </trans-unit>
<trans-unit id="c004f99bac91f7dc28e87d458f80e5035ae99884" datatype="html"> <trans-unit id="c004f99bac91f7dc28e87d458f80e5035ae99884" datatype="html">
@ -2294,7 +2306,7 @@
<target state="translated">Sprache</target> <target state="translated">Sprache</target>
<context-group purpose="location"> <context-group purpose="location">
<context context-type="sourcefile">apps/client/src/app/pages/account/account-page.html</context> <context context-type="sourcefile">apps/client/src/app/pages/account/account-page.html</context>
<context context-type="linenumber">115</context> <context context-type="linenumber">118</context>
</context-group> </context-group>
</trans-unit> </trans-unit>
<trans-unit id="22b6da584a3402f5d6bc028dcca0975ac27ad830" datatype="html"> <trans-unit id="22b6da584a3402f5d6bc028dcca0975ac27ad830" datatype="html">
@ -2302,7 +2314,7 @@
<target state="translated">Datenverwaltung</target> <target state="translated">Datenverwaltung</target>
<context-group purpose="location"> <context-group purpose="location">
<context context-type="sourcefile">apps/client/src/app/components/admin-overview/admin-overview.html</context> <context context-type="sourcefile">apps/client/src/app/components/admin-overview/admin-overview.html</context>
<context context-type="linenumber">20</context> <context context-type="linenumber">31</context>
</context-group> </context-group>
</trans-unit> </trans-unit>
<trans-unit id="3ccabfc3dc288eaa2355ba43298c739f85951ec3" datatype="html"> <trans-unit id="3ccabfc3dc288eaa2355ba43298c739f85951ec3" datatype="html">
@ -2346,7 +2358,7 @@
<target state="translated">Änderung</target> <target state="translated">Änderung</target>
<context-group purpose="location"> <context-group purpose="location">
<context context-type="sourcefile">apps/client/src/app/components/position/position-detail-dialog/position-detail-dialog.html</context> <context context-type="sourcefile">apps/client/src/app/components/position/position-detail-dialog/position-detail-dialog.html</context>
<context context-type="linenumber">44</context> <context context-type="linenumber">45</context>
</context-group> </context-group>
</trans-unit> </trans-unit>
<trans-unit id="3c5ec7bc638db6f37c402e4afab2084f8763e268" datatype="html"> <trans-unit id="3c5ec7bc638db6f37c402e4afab2084f8763e268" datatype="html">
@ -2354,7 +2366,7 @@
<target state="translated">Ø Preis pro Einheit</target> <target state="translated">Ø Preis pro Einheit</target>
<context-group purpose="location"> <context-group purpose="location">
<context context-type="sourcefile">apps/client/src/app/components/position/position-detail-dialog/position-detail-dialog.html</context> <context context-type="sourcefile">apps/client/src/app/components/position/position-detail-dialog/position-detail-dialog.html</context>
<context context-type="linenumber">65</context> <context context-type="linenumber">66</context>
</context-group> </context-group>
</trans-unit> </trans-unit>
<trans-unit id="b1c1c6a43da1ad3e41b7a6e3aa5dcc24226cf580" datatype="html"> <trans-unit id="b1c1c6a43da1ad3e41b7a6e3aa5dcc24226cf580" datatype="html">
@ -2362,7 +2374,7 @@
<target state="translated">Minimum Preis</target> <target state="translated">Minimum Preis</target>
<context-group purpose="location"> <context-group purpose="location">
<context context-type="sourcefile">apps/client/src/app/components/position/position-detail-dialog/position-detail-dialog.html</context> <context context-type="sourcefile">apps/client/src/app/components/position/position-detail-dialog/position-detail-dialog.html</context>
<context context-type="linenumber">86</context> <context context-type="linenumber">87</context>
</context-group> </context-group>
</trans-unit> </trans-unit>
<trans-unit id="6dd84054c52e1edf631f37accb054de0c4071069" datatype="html"> <trans-unit id="6dd84054c52e1edf631f37accb054de0c4071069" datatype="html">
@ -2370,7 +2382,7 @@
<target state="translated">Maximum Preis</target> <target state="translated">Maximum Preis</target>
<context-group purpose="location"> <context-group purpose="location">
<context context-type="sourcefile">apps/client/src/app/components/position/position-detail-dialog/position-detail-dialog.html</context> <context context-type="sourcefile">apps/client/src/app/components/position/position-detail-dialog/position-detail-dialog.html</context>
<context context-type="linenumber">97</context> <context context-type="linenumber">98</context>
</context-group> </context-group>
</trans-unit> </trans-unit>
<trans-unit id="bf3df1f4eb29a071630eed167406c06f974480b2" datatype="html"> <trans-unit id="bf3df1f4eb29a071630eed167406c06f974480b2" datatype="html">
@ -2378,7 +2390,7 @@
<target state="translated">Datum des Erstkaufs</target> <target state="translated">Datum des Erstkaufs</target>
<context-group purpose="location"> <context-group purpose="location">
<context context-type="sourcefile">apps/client/src/app/components/position/position-detail-dialog/position-detail-dialog.html</context> <context context-type="sourcefile">apps/client/src/app/components/position/position-detail-dialog/position-detail-dialog.html</context>
<context context-type="linenumber">127</context> <context context-type="linenumber">128</context>
</context-group> </context-group>
</trans-unit> </trans-unit>
<trans-unit id="27fe3d097c64eaec7ff564358f80fb7ba795f484" datatype="html"> <trans-unit id="27fe3d097c64eaec7ff564358f80fb7ba795f484" datatype="html">
@ -2390,7 +2402,7 @@
</context-group> </context-group>
<context-group purpose="location"> <context-group purpose="location">
<context context-type="sourcefile">apps/client/src/app/components/position/position-detail-dialog/position-detail-dialog.html</context> <context context-type="sourcefile">apps/client/src/app/components/position/position-detail-dialog/position-detail-dialog.html</context>
<context context-type="linenumber">154</context> <context context-type="linenumber">155</context>
</context-group> </context-group>
<context-group purpose="location"> <context-group purpose="location">
<context context-type="sourcefile">apps/client/src/app/pages/portfolio/transactions/create-or-update-transaction-dialog/create-or-update-transaction-dialog.html</context> <context context-type="sourcefile">apps/client/src/app/pages/portfolio/transactions/create-or-update-transaction-dialog/create-or-update-transaction-dialog.html</context>
@ -2402,7 +2414,7 @@
<target state="translated">Sektor</target> <target state="translated">Sektor</target>
<context-group purpose="location"> <context-group purpose="location">
<context context-type="sourcefile">apps/client/src/app/components/position/position-detail-dialog/position-detail-dialog.html</context> <context context-type="sourcefile">apps/client/src/app/components/position/position-detail-dialog/position-detail-dialog.html</context>
<context context-type="linenumber">169</context> <context context-type="linenumber">170</context>
</context-group> </context-group>
</trans-unit> </trans-unit>
<trans-unit id="a43f25a9ac40e8e2441ff0be7a36b8e5d15534df" datatype="html"> <trans-unit id="a43f25a9ac40e8e2441ff0be7a36b8e5d15534df" datatype="html">
@ -2410,7 +2422,7 @@
<target state="translated">Land</target> <target state="translated">Land</target>
<context-group purpose="location"> <context-group purpose="location">
<context context-type="sourcefile">apps/client/src/app/components/position/position-detail-dialog/position-detail-dialog.html</context> <context context-type="sourcefile">apps/client/src/app/components/position/position-detail-dialog/position-detail-dialog.html</context>
<context context-type="linenumber">181</context> <context context-type="linenumber">182</context>
</context-group> </context-group>
</trans-unit> </trans-unit>
<trans-unit id="034c2b473d0b76acbc938453375b13cb2491dc17" datatype="html"> <trans-unit id="034c2b473d0b76acbc938453375b13cb2491dc17" datatype="html">
@ -2418,7 +2430,7 @@
<target state="translated">Entwickelte Länder</target> <target state="translated">Entwickelte Länder</target>
<context-group purpose="location"> <context-group purpose="location">
<context context-type="sourcefile">apps/client/src/app/pages/portfolio/allocations/allocations-page.html</context> <context context-type="sourcefile">apps/client/src/app/pages/portfolio/allocations/allocations-page.html</context>
<context context-type="linenumber">256</context> <context context-type="linenumber">264</context>
</context-group> </context-group>
<context-group purpose="location"> <context-group purpose="location">
<context context-type="sourcefile">apps/client/src/app/pages/public/public-page.html</context> <context context-type="sourcefile">apps/client/src/app/pages/public/public-page.html</context>
@ -2430,7 +2442,7 @@
<target state="translated">Schwellenländer</target> <target state="translated">Schwellenländer</target>
<context-group purpose="location"> <context-group purpose="location">
<context context-type="sourcefile">apps/client/src/app/pages/portfolio/allocations/allocations-page.html</context> <context context-type="sourcefile">apps/client/src/app/pages/portfolio/allocations/allocations-page.html</context>
<context context-type="linenumber">265</context> <context context-type="linenumber">273</context>
</context-group> </context-group>
<context-group purpose="location"> <context-group purpose="location">
<context context-type="sourcefile">apps/client/src/app/pages/public/public-page.html</context> <context context-type="sourcefile">apps/client/src/app/pages/public/public-page.html</context>
@ -2442,7 +2454,7 @@
<target state="translated">Andere Länder</target> <target state="translated">Andere Länder</target>
<context-group purpose="location"> <context-group purpose="location">
<context context-type="sourcefile">apps/client/src/app/pages/portfolio/allocations/allocations-page.html</context> <context context-type="sourcefile">apps/client/src/app/pages/portfolio/allocations/allocations-page.html</context>
<context context-type="linenumber">274</context> <context context-type="linenumber">282</context>
</context-group> </context-group>
<context-group purpose="location"> <context-group purpose="location">
<context context-type="sourcefile">apps/client/src/app/pages/public/public-page.html</context> <context context-type="sourcefile">apps/client/src/app/pages/public/public-page.html</context>
@ -2454,7 +2466,7 @@
<target state="translated">Transaktionen</target> <target state="translated">Transaktionen</target>
<context-group purpose="location"> <context-group purpose="location">
<context context-type="sourcefile">apps/client/src/app/components/position/position-detail-dialog/position-detail-dialog.html</context> <context context-type="sourcefile">apps/client/src/app/components/position/position-detail-dialog/position-detail-dialog.html</context>
<context context-type="linenumber">136</context> <context context-type="linenumber">137</context>
</context-group> </context-group>
</trans-unit> </trans-unit>
<trans-unit id="e34e2478d2d30c9d01758d01b7212411171b9bd5" datatype="html"> <trans-unit id="e34e2478d2d30c9d01758d01b7212411171b9bd5" datatype="html">
@ -2486,15 +2498,7 @@
<target state="translated">Monatlich</target> <target state="translated">Monatlich</target>
<context-group purpose="location"> <context-group purpose="location">
<context context-type="sourcefile">apps/client/src/app/pages/portfolio/analysis/analysis-page.component.ts</context> <context context-type="sourcefile">apps/client/src/app/pages/portfolio/analysis/analysis-page.component.ts</context>
<context context-type="linenumber">38</context> <context context-type="linenumber">40</context>
</context-group>
</trans-unit>
<trans-unit id="1975246224413290232" datatype="html">
<source>Accumulating</source>
<target state="translated">Aufsummiert</target>
<context-group purpose="location">
<context context-type="sourcefile">apps/client/src/app/pages/portfolio/analysis/analysis-page.component.ts</context>
<context context-type="linenumber">39</context>
</context-group> </context-group>
</trans-unit> </trans-unit>
<trans-unit id="5213771062241898526" datatype="html"> <trans-unit id="5213771062241898526" datatype="html">
@ -2502,11 +2506,11 @@
<target state="translated">Einlage</target> <target state="translated">Einlage</target>
<context-group purpose="location"> <context-group purpose="location">
<context context-type="sourcefile">apps/client/src/app/components/investment-chart/investment-chart.component.ts</context> <context context-type="sourcefile">apps/client/src/app/components/investment-chart/investment-chart.component.ts</context>
<context context-type="linenumber">132</context> <context context-type="linenumber">139</context>
</context-group> </context-group>
<context-group purpose="location"> <context-group purpose="location">
<context context-type="sourcefile">libs/ui/src/lib/fire-calculator/fire-calculator.component.ts</context> <context context-type="sourcefile">libs/ui/src/lib/fire-calculator/fire-calculator.component.ts</context>
<context context-type="linenumber">276</context> <context context-type="linenumber">279</context>
</context-group> </context-group>
</trans-unit> </trans-unit>
<trans-unit id="3441715041566940420" datatype="html"> <trans-unit id="3441715041566940420" datatype="html">
@ -2514,7 +2518,7 @@
<target state="translated">Verzinsung</target> <target state="translated">Verzinsung</target>
<context-group purpose="location"> <context-group purpose="location">
<context context-type="sourcefile">libs/ui/src/lib/fire-calculator/fire-calculator.component.ts</context> <context context-type="sourcefile">libs/ui/src/lib/fire-calculator/fire-calculator.component.ts</context>
<context context-type="linenumber">286</context> <context context-type="linenumber">289</context>
</context-group> </context-group>
</trans-unit> </trans-unit>
<trans-unit id="1054498214311181686" datatype="html"> <trans-unit id="1054498214311181686" datatype="html">
@ -2522,7 +2526,7 @@
<target state="translated">Ersparnisse</target> <target state="translated">Ersparnisse</target>
<context-group purpose="location"> <context-group purpose="location">
<context context-type="sourcefile">libs/ui/src/lib/fire-calculator/fire-calculator.component.ts</context> <context context-type="sourcefile">libs/ui/src/lib/fire-calculator/fire-calculator.component.ts</context>
<context context-type="linenumber">296</context> <context context-type="linenumber">299</context>
</context-group> </context-group>
</trans-unit> </trans-unit>
<trans-unit id="aad5320acd7453f912bc8714e72c2fa71e8ab18e" datatype="html"> <trans-unit id="aad5320acd7453f912bc8714e72c2fa71e8ab18e" datatype="html">
@ -2610,15 +2614,15 @@
<target state="translated">Experimentelle Funktionen</target> <target state="translated">Experimentelle Funktionen</target>
<context-group purpose="location"> <context-group purpose="location">
<context context-type="sourcefile">apps/client/src/app/pages/account/account-page.html</context> <context context-type="sourcefile">apps/client/src/app/pages/account/account-page.html</context>
<context context-type="linenumber">196</context> <context context-type="linenumber">235</context>
</context-group> </context-group>
</trans-unit> </trans-unit>
<trans-unit id="1b25c6e22f822e07a3e4d5aae4edc5b41fe083c2" datatype="html"> <trans-unit id="1b25c6e22f822e07a3e4d5aae4edc5b41fe083c2" datatype="html">
<source>Benchmarks</source> <source>Benchmarks</source>
<target state="translated">Benchmarks</target> <target state="translated">Benchmarks</target>
<context-group purpose="location"> <context-group purpose="location">
<context context-type="sourcefile">apps/client/src/app/components/benchmark-comparator/benchmark-comparator.component.html</context> <context context-type="sourcefile">apps/client/src/app/components/admin-overview/admin-overview.html</context>
<context context-type="linenumber">4</context> <context context-type="linenumber">123</context>
</context-group> </context-group>
</trans-unit> </trans-unit>
<trans-unit id="44fcf77e86dc038202ebad6b46d1d833d60d781b" datatype="html"> <trans-unit id="44fcf77e86dc038202ebad6b46d1d833d60d781b" datatype="html">
@ -2626,7 +2630,7 @@
<target state="translated">Vergleichen mit...</target> <target state="translated">Vergleichen mit...</target>
<context-group purpose="location"> <context-group purpose="location">
<context context-type="sourcefile">apps/client/src/app/components/benchmark-comparator/benchmark-comparator.component.html</context> <context context-type="sourcefile">apps/client/src/app/components/benchmark-comparator/benchmark-comparator.component.html</context>
<context context-type="linenumber">14</context> <context context-type="linenumber">18</context>
</context-group> </context-group>
</trans-unit> </trans-unit>
<trans-unit id="1931353503905413384" datatype="html"> <trans-unit id="1931353503905413384" datatype="html">
@ -2634,7 +2638,7 @@
<target state="translated">Benchmark</target> <target state="translated">Benchmark</target>
<context-group purpose="location"> <context-group purpose="location">
<context context-type="sourcefile">apps/client/src/app/components/benchmark-comparator/benchmark-comparator.component.ts</context> <context context-type="sourcefile">apps/client/src/app/components/benchmark-comparator/benchmark-comparator.component.ts</context>
<context context-type="linenumber">120</context> <context context-type="linenumber">108</context>
</context-group> </context-group>
</trans-unit> </trans-unit>
<trans-unit id="4210837540bca56dca96fcc451518659d06ad02a" datatype="html"> <trans-unit id="4210837540bca56dca96fcc451518659d06ad02a" datatype="html">
@ -2642,7 +2646,87 @@
<target state="translated">Anteil am Gesamtvermögen</target> <target state="translated">Anteil am Gesamtvermögen</target>
<context-group purpose="location"> <context-group purpose="location">
<context context-type="sourcefile">apps/client/src/app/pages/portfolio/allocations/allocations-page.html</context> <context context-type="sourcefile">apps/client/src/app/pages/portfolio/allocations/allocations-page.html</context>
<context context-type="linenumber">17</context> <context context-type="linenumber">18</context>
</context-group>
</trans-unit>
<trans-unit id="ac598d664f86ba5783915d65f2664a7f38a9d23a" datatype="html">
<source>Account Type</source>
<target state="translated">Kontotyp</target>
<context-group purpose="location">
<context context-type="sourcefile">apps/client/src/app/components/account-detail-dialog/account-detail-dialog.html</context>
<context context-type="linenumber">25</context>
</context-group>
</trans-unit>
<trans-unit id="98fc3013bfcbf452b9f37bbfcdb77b9b882866e3" datatype="html">
<source>Excluded from Analysis</source>
<target state="translated">Von der Analyse ausgenommen</target>
<context-group purpose="location">
<context context-type="sourcefile">apps/client/src/app/components/portfolio-summary/portfolio-summary.component.html</context>
<context context-type="linenumber">176</context>
</context-group>
</trans-unit>
<trans-unit id="458363f8e413759aa9e3235a53fd0f64cc916395" datatype="html">
<source> If you retire today, you would be able to withdraw <x id="START_TAG_SPAN" ctype="x-span" equiv-text="&lt;span class=&quot;font-weight-bold&quot; &gt;"/><x id="START_TAG_GF_VALUE" ctype="x-gf_value" equiv-text="&lt;gf-value class=&quot;d-inline-block&quot; [currency]=&quot;user?.settings?.baseCurrency&quot; [locale]=&quot;user?.settings?.locale&quot; [value]=&quot;withdrawalRatePerYear?.toNumber()&quot; &gt;"/><x id="CLOSE_TAG_GF_VALUE" ctype="x-gf_value" equiv-text="&lt;/gf-value&gt;"/> per year<x id="CLOSE_TAG_SPAN" ctype="x-span" equiv-text="&lt;/span &gt;"/> or <x id="START_TAG_SPAN" ctype="x-span" equiv-text="&lt;span class=&quot;font-weight-bold&quot; &gt;"/><x id="START_TAG_GF_VALUE_1" ctype="x-gf_value_1" equiv-text="&lt;gf-value class=&quot;d-inline-block&quot; [currency]=&quot;user?.settings?.baseCurrency&quot; [locale]=&quot;user?.settings?.locale&quot; [value]=&quot;withdrawalRatePerMonth?.toNumber()&quot; &gt;"/><x id="CLOSE_TAG_GF_VALUE" ctype="x-gf_value" equiv-text="&lt;/gf-value&gt;"/> per month<x id="CLOSE_TAG_SPAN" ctype="x-span" equiv-text="&lt;/span &gt;"/>, based on your total assets of <x id="START_TAG_GF_VALUE_2" ctype="x-gf_value_2" equiv-text="&lt;gf-value class=&quot;d-inline-block&quot; [currency]=&quot;user?.settings?.baseCurrency&quot; [locale]=&quot;user?.settings?.locale&quot; [value]=&quot;fireWealth?.toNumber()&quot; &gt;"/><x id="CLOSE_TAG_GF_VALUE" ctype="x-gf_value" equiv-text="&lt;/gf-value&gt;"/> and a withdrawal rate of 4%. </source>
<target state="translated"> Wenn du heute in den Ruhestand gehen würdest, könnest du <x id="START_TAG_SPAN" ctype="x-span" equiv-text="&lt;span class=&quot;font-weight-bold&quot; &gt;"/><x id="START_TAG_GF_VALUE" ctype="x-gf_value" equiv-text="&lt;gf-value class=&quot;d-inline-block&quot; [currency]=&quot;user?.settings?.baseCurrency&quot; [locale]=&quot;user?.settings?.locale&quot; [value]=&quot;withdrawalRatePerYear?.toNumber()&quot; &gt;"/><x id="CLOSE_TAG_GF_VALUE" ctype="x-gf_value" equiv-text="&lt;/gf-value&gt;"/> pro Jahr<x id="CLOSE_TAG_SPAN" ctype="x-span" equiv-text="&lt;/span &gt;"/> oder <x id="START_TAG_SPAN" ctype="x-span" equiv-text="&lt;span class=&quot;font-weight-bold&quot; &gt;"/><x id="START_TAG_GF_VALUE_1" ctype="x-gf_value_1" equiv-text="&lt;gf-value class=&quot;d-inline-block&quot; [currency]=&quot;user?.settings?.baseCurrency&quot; [locale]=&quot;user?.settings?.locale&quot; [value]=&quot;withdrawalRatePerMonth?.toNumber()&quot; &gt;"/><x id="CLOSE_TAG_GF_VALUE" ctype="x-gf_value" equiv-text="&lt;/gf-value&gt;"/> pro Monat<x id="CLOSE_TAG_SPAN" ctype="x-span" equiv-text="&lt;/span &gt;"/> entnehmen, bezogen auf dein Gesamtanlagevermögen von <x id="START_TAG_GF_VALUE_2" ctype="x-gf_value_2" equiv-text="&lt;gf-value class=&quot;d-inline-block&quot; [currency]=&quot;user?.settings?.baseCurrency&quot; [locale]=&quot;user?.settings?.locale&quot; [value]=&quot;fireWealth?.toNumber()&quot; &gt;"/><x id="CLOSE_TAG_GF_VALUE" ctype="x-gf_value" equiv-text="&lt;/gf-value&gt;"/> und einer Entnahmerate von 4%. </target>
<context-group purpose="location">
<context context-type="sourcefile">apps/client/src/app/pages/portfolio/fire/fire-page.html</context>
<context context-type="linenumber">38,66</context>
</context-group>
</trans-unit>
<trans-unit id="616064537937996961" datatype="html">
<source>Auto</source>
<target state="translated">Automatisch</target>
<context-group purpose="location">
<context context-type="sourcefile">apps/client/src/app/pages/account/account-page.component.ts</context>
<context context-type="linenumber">45</context>
</context-group>
</trans-unit>
<trans-unit id="bbe41ac2ea4a6c00ea941a41b33105048f8e9f13" datatype="html">
<source>Appearance</source>
<target state="translated">Aussehen</target>
<context-group purpose="location">
<context context-type="sourcefile">apps/client/src/app/pages/account/account-page.html</context>
<context context-type="linenumber">195</context>
</context-group>
</trans-unit>
<trans-unit id="5fb13fb4a8447e59cdf05dc196ade39c02a6f8aa" datatype="html">
<source>Auto</source>
<target state="translated">Automatisch</target>
<context-group purpose="location">
<context context-type="sourcefile">apps/client/src/app/pages/account/account-page.html</context>
<context context-type="linenumber">210</context>
</context-group>
</trans-unit>
<trans-unit id="693d14f486a25e86bc515dfcfc4462d5201217ef" datatype="html">
<source>Light</source>
<target state="translated">Hell</target>
<context-group purpose="location">
<context context-type="sourcefile">apps/client/src/app/pages/account/account-page.html</context>
<context context-type="linenumber">211</context>
</context-group>
</trans-unit>
<trans-unit id="adb4562d2dbd3584370e44496969d58c511ecb63" datatype="html">
<source>Dark</source>
<target state="translated">Dunkel</target>
<context-group purpose="location">
<context context-type="sourcefile">apps/client/src/app/pages/account/account-page.html</context>
<context context-type="linenumber">212</context>
</context-group>
</trans-unit>
<trans-unit id="112783260724635106" datatype="html">
<source>Total Amount</source>
<target state="translated">Gesamtbetrag</target>
<context-group purpose="location">
<context context-type="sourcefile">apps/client/src/app/components/investment-chart/investment-chart.component.ts</context>
<context context-type="linenumber">160</context>
</context-group>
</trans-unit>
<trans-unit id="f1a355a1af2e818050a3af693ac8b521fa7edc5f" datatype="html">
<source>Portfolio Evolution</source>
<target state="translated">Portfolio Wertentwicklung</target>
<context-group purpose="location">
<context context-type="sourcefile">apps/client/src/app/pages/portfolio/analysis/analysis-page.html</context>
<context context-type="linenumber">112</context>
</context-group> </context-group>
</trans-unit> </trans-unit>
</body> </body>

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

@ -35,7 +35,7 @@
</context-group> </context-group>
<context-group purpose="location"> <context-group purpose="location">
<context context-type="sourcefile">apps/client/src/app/components/admin-jobs/admin-jobs.html</context> <context context-type="sourcefile">apps/client/src/app/components/admin-jobs/admin-jobs.html</context>
<context context-type="linenumber">28</context> <context context-type="linenumber">31</context>
</context-group> </context-group>
<context-group purpose="location"> <context-group purpose="location">
<context context-type="sourcefile">apps/client/src/app/pages/account/create-or-update-access-dialog/create-or-update-access-dialog.html</context> <context context-type="sourcefile">apps/client/src/app/pages/account/create-or-update-access-dialog/create-or-update-access-dialog.html</context>
@ -79,7 +79,7 @@
<source>Activities</source> <source>Activities</source>
<context-group purpose="location"> <context-group purpose="location">
<context context-type="sourcefile">apps/client/src/app/components/account-detail-dialog/account-detail-dialog.html</context> <context context-type="sourcefile">apps/client/src/app/components/account-detail-dialog/account-detail-dialog.html</context>
<context context-type="linenumber">33</context> <context context-type="linenumber">35</context>
</context-group> </context-group>
<context-group purpose="location"> <context-group purpose="location">
<context context-type="sourcefile">apps/client/src/app/components/accounts-table/accounts-table.component.html</context> <context context-type="sourcefile">apps/client/src/app/components/accounts-table/accounts-table.component.html</context>
@ -91,7 +91,7 @@
</context-group> </context-group>
<context-group purpose="location"> <context-group purpose="location">
<context context-type="sourcefile">apps/client/src/app/components/position/position-detail-dialog/position-detail-dialog.html</context> <context context-type="sourcefile">apps/client/src/app/components/position/position-detail-dialog/position-detail-dialog.html</context>
<context context-type="linenumber">214</context> <context context-type="linenumber">215</context>
</context-group> </context-group>
<context-group purpose="location"> <context-group purpose="location">
<context context-type="sourcefile">apps/client/src/app/pages/portfolio/portfolio-page.html</context> <context context-type="sourcefile">apps/client/src/app/pages/portfolio/portfolio-page.html</context>
@ -196,14 +196,14 @@
<source>Delete Jobs</source> <source>Delete Jobs</source>
<context-group purpose="location"> <context-group purpose="location">
<context context-type="sourcefile">apps/client/src/app/components/admin-jobs/admin-jobs.html</context> <context context-type="sourcefile">apps/client/src/app/components/admin-jobs/admin-jobs.html</context>
<context context-type="linenumber">21</context> <context context-type="linenumber">24</context>
</context-group> </context-group>
</trans-unit> </trans-unit>
<trans-unit id="7cd2168068d1fd50772c493d493f83e4e412ebc8" datatype="html"> <trans-unit id="7cd2168068d1fd50772c493d493f83e4e412ebc8" datatype="html">
<source>Symbol</source> <source>Symbol</source>
<context-group purpose="location"> <context-group purpose="location">
<context context-type="sourcefile">apps/client/src/app/components/admin-jobs/admin-jobs.html</context> <context context-type="sourcefile">apps/client/src/app/components/admin-jobs/admin-jobs.html</context>
<context context-type="linenumber">29</context> <context context-type="linenumber">32</context>
</context-group> </context-group>
<context-group purpose="location"> <context-group purpose="location">
<context context-type="sourcefile">apps/client/src/app/components/admin-market-data/admin-market-data.html</context> <context context-type="sourcefile">apps/client/src/app/components/admin-market-data/admin-market-data.html</context>
@ -222,7 +222,7 @@
<source>Data Source</source> <source>Data Source</source>
<context-group purpose="location"> <context-group purpose="location">
<context context-type="sourcefile">apps/client/src/app/components/admin-jobs/admin-jobs.html</context> <context context-type="sourcefile">apps/client/src/app/components/admin-jobs/admin-jobs.html</context>
<context context-type="linenumber">30</context> <context context-type="linenumber">33</context>
</context-group> </context-group>
<context-group purpose="location"> <context-group purpose="location">
<context context-type="sourcefile">apps/client/src/app/components/admin-market-data/admin-market-data.html</context> <context context-type="sourcefile">apps/client/src/app/components/admin-market-data/admin-market-data.html</context>
@ -237,63 +237,63 @@
<source>Attempts</source> <source>Attempts</source>
<context-group purpose="location"> <context-group purpose="location">
<context context-type="sourcefile">apps/client/src/app/components/admin-jobs/admin-jobs.html</context> <context context-type="sourcefile">apps/client/src/app/components/admin-jobs/admin-jobs.html</context>
<context context-type="linenumber">31</context> <context context-type="linenumber">34</context>
</context-group> </context-group>
</trans-unit> </trans-unit>
<trans-unit id="1b051734b0ee9021991c91b3ed4e81c244322462" datatype="html"> <trans-unit id="1b051734b0ee9021991c91b3ed4e81c244322462" datatype="html">
<source>Created</source> <source>Created</source>
<context-group purpose="location"> <context-group purpose="location">
<context context-type="sourcefile">apps/client/src/app/components/admin-jobs/admin-jobs.html</context> <context context-type="sourcefile">apps/client/src/app/components/admin-jobs/admin-jobs.html</context>
<context context-type="linenumber">32</context> <context context-type="linenumber">35</context>
</context-group> </context-group>
</trans-unit> </trans-unit>
<trans-unit id="edcc19a49c950289ffe5d38be4843cdf194e5622" datatype="html"> <trans-unit id="edcc19a49c950289ffe5d38be4843cdf194e5622" datatype="html">
<source>Finished</source> <source>Finished</source>
<context-group purpose="location"> <context-group purpose="location">
<context context-type="sourcefile">apps/client/src/app/components/admin-jobs/admin-jobs.html</context> <context context-type="sourcefile">apps/client/src/app/components/admin-jobs/admin-jobs.html</context>
<context context-type="linenumber">33</context> <context context-type="linenumber">36</context>
</context-group> </context-group>
</trans-unit> </trans-unit>
<trans-unit id="81b97b8ea996ad1e4f9fca8415021850214884b1" datatype="html"> <trans-unit id="81b97b8ea996ad1e4f9fca8415021850214884b1" datatype="html">
<source>Status</source> <source>Status</source>
<context-group purpose="location"> <context-group purpose="location">
<context context-type="sourcefile">apps/client/src/app/components/admin-jobs/admin-jobs.html</context> <context context-type="sourcefile">apps/client/src/app/components/admin-jobs/admin-jobs.html</context>
<context context-type="linenumber">34</context> <context context-type="linenumber">37</context>
</context-group> </context-group>
</trans-unit> </trans-unit>
<trans-unit id="779aa6949e9d62c58ad44357d11a3157ef6780f5" datatype="html"> <trans-unit id="779aa6949e9d62c58ad44357d11a3157ef6780f5" datatype="html">
<source>Asset Profile</source> <source>Asset Profile</source>
<context-group purpose="location"> <context-group purpose="location">
<context context-type="sourcefile">apps/client/src/app/components/admin-jobs/admin-jobs.html</context> <context context-type="sourcefile">apps/client/src/app/components/admin-jobs/admin-jobs.html</context>
<context context-type="linenumber">49</context> <context context-type="linenumber">52</context>
</context-group> </context-group>
</trans-unit> </trans-unit>
<trans-unit id="8ea23a2cc3e9549fa71e7724870038a17216b210" datatype="html"> <trans-unit id="8ea23a2cc3e9549fa71e7724870038a17216b210" datatype="html">
<source>Historical Market Data</source> <source>Historical Market Data</source>
<context-group purpose="location"> <context-group purpose="location">
<context context-type="sourcefile">apps/client/src/app/components/admin-jobs/admin-jobs.html</context> <context context-type="sourcefile">apps/client/src/app/components/admin-jobs/admin-jobs.html</context>
<context context-type="linenumber">54</context> <context context-type="linenumber">57</context>
</context-group> </context-group>
</trans-unit> </trans-unit>
<trans-unit id="30afc50625f30e4ac97acc23fd7e77031a341a8f" datatype="html"> <trans-unit id="30afc50625f30e4ac97acc23fd7e77031a341a8f" datatype="html">
<source>View Data</source> <source>View Data</source>
<context-group purpose="location"> <context-group purpose="location">
<context context-type="sourcefile">apps/client/src/app/components/admin-jobs/admin-jobs.html</context> <context context-type="sourcefile">apps/client/src/app/components/admin-jobs/admin-jobs.html</context>
<context context-type="linenumber">109</context> <context context-type="linenumber">112</context>
</context-group> </context-group>
</trans-unit> </trans-unit>
<trans-unit id="94e6ec0f0d021b88dfa4ef191447315fc1898f00" datatype="html"> <trans-unit id="94e6ec0f0d021b88dfa4ef191447315fc1898f00" datatype="html">
<source>View Stacktrace</source> <source>View Stacktrace</source>
<context-group purpose="location"> <context-group purpose="location">
<context context-type="sourcefile">apps/client/src/app/components/admin-jobs/admin-jobs.html</context> <context context-type="sourcefile">apps/client/src/app/components/admin-jobs/admin-jobs.html</context>
<context context-type="linenumber">116</context> <context context-type="linenumber">119</context>
</context-group> </context-group>
</trans-unit> </trans-unit>
<trans-unit id="de50f7bcb18ed16c00012741202155acb5c61acf" datatype="html"> <trans-unit id="de50f7bcb18ed16c00012741202155acb5c61acf" datatype="html">
<source>Delete Job</source> <source>Delete Job</source>
<context-group purpose="location"> <context-group purpose="location">
<context context-type="sourcefile">apps/client/src/app/components/admin-jobs/admin-jobs.html</context> <context context-type="sourcefile">apps/client/src/app/components/admin-jobs/admin-jobs.html</context>
<context context-type="linenumber">119</context> <context context-type="linenumber">122</context>
</context-group> </context-group>
</trans-unit> </trans-unit>
<trans-unit id="bfb6a28329c452254e363723ef9718b5178dfd1d" datatype="html"> <trans-unit id="bfb6a28329c452254e363723ef9718b5178dfd1d" datatype="html">
@ -326,7 +326,7 @@
</context-group> </context-group>
<context-group purpose="location"> <context-group purpose="location">
<context context-type="sourcefile">apps/client/src/app/components/position/position-detail-dialog/position-detail-dialog.html</context> <context context-type="sourcefile">apps/client/src/app/components/position/position-detail-dialog/position-detail-dialog.html</context>
<context context-type="linenumber">75</context> <context context-type="linenumber">76</context>
</context-group> </context-group>
</trans-unit> </trans-unit>
<trans-unit id="d7b35c384aecd25a516200d6921836374613dfe7" datatype="html"> <trans-unit id="d7b35c384aecd25a516200d6921836374613dfe7" datatype="html">
@ -341,7 +341,7 @@
</context-group> </context-group>
<context-group purpose="location"> <context-group purpose="location">
<context context-type="sourcefile">apps/client/src/app/pages/accounts/create-or-update-account-dialog/create-or-update-account-dialog.html</context> <context context-type="sourcefile">apps/client/src/app/pages/accounts/create-or-update-account-dialog/create-or-update-account-dialog.html</context>
<context context-type="linenumber">66</context> <context context-type="linenumber">74</context>
</context-group> </context-group>
<context-group purpose="location"> <context-group purpose="location">
<context context-type="sourcefile">apps/client/src/app/pages/portfolio/transactions/create-or-update-transaction-dialog/create-or-update-transaction-dialog.html</context> <context context-type="sourcefile">apps/client/src/app/pages/portfolio/transactions/create-or-update-transaction-dialog/create-or-update-transaction-dialog.html</context>
@ -364,7 +364,7 @@
</context-group> </context-group>
<context-group purpose="location"> <context-group purpose="location">
<context context-type="sourcefile">apps/client/src/app/pages/accounts/create-or-update-account-dialog/create-or-update-account-dialog.html</context> <context context-type="sourcefile">apps/client/src/app/pages/accounts/create-or-update-account-dialog/create-or-update-account-dialog.html</context>
<context context-type="linenumber">73</context> <context context-type="linenumber">81</context>
</context-group> </context-group>
<context-group purpose="location"> <context-group purpose="location">
<context context-type="sourcefile">apps/client/src/app/pages/portfolio/transactions/create-or-update-transaction-dialog/create-or-update-transaction-dialog.html</context> <context context-type="sourcefile">apps/client/src/app/pages/portfolio/transactions/create-or-update-transaction-dialog/create-or-update-transaction-dialog.html</context>
@ -386,7 +386,7 @@
</context-group> </context-group>
<context-group purpose="location"> <context-group purpose="location">
<context context-type="sourcefile">apps/client/src/app/components/admin-overview/admin-overview.html</context> <context context-type="sourcefile">apps/client/src/app/components/admin-overview/admin-overview.html</context>
<context context-type="linenumber">11</context> <context context-type="linenumber">17</context>
</context-group> </context-group>
</trans-unit> </trans-unit>
<trans-unit id="5eebcc824c2d34b17abba8c9ef718c5ce118a5c8" datatype="html"> <trans-unit id="5eebcc824c2d34b17abba8c9ef718c5ce118a5c8" datatype="html">
@ -435,7 +435,7 @@
<source>Please set your system message:</source> <source>Please set your system message:</source>
<context-group purpose="location"> <context-group purpose="location">
<context context-type="sourcefile">apps/client/src/app/components/admin-overview/admin-overview.component.ts</context> <context context-type="sourcefile">apps/client/src/app/components/admin-overview/admin-overview.component.ts</context>
<context context-type="linenumber">199</context> <context context-type="linenumber">202</context>
</context-group> </context-group>
</trans-unit> </trans-unit>
<trans-unit id="ec03f5c28b327fc7ecfc4b20a0a7cf14a75843ff" datatype="html"> <trans-unit id="ec03f5c28b327fc7ecfc4b20a0a7cf14a75843ff" datatype="html">
@ -449,21 +449,21 @@
<source>per User</source> <source>per User</source>
<context-group purpose="location"> <context-group purpose="location">
<context context-type="sourcefile">apps/client/src/app/components/admin-overview/admin-overview.html</context> <context context-type="sourcefile">apps/client/src/app/components/admin-overview/admin-overview.html</context>
<context context-type="linenumber">15</context> <context context-type="linenumber">26</context>
</context-group> </context-group>
</trans-unit> </trans-unit>
<trans-unit id="5067ede95804e0e0e2b078747848367a7373cc9b" datatype="html"> <trans-unit id="5067ede95804e0e0e2b078747848367a7373cc9b" datatype="html">
<source>Gather Recent Data</source> <source>Gather Recent Data</source>
<context-group purpose="location"> <context-group purpose="location">
<context context-type="sourcefile">apps/client/src/app/components/admin-overview/admin-overview.html</context> <context context-type="sourcefile">apps/client/src/app/components/admin-overview/admin-overview.html</context>
<context context-type="linenumber">33</context> <context context-type="linenumber">44</context>
</context-group> </context-group>
</trans-unit> </trans-unit>
<trans-unit id="27e27f3f7338baefacfd5668bad03ece9c5955df" datatype="html"> <trans-unit id="27e27f3f7338baefacfd5668bad03ece9c5955df" datatype="html">
<source>Gather All Data</source> <source>Gather All Data</source>
<context-group purpose="location"> <context-group purpose="location">
<context context-type="sourcefile">apps/client/src/app/components/admin-overview/admin-overview.html</context> <context context-type="sourcefile">apps/client/src/app/components/admin-overview/admin-overview.html</context>
<context context-type="linenumber">46</context> <context context-type="linenumber">57</context>
</context-group> </context-group>
</trans-unit> </trans-unit>
<trans-unit id="cc65b67b46b69cf06ff1f16a909e61612c9d57b8" datatype="html"> <trans-unit id="cc65b67b46b69cf06ff1f16a909e61612c9d57b8" datatype="html">
@ -474,70 +474,70 @@
</context-group> </context-group>
<context-group purpose="location"> <context-group purpose="location">
<context context-type="sourcefile">apps/client/src/app/components/admin-overview/admin-overview.html</context> <context context-type="sourcefile">apps/client/src/app/components/admin-overview/admin-overview.html</context>
<context context-type="linenumber">60</context> <context context-type="linenumber">71</context>
</context-group> </context-group>
</trans-unit> </trans-unit>
<trans-unit id="37ff0697aa81b2777250c483d2ce1ebdaa5ab042" datatype="html"> <trans-unit id="37ff0697aa81b2777250c483d2ce1ebdaa5ab042" datatype="html">
<source>Exchange Rates</source> <source>Exchange Rates</source>
<context-group purpose="location"> <context-group purpose="location">
<context context-type="sourcefile">apps/client/src/app/components/admin-overview/admin-overview.html</context> <context context-type="sourcefile">apps/client/src/app/components/admin-overview/admin-overview.html</context>
<context context-type="linenumber">67</context> <context context-type="linenumber">78</context>
</context-group> </context-group>
</trans-unit> </trans-unit>
<trans-unit id="4567a660a7f67e254bbf13219906843e34d9f807" datatype="html"> <trans-unit id="4567a660a7f67e254bbf13219906843e34d9f807" datatype="html">
<source>Add Currency</source> <source>Add Currency</source>
<context-group purpose="location"> <context-group purpose="location">
<context context-type="sourcefile">apps/client/src/app/components/admin-overview/admin-overview.html</context> <context context-type="sourcefile">apps/client/src/app/components/admin-overview/admin-overview.html</context>
<context context-type="linenumber">106</context> <context context-type="linenumber">117</context>
</context-group> </context-group>
</trans-unit> </trans-unit>
<trans-unit id="860e5f056b59410ec8db65cb53955505c6931752" datatype="html"> <trans-unit id="860e5f056b59410ec8db65cb53955505c6931752" datatype="html">
<source>System Message</source> <source>System Message</source>
<context-group purpose="location"> <context-group purpose="location">
<context context-type="sourcefile">apps/client/src/app/components/admin-overview/admin-overview.html</context> <context context-type="sourcefile">apps/client/src/app/components/admin-overview/admin-overview.html</context>
<context context-type="linenumber">112</context> <context context-type="linenumber">133</context>
</context-group> </context-group>
</trans-unit> </trans-unit>
<trans-unit id="657028d5fc9c3da8f2d667b6b15cd0df8b9a3729" datatype="html"> <trans-unit id="657028d5fc9c3da8f2d667b6b15cd0df8b9a3729" datatype="html">
<source>Set Message</source> <source>Set Message</source>
<context-group purpose="location"> <context-group purpose="location">
<context context-type="sourcefile">apps/client/src/app/components/admin-overview/admin-overview.html</context> <context context-type="sourcefile">apps/client/src/app/components/admin-overview/admin-overview.html</context>
<context context-type="linenumber">134</context> <context context-type="linenumber">155</context>
</context-group> </context-group>
</trans-unit> </trans-unit>
<trans-unit id="7fd64c34428887e4cd56d05534b89c100b8544ad" datatype="html"> <trans-unit id="7fd64c34428887e4cd56d05534b89c100b8544ad" datatype="html">
<source>Read-only Mode</source> <source>Read-only Mode</source>
<context-group purpose="location"> <context-group purpose="location">
<context context-type="sourcefile">apps/client/src/app/components/admin-overview/admin-overview.html</context> <context context-type="sourcefile">apps/client/src/app/components/admin-overview/admin-overview.html</context>
<context context-type="linenumber">139</context> <context context-type="linenumber">160</context>
</context-group> </context-group>
</trans-unit> </trans-unit>
<trans-unit id="e698b03c34b459b1b006d7f0473a49b9fcf5dfc1" datatype="html"> <trans-unit id="e698b03c34b459b1b006d7f0473a49b9fcf5dfc1" datatype="html">
<source>Coupons</source> <source>Coupons</source>
<context-group purpose="location"> <context-group purpose="location">
<context context-type="sourcefile">apps/client/src/app/components/admin-overview/admin-overview.html</context> <context context-type="sourcefile">apps/client/src/app/components/admin-overview/admin-overview.html</context>
<context context-type="linenumber">152</context> <context context-type="linenumber">173</context>
</context-group> </context-group>
</trans-unit> </trans-unit>
<trans-unit id="f6755cff4957d5c3c89bafce5651f1b6fa2b1fd9" datatype="html"> <trans-unit id="f6755cff4957d5c3c89bafce5651f1b6fa2b1fd9" datatype="html">
<source>Add</source> <source>Add</source>
<context-group purpose="location"> <context-group purpose="location">
<context context-type="sourcefile">apps/client/src/app/components/admin-overview/admin-overview.html</context> <context context-type="sourcefile">apps/client/src/app/components/admin-overview/admin-overview.html</context>
<context context-type="linenumber">183</context> <context context-type="linenumber">208</context>
</context-group> </context-group>
</trans-unit> </trans-unit>
<trans-unit id="e799e6b926557f0098f41888cdf8df868eff3d47" datatype="html"> <trans-unit id="e799e6b926557f0098f41888cdf8df868eff3d47" datatype="html">
<source>Housekeeping</source> <source>Housekeeping</source>
<context-group purpose="location"> <context-group purpose="location">
<context context-type="sourcefile">apps/client/src/app/components/admin-overview/admin-overview.html</context> <context context-type="sourcefile">apps/client/src/app/components/admin-overview/admin-overview.html</context>
<context context-type="linenumber">190</context> <context context-type="linenumber">215</context>
</context-group> </context-group>
</trans-unit> </trans-unit>
<trans-unit id="c7ac907e52a7ce2ac70b1786eb5f403ce306ce1f" datatype="html"> <trans-unit id="c7ac907e52a7ce2ac70b1786eb5f403ce306ce1f" datatype="html">
<source>Flush Cache</source> <source>Flush Cache</source>
<context-group purpose="location"> <context-group purpose="location">
<context context-type="sourcefile">apps/client/src/app/components/admin-overview/admin-overview.html</context> <context context-type="sourcefile">apps/client/src/app/components/admin-overview/admin-overview.html</context>
<context context-type="linenumber">194</context> <context context-type="linenumber">219</context>
</context-group> </context-group>
</trans-unit> </trans-unit>
<trans-unit id="2817099043823177227" datatype="html"> <trans-unit id="2817099043823177227" datatype="html">
@ -859,7 +859,7 @@
</context-group> </context-group>
<context-group purpose="location"> <context-group purpose="location">
<context context-type="sourcefile">apps/client/src/app/components/position/position-detail-dialog/position-detail-dialog.html</context> <context context-type="sourcefile">apps/client/src/app/components/position/position-detail-dialog/position-detail-dialog.html</context>
<context context-type="linenumber">117</context> <context context-type="linenumber">118</context>
</context-group> </context-group>
</trans-unit> </trans-unit>
<trans-unit id="a64cd8d0131a583e3de081c9e6458af7aecdafbc" datatype="html"> <trans-unit id="a64cd8d0131a583e3de081c9e6458af7aecdafbc" datatype="html">
@ -936,21 +936,21 @@
<source>Net Worth</source> <source>Net Worth</source>
<context-group purpose="location"> <context-group purpose="location">
<context context-type="sourcefile">apps/client/src/app/components/portfolio-summary/portfolio-summary.component.html</context> <context context-type="sourcefile">apps/client/src/app/components/portfolio-summary/portfolio-summary.component.html</context>
<context context-type="linenumber">179</context> <context context-type="linenumber">190</context>
</context-group> </context-group>
</trans-unit> </trans-unit>
<trans-unit id="e1b20ce1622d86ae0a75a3555a1a9ae7c2c60e58" datatype="html"> <trans-unit id="e1b20ce1622d86ae0a75a3555a1a9ae7c2c60e58" datatype="html">
<source>Annualized Performance</source> <source>Annualized Performance</source>
<context-group purpose="location"> <context-group purpose="location">
<context context-type="sourcefile">apps/client/src/app/components/portfolio-summary/portfolio-summary.component.html</context> <context context-type="sourcefile">apps/client/src/app/components/portfolio-summary/portfolio-summary.component.html</context>
<context context-type="linenumber">190</context> <context context-type="linenumber">201</context>
</context-group> </context-group>
</trans-unit> </trans-unit>
<trans-unit id="d3aa83bd247983dd056a62f56ffb25269bd98d47" datatype="html"> <trans-unit id="d3aa83bd247983dd056a62f56ffb25269bd98d47" datatype="html">
<source>Dividend</source> <source>Dividend</source>
<context-group purpose="location"> <context-group purpose="location">
<context context-type="sourcefile">apps/client/src/app/components/portfolio-summary/portfolio-summary.component.html</context> <context context-type="sourcefile">apps/client/src/app/components/portfolio-summary/portfolio-summary.component.html</context>
<context context-type="linenumber">206</context> <context context-type="linenumber">217</context>
</context-group> </context-group>
</trans-unit> </trans-unit>
<trans-unit id="6785405835169448749" datatype="html"> <trans-unit id="6785405835169448749" datatype="html">
@ -964,7 +964,7 @@
<source>Sectors</source> <source>Sectors</source>
<context-group purpose="location"> <context-group purpose="location">
<context context-type="sourcefile">apps/client/src/app/components/position/position-detail-dialog/position-detail-dialog.html</context> <context context-type="sourcefile">apps/client/src/app/components/position/position-detail-dialog/position-detail-dialog.html</context>
<context context-type="linenumber">187</context> <context context-type="linenumber">188</context>
</context-group> </context-group>
<context-group purpose="location"> <context-group purpose="location">
<context context-type="sourcefile">apps/client/src/app/pages/public/public-page.html</context> <context context-type="sourcefile">apps/client/src/app/pages/public/public-page.html</context>
@ -975,14 +975,14 @@
<source>Countries</source> <source>Countries</source>
<context-group purpose="location"> <context-group purpose="location">
<context context-type="sourcefile">apps/client/src/app/components/position/position-detail-dialog/position-detail-dialog.html</context> <context context-type="sourcefile">apps/client/src/app/components/position/position-detail-dialog/position-detail-dialog.html</context>
<context context-type="linenumber">198</context> <context context-type="linenumber">199</context>
</context-group> </context-group>
</trans-unit> </trans-unit>
<trans-unit id="cafc87479686947e2590b9f588a88040aeaf660b" datatype="html"> <trans-unit id="cafc87479686947e2590b9f588a88040aeaf660b" datatype="html">
<source>Tags</source> <source>Tags</source>
<context-group purpose="location"> <context-group purpose="location">
<context context-type="sourcefile">apps/client/src/app/components/position/position-detail-dialog/position-detail-dialog.html</context> <context context-type="sourcefile">apps/client/src/app/components/position/position-detail-dialog/position-detail-dialog.html</context>
<context context-type="linenumber">234</context> <context context-type="linenumber">235</context>
</context-group> </context-group>
<context-group purpose="location"> <context-group purpose="location">
<context context-type="sourcefile">apps/client/src/app/pages/portfolio/transactions/create-or-update-transaction-dialog/create-or-update-transaction-dialog.html</context> <context context-type="sourcefile">apps/client/src/app/pages/portfolio/transactions/create-or-update-transaction-dialog/create-or-update-transaction-dialog.html</context>
@ -993,7 +993,7 @@
<source>Report Data Glitch</source> <source>Report Data Glitch</source>
<context-group purpose="location"> <context-group purpose="location">
<context context-type="sourcefile">apps/client/src/app/components/position/position-detail-dialog/position-detail-dialog.html</context> <context context-type="sourcefile">apps/client/src/app/components/position/position-detail-dialog/position-detail-dialog.html</context>
<context context-type="linenumber">249</context> <context context-type="linenumber">250</context>
</context-group> </context-group>
</trans-unit> </trans-unit>
<trans-unit id="2ee26d58f2707416e636887111d5603b35346c4a" datatype="html"> <trans-unit id="2ee26d58f2707416e636887111d5603b35346c4a" datatype="html">
@ -1005,9 +1005,13 @@
</trans-unit> </trans-unit>
<trans-unit id="3cc9c2ae277393b3946b38c088dabff671b1ee1b" datatype="html"> <trans-unit id="3cc9c2ae277393b3946b38c088dabff671b1ee1b" datatype="html">
<source>Performance</source> <source>Performance</source>
<context-group purpose="location">
<context context-type="sourcefile">apps/client/src/app/components/benchmark-comparator/benchmark-comparator.component.html</context>
<context context-type="linenumber">4</context>
</context-group>
<context-group purpose="location"> <context-group purpose="location">
<context context-type="sourcefile">apps/client/src/app/components/position/position-detail-dialog/position-detail-dialog.html</context> <context context-type="sourcefile">apps/client/src/app/components/position/position-detail-dialog/position-detail-dialog.html</context>
<context context-type="linenumber">55</context> <context context-type="linenumber">56</context>
</context-group> </context-group>
<context-group purpose="location"> <context-group purpose="location">
<context context-type="sourcefile">apps/client/src/app/components/positions-table/positions-table.component.html</context> <context context-type="sourcefile">apps/client/src/app/components/positions-table/positions-table.component.html</context>
@ -1058,6 +1062,10 @@
</trans-unit> </trans-unit>
<trans-unit id="3041670542776846470" datatype="html"> <trans-unit id="3041670542776846470" datatype="html">
<source>This feature requires a subscription.</source> <source>This feature requires a subscription.</source>
<context-group purpose="location">
<context context-type="sourcefile">apps/client/src/app/components/home-summary/home-summary.component.ts</context>
<context context-type="linenumber">112</context>
</context-group>
<context-group purpose="location"> <context-group purpose="location">
<context context-type="sourcefile">apps/client/src/app/core/http-response.interceptor.ts</context> <context context-type="sourcefile">apps/client/src/app/core/http-response.interceptor.ts</context>
<context context-type="linenumber">67</context> <context context-type="linenumber">67</context>
@ -1065,6 +1073,10 @@
</trans-unit> </trans-unit>
<trans-unit id="5499742151525073097" datatype="html"> <trans-unit id="5499742151525073097" datatype="html">
<source>Upgrade Plan</source> <source>Upgrade Plan</source>
<context-group purpose="location">
<context context-type="sourcefile">apps/client/src/app/components/home-summary/home-summary.component.ts</context>
<context context-type="linenumber">114</context>
</context-group>
<context-group purpose="location"> <context-group purpose="location">
<context context-type="sourcefile">apps/client/src/app/core/http-response.interceptor.ts</context> <context context-type="sourcefile">apps/client/src/app/core/http-response.interceptor.ts</context>
<context context-type="linenumber">69</context> <context context-type="linenumber">69</context>
@ -1137,35 +1149,35 @@
<source>Please enter your coupon code:</source> <source>Please enter your coupon code:</source>
<context-group purpose="location"> <context-group purpose="location">
<context context-type="sourcefile">apps/client/src/app/pages/account/account-page.component.ts</context> <context context-type="sourcefile">apps/client/src/app/pages/account/account-page.component.ts</context>
<context context-type="linenumber">225</context> <context context-type="linenumber">226</context>
</context-group> </context-group>
</trans-unit> </trans-unit>
<trans-unit id="4420880039966769543" datatype="html"> <trans-unit id="4420880039966769543" datatype="html">
<source>Could not redeem coupon code</source> <source>Could not redeem coupon code</source>
<context-group purpose="location"> <context-group purpose="location">
<context context-type="sourcefile">apps/client/src/app/pages/account/account-page.component.ts</context> <context context-type="sourcefile">apps/client/src/app/pages/account/account-page.component.ts</context>
<context context-type="linenumber">235</context> <context context-type="linenumber">236</context>
</context-group> </context-group>
</trans-unit> </trans-unit>
<trans-unit id="4819099731531004979" datatype="html"> <trans-unit id="4819099731531004979" datatype="html">
<source>Coupon code has been redeemed</source> <source>Coupon code has been redeemed</source>
<context-group purpose="location"> <context-group purpose="location">
<context context-type="sourcefile">apps/client/src/app/pages/account/account-page.component.ts</context> <context context-type="sourcefile">apps/client/src/app/pages/account/account-page.component.ts</context>
<context context-type="linenumber">247</context> <context context-type="linenumber">248</context>
</context-group> </context-group>
</trans-unit> </trans-unit>
<trans-unit id="7967484035994732534" datatype="html"> <trans-unit id="7967484035994732534" datatype="html">
<source>Reload</source> <source>Reload</source>
<context-group purpose="location"> <context-group purpose="location">
<context context-type="sourcefile">apps/client/src/app/pages/account/account-page.component.ts</context> <context context-type="sourcefile">apps/client/src/app/pages/account/account-page.component.ts</context>
<context context-type="linenumber">248</context> <context context-type="linenumber">249</context>
</context-group> </context-group>
</trans-unit> </trans-unit>
<trans-unit id="7963559562180316948" datatype="html"> <trans-unit id="7963559562180316948" datatype="html">
<source>Do you really want to remove this sign in method?</source> <source>Do you really want to remove this sign in method?</source>
<context-group purpose="location"> <context-group purpose="location">
<context context-type="sourcefile">apps/client/src/app/pages/account/account-page.component.ts</context> <context context-type="sourcefile">apps/client/src/app/pages/account/account-page.component.ts</context>
<context context-type="linenumber">294</context> <context context-type="linenumber">295</context>
</context-group> </context-group>
</trans-unit> </trans-unit>
<trans-unit id="29881a45dafbe5aa05cd9d0441a4c0c2fb06df92" datatype="html"> <trans-unit id="29881a45dafbe5aa05cd9d0441a4c0c2fb06df92" datatype="html">
@ -1243,42 +1255,42 @@
<source>Locale</source> <source>Locale</source>
<context-group purpose="location"> <context-group purpose="location">
<context context-type="sourcefile">apps/client/src/app/pages/account/account-page.html</context> <context context-type="sourcefile">apps/client/src/app/pages/account/account-page.html</context>
<context context-type="linenumber">135</context> <context context-type="linenumber">144</context>
</context-group> </context-group>
</trans-unit> </trans-unit>
<trans-unit id="4402006eb2c97591dd8c87a5bd8f721fe9e4dc00" datatype="html"> <trans-unit id="4402006eb2c97591dd8c87a5bd8f721fe9e4dc00" datatype="html">
<source>Date and number format</source> <source>Date and number format</source>
<context-group purpose="location"> <context-group purpose="location">
<context context-type="sourcefile">apps/client/src/app/pages/account/account-page.html</context> <context context-type="sourcefile">apps/client/src/app/pages/account/account-page.html</context>
<context context-type="linenumber">137</context> <context context-type="linenumber">146</context>
</context-group> </context-group>
</trans-unit> </trans-unit>
<trans-unit id="234d001ccf20d47ac6a2846bb029eebb61444d15" datatype="html"> <trans-unit id="234d001ccf20d47ac6a2846bb029eebb61444d15" datatype="html">
<source>View Mode</source> <source>View Mode</source>
<context-group purpose="location"> <context-group purpose="location">
<context context-type="sourcefile">apps/client/src/app/pages/account/account-page.html</context> <context context-type="sourcefile">apps/client/src/app/pages/account/account-page.html</context>
<context context-type="linenumber">160</context> <context context-type="linenumber">172</context>
</context-group> </context-group>
</trans-unit> </trans-unit>
<trans-unit id="9ae348ee3a7319c2fc4794fa8bc425999d355f8f" datatype="html"> <trans-unit id="9ae348ee3a7319c2fc4794fa8bc425999d355f8f" datatype="html">
<source>Sign in with fingerprint</source> <source>Sign in with fingerprint</source>
<context-group purpose="location"> <context-group purpose="location">
<context context-type="sourcefile">apps/client/src/app/pages/account/account-page.html</context> <context context-type="sourcefile">apps/client/src/app/pages/account/account-page.html</context>
<context context-type="linenumber">181</context> <context context-type="linenumber">220</context>
</context-group> </context-group>
</trans-unit> </trans-unit>
<trans-unit id="83c4d4d764d2e2725ab8e919ec16ac400e1f290a" datatype="html"> <trans-unit id="83c4d4d764d2e2725ab8e919ec16ac400e1f290a" datatype="html">
<source>User ID</source> <source>User ID</source>
<context-group purpose="location"> <context-group purpose="location">
<context context-type="sourcefile">apps/client/src/app/pages/account/account-page.html</context> <context context-type="sourcefile">apps/client/src/app/pages/account/account-page.html</context>
<context context-type="linenumber">208</context> <context context-type="linenumber">247</context>
</context-group> </context-group>
</trans-unit> </trans-unit>
<trans-unit id="9021c579c084e68d9db06a569d76f024111c6c54" datatype="html"> <trans-unit id="9021c579c084e68d9db06a569d76f024111c6c54" datatype="html">
<source>Granted Access</source> <source>Granted Access</source>
<context-group purpose="location"> <context-group purpose="location">
<context context-type="sourcefile">apps/client/src/app/pages/account/account-page.html</context> <context context-type="sourcefile">apps/client/src/app/pages/account/account-page.html</context>
<context context-type="linenumber">217</context> <context context-type="linenumber">256</context>
</context-group> </context-group>
</trans-unit> </trans-unit>
<trans-unit id="5e41f1b4c46ad9e0a9bc83fa36445483aa5cc324" datatype="html"> <trans-unit id="5e41f1b4c46ad9e0a9bc83fa36445483aa5cc324" datatype="html">
@ -1362,6 +1374,10 @@
</trans-unit> </trans-unit>
<trans-unit id="f53dff66901984e217d461bf10fde4e26612c3d3" datatype="html"> <trans-unit id="f53dff66901984e217d461bf10fde4e26612c3d3" datatype="html">
<source>Platform</source> <source>Platform</source>
<context-group purpose="location">
<context context-type="sourcefile">apps/client/src/app/components/account-detail-dialog/account-detail-dialog.html</context>
<context context-type="linenumber">29</context>
</context-group>
<context-group purpose="location"> <context-group purpose="location">
<context context-type="sourcefile">apps/client/src/app/components/accounts-table/accounts-table.component.html</context> <context context-type="sourcefile">apps/client/src/app/components/accounts-table/accounts-table.component.html</context>
<context context-type="linenumber">35</context> <context context-type="linenumber">35</context>
@ -1375,7 +1391,7 @@
<source>Account ID</source> <source>Account ID</source>
<context-group purpose="location"> <context-group purpose="location">
<context context-type="sourcefile">apps/client/src/app/pages/accounts/create-or-update-account-dialog/create-or-update-account-dialog.html</context> <context context-type="sourcefile">apps/client/src/app/pages/accounts/create-or-update-account-dialog/create-or-update-account-dialog.html</context>
<context context-type="linenumber">55</context> <context context-type="linenumber">63</context>
</context-group> </context-group>
</trans-unit> </trans-unit>
<trans-unit id="4979019387603946865" datatype="html"> <trans-unit id="4979019387603946865" datatype="html">
@ -1453,56 +1469,56 @@
<source>By Account</source> <source>By Account</source>
<context-group purpose="location"> <context-group purpose="location">
<context context-type="sourcefile">apps/client/src/app/pages/portfolio/allocations/allocations-page.html</context> <context context-type="sourcefile">apps/client/src/app/pages/portfolio/allocations/allocations-page.html</context>
<context context-type="linenumber">33</context> <context context-type="linenumber">41</context>
</context-group> </context-group>
</trans-unit> </trans-unit>
<trans-unit id="b79f5520c0cb9a00bd589e8a4c86ffcf5ae439d7" datatype="html"> <trans-unit id="b79f5520c0cb9a00bd589e8a4c86ffcf5ae439d7" datatype="html">
<source>By Currency</source> <source>By Currency</source>
<context-group purpose="location"> <context-group purpose="location">
<context context-type="sourcefile">apps/client/src/app/pages/portfolio/allocations/allocations-page.html</context> <context context-type="sourcefile">apps/client/src/app/pages/portfolio/allocations/allocations-page.html</context>
<context context-type="linenumber">58</context> <context context-type="linenumber">66</context>
</context-group> </context-group>
</trans-unit> </trans-unit>
<trans-unit id="8288ff761f2d259625d2e5a3d96db727926d9cd4" datatype="html"> <trans-unit id="8288ff761f2d259625d2e5a3d96db727926d9cd4" datatype="html">
<source>By Asset Class</source> <source>By Asset Class</source>
<context-group purpose="location"> <context-group purpose="location">
<context context-type="sourcefile">apps/client/src/app/pages/portfolio/allocations/allocations-page.html</context> <context context-type="sourcefile">apps/client/src/app/pages/portfolio/allocations/allocations-page.html</context>
<context context-type="linenumber">86</context> <context context-type="linenumber">94</context>
</context-group> </context-group>
</trans-unit> </trans-unit>
<trans-unit id="b64539bb7815eb3275b55ad723d3897fc6ba8d23" datatype="html"> <trans-unit id="b64539bb7815eb3275b55ad723d3897fc6ba8d23" datatype="html">
<source>By Holding</source> <source>By Holding</source>
<context-group purpose="location"> <context-group purpose="location">
<context context-type="sourcefile">apps/client/src/app/pages/portfolio/allocations/allocations-page.html</context> <context context-type="sourcefile">apps/client/src/app/pages/portfolio/allocations/allocations-page.html</context>
<context context-type="linenumber">114</context> <context context-type="linenumber">122</context>
</context-group> </context-group>
</trans-unit> </trans-unit>
<trans-unit id="9f86714c9a6b74e13c96ab02102ce40c34fe13b9" datatype="html"> <trans-unit id="9f86714c9a6b74e13c96ab02102ce40c34fe13b9" datatype="html">
<source>By Sector</source> <source>By Sector</source>
<context-group purpose="location"> <context-group purpose="location">
<context context-type="sourcefile">apps/client/src/app/pages/portfolio/allocations/allocations-page.html</context> <context context-type="sourcefile">apps/client/src/app/pages/portfolio/allocations/allocations-page.html</context>
<context context-type="linenumber">142</context> <context context-type="linenumber">150</context>
</context-group> </context-group>
</trans-unit> </trans-unit>
<trans-unit id="7017e0e26b53ef322c3e3bbf95f06a85487a12b2" datatype="html"> <trans-unit id="7017e0e26b53ef322c3e3bbf95f06a85487a12b2" datatype="html">
<source>By Continent</source> <source>By Continent</source>
<context-group purpose="location"> <context-group purpose="location">
<context context-type="sourcefile">apps/client/src/app/pages/portfolio/allocations/allocations-page.html</context> <context context-type="sourcefile">apps/client/src/app/pages/portfolio/allocations/allocations-page.html</context>
<context context-type="linenumber">171</context> <context context-type="linenumber">179</context>
</context-group> </context-group>
</trans-unit> </trans-unit>
<trans-unit id="f27e9dd8de80176286e02312e694cb8d1e485a5d" datatype="html"> <trans-unit id="f27e9dd8de80176286e02312e694cb8d1e485a5d" datatype="html">
<source>By Country</source> <source>By Country</source>
<context-group purpose="location"> <context-group purpose="location">
<context context-type="sourcefile">apps/client/src/app/pages/portfolio/allocations/allocations-page.html</context> <context context-type="sourcefile">apps/client/src/app/pages/portfolio/allocations/allocations-page.html</context>
<context context-type="linenumber">199</context> <context context-type="linenumber">207</context>
</context-group> </context-group>
</trans-unit> </trans-unit>
<trans-unit id="85780db87ac6c9f202615ac63754551c061e7236" datatype="html"> <trans-unit id="85780db87ac6c9f202615ac63754551c061e7236" datatype="html">
<source>Regions</source> <source>Regions</source>
<context-group purpose="location"> <context-group purpose="location">
<context context-type="sourcefile">apps/client/src/app/pages/portfolio/allocations/allocations-page.html</context> <context context-type="sourcefile">apps/client/src/app/pages/portfolio/allocations/allocations-page.html</context>
<context context-type="linenumber">230</context> <context context-type="linenumber">238</context>
</context-group> </context-group>
<context-group purpose="location"> <context-group purpose="location">
<context context-type="sourcefile">apps/client/src/app/pages/public/public-page.html</context> <context context-type="sourcefile">apps/client/src/app/pages/public/public-page.html</context>
@ -1531,21 +1547,21 @@
<source>Investment Timeline</source> <source>Investment Timeline</source>
<context-group purpose="location"> <context-group purpose="location">
<context context-type="sourcefile">apps/client/src/app/pages/portfolio/analysis/analysis-page.html</context> <context context-type="sourcefile">apps/client/src/app/pages/portfolio/analysis/analysis-page.html</context>
<context context-type="linenumber">105</context> <context context-type="linenumber">140</context>
</context-group> </context-group>
</trans-unit> </trans-unit>
<trans-unit id="6ae1c94f6bad274424f97e9bc8766242c1577447" datatype="html"> <trans-unit id="6ae1c94f6bad274424f97e9bc8766242c1577447" datatype="html">
<source>Top</source> <source>Top</source>
<context-group purpose="location"> <context-group purpose="location">
<context context-type="sourcefile">apps/client/src/app/pages/portfolio/analysis/analysis-page.html</context> <context context-type="sourcefile">apps/client/src/app/pages/portfolio/analysis/analysis-page.html</context>
<context context-type="linenumber">26</context> <context context-type="linenumber">33</context>
</context-group> </context-group>
</trans-unit> </trans-unit>
<trans-unit id="6723d5c967329a3ac75524cf0c1af5ced022b9a3" datatype="html"> <trans-unit id="6723d5c967329a3ac75524cf0c1af5ced022b9a3" datatype="html">
<source>Bottom</source> <source>Bottom</source>
<context-group purpose="location"> <context-group purpose="location">
<context context-type="sourcefile">apps/client/src/app/pages/portfolio/analysis/analysis-page.html</context> <context context-type="sourcefile">apps/client/src/app/pages/portfolio/analysis/analysis-page.html</context>
<context context-type="linenumber">62</context> <context context-type="linenumber">69</context>
</context-group> </context-group>
</trans-unit> </trans-unit>
<trans-unit id="5857197365507636437" datatype="html"> <trans-unit id="5857197365507636437" datatype="html">
@ -1739,7 +1755,7 @@
<source>Quantity</source> <source>Quantity</source>
<context-group purpose="location"> <context-group purpose="location">
<context context-type="sourcefile">apps/client/src/app/components/position/position-detail-dialog/position-detail-dialog.html</context> <context context-type="sourcefile">apps/client/src/app/components/position/position-detail-dialog/position-detail-dialog.html</context>
<context context-type="linenumber">107</context> <context context-type="linenumber">108</context>
</context-group> </context-group>
<context-group purpose="location"> <context-group purpose="location">
<context context-type="sourcefile">apps/client/src/app/pages/portfolio/transactions/create-or-update-transaction-dialog/create-or-update-transaction-dialog.html</context> <context context-type="sourcefile">apps/client/src/app/pages/portfolio/transactions/create-or-update-transaction-dialog/create-or-update-transaction-dialog.html</context>
@ -1787,7 +1803,7 @@
</context-group> </context-group>
<context-group purpose="location"> <context-group purpose="location">
<context context-type="sourcefile">apps/client/src/app/components/position/position-detail-dialog/position-detail-dialog.html</context> <context context-type="sourcefile">apps/client/src/app/components/position/position-detail-dialog/position-detail-dialog.html</context>
<context context-type="linenumber">145</context> <context context-type="linenumber">146</context>
</context-group> </context-group>
<context-group purpose="location"> <context-group purpose="location">
<context context-type="sourcefile">apps/client/src/app/pages/portfolio/transactions/create-or-update-transaction-dialog/create-or-update-transaction-dialog.html</context> <context context-type="sourcefile">apps/client/src/app/pages/portfolio/transactions/create-or-update-transaction-dialog/create-or-update-transaction-dialog.html</context>
@ -1826,7 +1842,7 @@
<source>Portfolio</source> <source>Portfolio</source>
<context-group purpose="location"> <context-group purpose="location">
<context context-type="sourcefile">apps/client/src/app/components/benchmark-comparator/benchmark-comparator.component.ts</context> <context context-type="sourcefile">apps/client/src/app/components/benchmark-comparator/benchmark-comparator.component.ts</context>
<context context-type="linenumber">111</context> <context context-type="linenumber">99</context>
</context-group> </context-group>
<context-group purpose="location"> <context-group purpose="location">
<context context-type="sourcefile">apps/client/src/app/pages/public/public-page-routing.module.ts</context> <context context-type="sourcefile">apps/client/src/app/pages/public/public-page-routing.module.ts</context>
@ -2031,13 +2047,9 @@
</trans-unit> </trans-unit>
<trans-unit id="313fcf0f8dac5ff5800a3e6bd67cb1955089ccca" datatype="html"> <trans-unit id="313fcf0f8dac5ff5800a3e6bd67cb1955089ccca" datatype="html">
<source>Beta</source> <source>Beta</source>
<context-group purpose="location">
<context context-type="sourcefile">apps/client/src/app/components/benchmark-comparator/benchmark-comparator.component.html</context>
<context context-type="linenumber">5</context>
</context-group>
<context-group purpose="location"> <context-group purpose="location">
<context context-type="sourcefile">apps/client/src/app/pages/account/account-page.html</context> <context context-type="sourcefile">apps/client/src/app/pages/account/account-page.html</context>
<context context-type="linenumber">116</context> <context context-type="linenumber">119</context>
</context-group> </context-group>
</trans-unit> </trans-unit>
<trans-unit id="c004f99bac91f7dc28e87d458f80e5035ae99884" datatype="html"> <trans-unit id="c004f99bac91f7dc28e87d458f80e5035ae99884" datatype="html">
@ -2051,14 +2063,14 @@
<source>Language</source> <source>Language</source>
<context-group purpose="location"> <context-group purpose="location">
<context context-type="sourcefile">apps/client/src/app/pages/account/account-page.html</context> <context context-type="sourcefile">apps/client/src/app/pages/account/account-page.html</context>
<context context-type="linenumber">115</context> <context context-type="linenumber">118</context>
</context-group> </context-group>
</trans-unit> </trans-unit>
<trans-unit id="22b6da584a3402f5d6bc028dcca0975ac27ad830" datatype="html"> <trans-unit id="22b6da584a3402f5d6bc028dcca0975ac27ad830" datatype="html">
<source>Data Management</source> <source>Data Management</source>
<context-group purpose="location"> <context-group purpose="location">
<context context-type="sourcefile">apps/client/src/app/components/admin-overview/admin-overview.html</context> <context context-type="sourcefile">apps/client/src/app/components/admin-overview/admin-overview.html</context>
<context context-type="linenumber">20</context> <context context-type="linenumber">31</context>
</context-group> </context-group>
</trans-unit> </trans-unit>
<trans-unit id="3ccabfc3dc288eaa2355ba43298c739f85951ec3" datatype="html"> <trans-unit id="3ccabfc3dc288eaa2355ba43298c739f85951ec3" datatype="html">
@ -2097,14 +2109,14 @@
<source>Change</source> <source>Change</source>
<context-group purpose="location"> <context-group purpose="location">
<context context-type="sourcefile">apps/client/src/app/components/position/position-detail-dialog/position-detail-dialog.html</context> <context context-type="sourcefile">apps/client/src/app/components/position/position-detail-dialog/position-detail-dialog.html</context>
<context context-type="linenumber">44</context> <context context-type="linenumber">45</context>
</context-group> </context-group>
</trans-unit> </trans-unit>
<trans-unit id="034c2b473d0b76acbc938453375b13cb2491dc17" datatype="html"> <trans-unit id="034c2b473d0b76acbc938453375b13cb2491dc17" datatype="html">
<source>Developed Markets</source> <source>Developed Markets</source>
<context-group purpose="location"> <context-group purpose="location">
<context context-type="sourcefile">apps/client/src/app/pages/portfolio/allocations/allocations-page.html</context> <context context-type="sourcefile">apps/client/src/app/pages/portfolio/allocations/allocations-page.html</context>
<context context-type="linenumber">256</context> <context context-type="linenumber">264</context>
</context-group> </context-group>
<context-group purpose="location"> <context-group purpose="location">
<context context-type="sourcefile">apps/client/src/app/pages/public/public-page.html</context> <context context-type="sourcefile">apps/client/src/app/pages/public/public-page.html</context>
@ -2119,7 +2131,7 @@
</context-group> </context-group>
<context-group purpose="location"> <context-group purpose="location">
<context context-type="sourcefile">apps/client/src/app/components/position/position-detail-dialog/position-detail-dialog.html</context> <context context-type="sourcefile">apps/client/src/app/components/position/position-detail-dialog/position-detail-dialog.html</context>
<context context-type="linenumber">154</context> <context context-type="linenumber">155</context>
</context-group> </context-group>
<context-group purpose="location"> <context-group purpose="location">
<context context-type="sourcefile">apps/client/src/app/pages/portfolio/transactions/create-or-update-transaction-dialog/create-or-update-transaction-dialog.html</context> <context context-type="sourcefile">apps/client/src/app/pages/portfolio/transactions/create-or-update-transaction-dialog/create-or-update-transaction-dialog.html</context>
@ -2130,21 +2142,21 @@
<source>Average Unit Price</source> <source>Average Unit Price</source>
<context-group purpose="location"> <context-group purpose="location">
<context context-type="sourcefile">apps/client/src/app/components/position/position-detail-dialog/position-detail-dialog.html</context> <context context-type="sourcefile">apps/client/src/app/components/position/position-detail-dialog/position-detail-dialog.html</context>
<context context-type="linenumber">65</context> <context context-type="linenumber">66</context>
</context-group> </context-group>
</trans-unit> </trans-unit>
<trans-unit id="6dd84054c52e1edf631f37accb054de0c4071069" datatype="html"> <trans-unit id="6dd84054c52e1edf631f37accb054de0c4071069" datatype="html">
<source>Maximum Price</source> <source>Maximum Price</source>
<context-group purpose="location"> <context-group purpose="location">
<context context-type="sourcefile">apps/client/src/app/components/position/position-detail-dialog/position-detail-dialog.html</context> <context context-type="sourcefile">apps/client/src/app/components/position/position-detail-dialog/position-detail-dialog.html</context>
<context context-type="linenumber">97</context> <context context-type="linenumber">98</context>
</context-group> </context-group>
</trans-unit> </trans-unit>
<trans-unit id="7233cd3a1ef8913fa5c6db7a29c88044646ceacc" datatype="html"> <trans-unit id="7233cd3a1ef8913fa5c6db7a29c88044646ceacc" datatype="html">
<source>Other Markets</source> <source>Other Markets</source>
<context-group purpose="location"> <context-group purpose="location">
<context context-type="sourcefile">apps/client/src/app/pages/portfolio/allocations/allocations-page.html</context> <context context-type="sourcefile">apps/client/src/app/pages/portfolio/allocations/allocations-page.html</context>
<context context-type="linenumber">274</context> <context context-type="linenumber">282</context>
</context-group> </context-group>
<context-group purpose="location"> <context-group purpose="location">
<context context-type="sourcefile">apps/client/src/app/pages/public/public-page.html</context> <context context-type="sourcefile">apps/client/src/app/pages/public/public-page.html</context>
@ -2155,7 +2167,7 @@
<source>Emerging Markets</source> <source>Emerging Markets</source>
<context-group purpose="location"> <context-group purpose="location">
<context context-type="sourcefile">apps/client/src/app/pages/portfolio/allocations/allocations-page.html</context> <context context-type="sourcefile">apps/client/src/app/pages/portfolio/allocations/allocations-page.html</context>
<context context-type="linenumber">265</context> <context context-type="linenumber">273</context>
</context-group> </context-group>
<context-group purpose="location"> <context-group purpose="location">
<context context-type="sourcefile">apps/client/src/app/pages/public/public-page.html</context> <context context-type="sourcefile">apps/client/src/app/pages/public/public-page.html</context>
@ -2166,35 +2178,35 @@
<source>Sector</source> <source>Sector</source>
<context-group purpose="location"> <context-group purpose="location">
<context context-type="sourcefile">apps/client/src/app/components/position/position-detail-dialog/position-detail-dialog.html</context> <context context-type="sourcefile">apps/client/src/app/components/position/position-detail-dialog/position-detail-dialog.html</context>
<context context-type="linenumber">169</context> <context context-type="linenumber">170</context>
</context-group> </context-group>
</trans-unit> </trans-unit>
<trans-unit id="a43f25a9ac40e8e2441ff0be7a36b8e5d15534df" datatype="html"> <trans-unit id="a43f25a9ac40e8e2441ff0be7a36b8e5d15534df" datatype="html">
<source>Country</source> <source>Country</source>
<context-group purpose="location"> <context-group purpose="location">
<context context-type="sourcefile">apps/client/src/app/components/position/position-detail-dialog/position-detail-dialog.html</context> <context context-type="sourcefile">apps/client/src/app/components/position/position-detail-dialog/position-detail-dialog.html</context>
<context context-type="linenumber">181</context> <context context-type="linenumber">182</context>
</context-group> </context-group>
</trans-unit> </trans-unit>
<trans-unit id="b1c1c6a43da1ad3e41b7a6e3aa5dcc24226cf580" datatype="html"> <trans-unit id="b1c1c6a43da1ad3e41b7a6e3aa5dcc24226cf580" datatype="html">
<source>Minimum Price</source> <source>Minimum Price</source>
<context-group purpose="location"> <context-group purpose="location">
<context context-type="sourcefile">apps/client/src/app/components/position/position-detail-dialog/position-detail-dialog.html</context> <context context-type="sourcefile">apps/client/src/app/components/position/position-detail-dialog/position-detail-dialog.html</context>
<context context-type="linenumber">86</context> <context context-type="linenumber">87</context>
</context-group> </context-group>
</trans-unit> </trans-unit>
<trans-unit id="bf3df1f4eb29a071630eed167406c06f974480b2" datatype="html"> <trans-unit id="bf3df1f4eb29a071630eed167406c06f974480b2" datatype="html">
<source>First Buy Date</source> <source>First Buy Date</source>
<context-group purpose="location"> <context-group purpose="location">
<context context-type="sourcefile">apps/client/src/app/components/position/position-detail-dialog/position-detail-dialog.html</context> <context context-type="sourcefile">apps/client/src/app/components/position/position-detail-dialog/position-detail-dialog.html</context>
<context context-type="linenumber">127</context> <context context-type="linenumber">128</context>
</context-group> </context-group>
</trans-unit> </trans-unit>
<trans-unit id="add4cd82e3e38a3110fe67b3c7df56e9602644ee" datatype="html"> <trans-unit id="add4cd82e3e38a3110fe67b3c7df56e9602644ee" datatype="html">
<source>Transactions</source> <source>Transactions</source>
<context-group purpose="location"> <context-group purpose="location">
<context context-type="sourcefile">apps/client/src/app/components/position/position-detail-dialog/position-detail-dialog.html</context> <context context-type="sourcefile">apps/client/src/app/components/position/position-detail-dialog/position-detail-dialog.html</context>
<context context-type="linenumber">136</context> <context context-type="linenumber">137</context>
</context-group> </context-group>
</trans-unit> </trans-unit>
<trans-unit id="e34e2478d2d30c9d01758d01b7212411171b9bd5" datatype="html"> <trans-unit id="e34e2478d2d30c9d01758d01b7212411171b9bd5" datatype="html">
@ -2208,14 +2220,7 @@
<source>Savings</source> <source>Savings</source>
<context-group purpose="location"> <context-group purpose="location">
<context context-type="sourcefile">libs/ui/src/lib/fire-calculator/fire-calculator.component.ts</context> <context context-type="sourcefile">libs/ui/src/lib/fire-calculator/fire-calculator.component.ts</context>
<context context-type="linenumber">296</context> <context context-type="linenumber">299</context>
</context-group>
</trans-unit>
<trans-unit id="1975246224413290232" datatype="html">
<source>Accumulating</source>
<context-group purpose="location">
<context context-type="sourcefile">apps/client/src/app/pages/portfolio/analysis/analysis-page.component.ts</context>
<context context-type="linenumber">39</context>
</context-group> </context-group>
</trans-unit> </trans-unit>
<trans-unit id="2937311350146031865" datatype="html"> <trans-unit id="2937311350146031865" datatype="html">
@ -2229,18 +2234,18 @@
<source>Interest</source> <source>Interest</source>
<context-group purpose="location"> <context-group purpose="location">
<context context-type="sourcefile">libs/ui/src/lib/fire-calculator/fire-calculator.component.ts</context> <context context-type="sourcefile">libs/ui/src/lib/fire-calculator/fire-calculator.component.ts</context>
<context context-type="linenumber">286</context> <context context-type="linenumber">289</context>
</context-group> </context-group>
</trans-unit> </trans-unit>
<trans-unit id="5213771062241898526" datatype="html"> <trans-unit id="5213771062241898526" datatype="html">
<source>Deposit</source> <source>Deposit</source>
<context-group purpose="location"> <context-group purpose="location">
<context context-type="sourcefile">apps/client/src/app/components/investment-chart/investment-chart.component.ts</context> <context context-type="sourcefile">apps/client/src/app/components/investment-chart/investment-chart.component.ts</context>
<context context-type="linenumber">132</context> <context context-type="linenumber">139</context>
</context-group> </context-group>
<context-group purpose="location"> <context-group purpose="location">
<context context-type="sourcefile">libs/ui/src/lib/fire-calculator/fire-calculator.component.ts</context> <context context-type="sourcefile">libs/ui/src/lib/fire-calculator/fire-calculator.component.ts</context>
<context context-type="linenumber">276</context> <context context-type="linenumber">279</context>
</context-group> </context-group>
</trans-unit> </trans-unit>
<trans-unit id="6603000223840533819" datatype="html"> <trans-unit id="6603000223840533819" datatype="html">
@ -2254,7 +2259,7 @@
<source>Monthly</source> <source>Monthly</source>
<context-group purpose="location"> <context-group purpose="location">
<context context-type="sourcefile">apps/client/src/app/pages/portfolio/analysis/analysis-page.component.ts</context> <context context-type="sourcefile">apps/client/src/app/pages/portfolio/analysis/analysis-page.component.ts</context>
<context context-type="linenumber">38</context> <context context-type="linenumber">40</context>
</context-group> </context-group>
</trans-unit> </trans-unit>
<trans-unit id="8511b16abcf065252b350d64e337ba2447db3ffb" datatype="html"> <trans-unit id="8511b16abcf065252b350d64e337ba2447db3ffb" datatype="html">
@ -2332,35 +2337,105 @@
<source>Experimental Features</source> <source>Experimental Features</source>
<context-group purpose="location"> <context-group purpose="location">
<context context-type="sourcefile">apps/client/src/app/pages/account/account-page.html</context> <context context-type="sourcefile">apps/client/src/app/pages/account/account-page.html</context>
<context context-type="linenumber">196</context> <context context-type="linenumber">235</context>
</context-group> </context-group>
</trans-unit> </trans-unit>
<trans-unit id="1931353503905413384" datatype="html"> <trans-unit id="1931353503905413384" datatype="html">
<source>Benchmark</source> <source>Benchmark</source>
<context-group purpose="location"> <context-group purpose="location">
<context context-type="sourcefile">apps/client/src/app/components/benchmark-comparator/benchmark-comparator.component.ts</context> <context context-type="sourcefile">apps/client/src/app/components/benchmark-comparator/benchmark-comparator.component.ts</context>
<context context-type="linenumber">120</context> <context context-type="linenumber">108</context>
</context-group> </context-group>
</trans-unit> </trans-unit>
<trans-unit id="1b25c6e22f822e07a3e4d5aae4edc5b41fe083c2" datatype="html"> <trans-unit id="1b25c6e22f822e07a3e4d5aae4edc5b41fe083c2" datatype="html">
<source>Benchmarks</source> <source>Benchmarks</source>
<context-group purpose="location"> <context-group purpose="location">
<context context-type="sourcefile">apps/client/src/app/components/benchmark-comparator/benchmark-comparator.component.html</context> <context context-type="sourcefile">apps/client/src/app/components/admin-overview/admin-overview.html</context>
<context context-type="linenumber">4</context> <context context-type="linenumber">123</context>
</context-group> </context-group>
</trans-unit> </trans-unit>
<trans-unit id="44fcf77e86dc038202ebad6b46d1d833d60d781b" datatype="html"> <trans-unit id="44fcf77e86dc038202ebad6b46d1d833d60d781b" datatype="html">
<source>Compare with...</source> <source>Compare with...</source>
<context-group purpose="location"> <context-group purpose="location">
<context context-type="sourcefile">apps/client/src/app/components/benchmark-comparator/benchmark-comparator.component.html</context> <context context-type="sourcefile">apps/client/src/app/components/benchmark-comparator/benchmark-comparator.component.html</context>
<context context-type="linenumber">14</context> <context context-type="linenumber">18</context>
</context-group> </context-group>
</trans-unit> </trans-unit>
<trans-unit id="4210837540bca56dca96fcc451518659d06ad02a" datatype="html"> <trans-unit id="4210837540bca56dca96fcc451518659d06ad02a" datatype="html">
<source>Proportion of Net Worth</source> <source>Proportion of Net Worth</source>
<context-group purpose="location"> <context-group purpose="location">
<context context-type="sourcefile">apps/client/src/app/pages/portfolio/allocations/allocations-page.html</context> <context context-type="sourcefile">apps/client/src/app/pages/portfolio/allocations/allocations-page.html</context>
<context context-type="linenumber">17</context> <context context-type="linenumber">18</context>
</context-group>
</trans-unit>
<trans-unit id="98fc3013bfcbf452b9f37bbfcdb77b9b882866e3" datatype="html">
<source>Excluded from Analysis</source>
<context-group purpose="location">
<context context-type="sourcefile">apps/client/src/app/components/portfolio-summary/portfolio-summary.component.html</context>
<context context-type="linenumber">176</context>
</context-group>
</trans-unit>
<trans-unit id="ac598d664f86ba5783915d65f2664a7f38a9d23a" datatype="html">
<source>Account Type</source>
<context-group purpose="location">
<context context-type="sourcefile">apps/client/src/app/components/account-detail-dialog/account-detail-dialog.html</context>
<context context-type="linenumber">25</context>
</context-group>
</trans-unit>
<trans-unit id="458363f8e413759aa9e3235a53fd0f64cc916395" datatype="html">
<source> If you retire today, you would be able to withdraw <x id="START_TAG_SPAN" ctype="x-span" equiv-text="&lt;span class=&quot;font-weight-bold&quot; &gt;"/><x id="START_TAG_GF_VALUE" ctype="x-gf_value" equiv-text="&lt;gf-value class=&quot;d-inline-block&quot; [currency]=&quot;user?.settings?.baseCurrency&quot; [locale]=&quot;user?.settings?.locale&quot; [value]=&quot;withdrawalRatePerYear?.toNumber()&quot; &gt;"/><x id="CLOSE_TAG_GF_VALUE" ctype="x-gf_value" equiv-text="&lt;/gf-value&gt;"/> per year<x id="CLOSE_TAG_SPAN" ctype="x-span" equiv-text="&lt;/span &gt;"/> or <x id="START_TAG_SPAN" ctype="x-span" equiv-text="&lt;span class=&quot;font-weight-bold&quot; &gt;"/><x id="START_TAG_GF_VALUE_1" ctype="x-gf_value_1" equiv-text="&lt;gf-value class=&quot;d-inline-block&quot; [currency]=&quot;user?.settings?.baseCurrency&quot; [locale]=&quot;user?.settings?.locale&quot; [value]=&quot;withdrawalRatePerMonth?.toNumber()&quot; &gt;"/><x id="CLOSE_TAG_GF_VALUE" ctype="x-gf_value" equiv-text="&lt;/gf-value&gt;"/> per month<x id="CLOSE_TAG_SPAN" ctype="x-span" equiv-text="&lt;/span &gt;"/>, based on your total assets of <x id="START_TAG_GF_VALUE_2" ctype="x-gf_value_2" equiv-text="&lt;gf-value class=&quot;d-inline-block&quot; [currency]=&quot;user?.settings?.baseCurrency&quot; [locale]=&quot;user?.settings?.locale&quot; [value]=&quot;fireWealth?.toNumber()&quot; &gt;"/><x id="CLOSE_TAG_GF_VALUE" ctype="x-gf_value" equiv-text="&lt;/gf-value&gt;"/> and a withdrawal rate of 4%. </source>
<context-group purpose="location">
<context context-type="sourcefile">apps/client/src/app/pages/portfolio/fire/fire-page.html</context>
<context context-type="linenumber">38,66</context>
</context-group>
</trans-unit>
<trans-unit id="5fb13fb4a8447e59cdf05dc196ade39c02a6f8aa" datatype="html">
<source>Auto</source>
<context-group purpose="location">
<context context-type="sourcefile">apps/client/src/app/pages/account/account-page.html</context>
<context context-type="linenumber">210</context>
</context-group>
</trans-unit>
<trans-unit id="616064537937996961" datatype="html">
<source>Auto</source>
<context-group purpose="location">
<context context-type="sourcefile">apps/client/src/app/pages/account/account-page.component.ts</context>
<context context-type="linenumber">45</context>
</context-group>
</trans-unit>
<trans-unit id="693d14f486a25e86bc515dfcfc4462d5201217ef" datatype="html">
<source>Light</source>
<context-group purpose="location">
<context context-type="sourcefile">apps/client/src/app/pages/account/account-page.html</context>
<context context-type="linenumber">211</context>
</context-group>
</trans-unit>
<trans-unit id="adb4562d2dbd3584370e44496969d58c511ecb63" datatype="html">
<source>Dark</source>
<context-group purpose="location">
<context context-type="sourcefile">apps/client/src/app/pages/account/account-page.html</context>
<context context-type="linenumber">212</context>
</context-group>
</trans-unit>
<trans-unit id="bbe41ac2ea4a6c00ea941a41b33105048f8e9f13" datatype="html">
<source>Appearance</source>
<context-group purpose="location">
<context context-type="sourcefile">apps/client/src/app/pages/account/account-page.html</context>
<context context-type="linenumber">195</context>
</context-group>
</trans-unit>
<trans-unit id="112783260724635106" datatype="html">
<source>Total Amount</source>
<context-group purpose="location">
<context context-type="sourcefile">apps/client/src/app/components/investment-chart/investment-chart.component.ts</context>
<context context-type="linenumber">160</context>
</context-group>
</trans-unit>
<trans-unit id="f1a355a1af2e818050a3af693ac8b521fa7edc5f" datatype="html">
<source>Portfolio Evolution</source>
<context-group purpose="location">
<context context-type="sourcefile">apps/client/src/app/pages/portfolio/analysis/analysis-page.html</context>
<context context-type="linenumber">112</context>
</context-group> </context-group>
</trans-unit> </trans-unit>
</body> </body>

View File

@ -18,6 +18,7 @@ $mat-css-light-theme-selector: '.is-light-theme';
:root { :root {
--dark-background: rgb(39, 39, 39); --dark-background: rgb(39, 39, 39);
--font-family-sans-serif: Roboto, 'Helvetica Neue', sans-serif;
--light-background: rgb(255, 255, 255); --light-background: rgb(255, 255, 255);
} }
@ -96,6 +97,12 @@ body {
color: rgba(var(--light-primary-text)); color: rgba(var(--light-primary-text));
} }
} }
.with-placeholder-as-option {
.mat-select-placeholder {
color: rgba(var(--light-primary-text));
}
}
} }
} }
@ -146,11 +153,8 @@ ngx-skeleton-loader {
@include gf-table; @include gf-table;
} }
.mat-fab, .lead {
.mat-flat-button { font-weight: unset;
&.mat-primary {
color: rgba(var(--light-primary-text)) !important;
}
} }
.mat-card { .mat-card {
@ -164,6 +168,49 @@ ngx-skeleton-loader {
margin: 0 !important; margin: 0 !important;
} }
.mat-fab,
.mat-flat-button {
&.mat-primary {
color: rgba(var(--light-primary-text)) !important;
}
}
.mat-form-field {
&.compact-with-outline {
.mat-form-field-wrapper {
margin: 0.5rem 0 0.25rem;
padding-bottom: 1rem;
.mat-form-field-infix {
border-top-width: 0;
padding: 1rem 0 0.75rem;
.mat-form-field-label {
margin-top: 0.1rem;
}
.mat-select-arrow-wrapper {
transform: none;
}
}
.mat-form-field-prefix {
top: 0;
}
.mat-form-field-suffix {
top: 0;
}
}
}
&.without-hint {
.mat-form-field-wrapper {
padding-bottom: 0;
}
}
}
.no-min-width { .no-min-width {
min-width: unset !important; min-width: unset !important;
} }
@ -187,3 +234,9 @@ ngx-skeleton-loader {
.with-info-message { .with-info-message {
height: calc(100vh - 5rem - 3.5rem) !important; height: calc(100vh - 5rem - 3.5rem) !important;
} }
.with-placeholder-as-option {
.mat-select-placeholder {
color: rgba(var(--dark-primary-text));
}
}

View File

@ -6,7 +6,7 @@ export const DEMO_USER_ID = '9b112b4d-3b7d-4bad-9bdd-3b0f7b4dac2f';
export const ghostfolioScraperApiSymbolPrefix = '_GF_'; export const ghostfolioScraperApiSymbolPrefix = '_GF_';
export const ghostfolioCashSymbol = `${ghostfolioScraperApiSymbolPrefix}CASH`; export const ghostfolioCashSymbol = `${ghostfolioScraperApiSymbolPrefix}CASH`;
export const ghostfolioFearAndGreedIndexDataSource = DataSource.RAKUTEN; export const ghostfolioFearAndGreedIndexDataSource = DataSource.RAPID_API;
export const ghostfolioFearAndGreedIndexSymbol = `${ghostfolioScraperApiSymbolPrefix}FEAR_AND_GREED_INDEX`; export const ghostfolioFearAndGreedIndexSymbol = `${ghostfolioScraperApiSymbolPrefix}FEAR_AND_GREED_INDEX`;
export const locale = 'en-US'; export const locale = 'en-US';

View File

@ -1,7 +1,7 @@
import * as currencies from '@dinero.js/currencies'; import * as currencies from '@dinero.js/currencies';
import { DataSource } from '@prisma/client'; import { DataSource } from '@prisma/client';
import { getDate, getMonth, getYear, parse, subDays } from 'date-fns'; import { getDate, getMonth, getYear, parse, subDays } from 'date-fns';
import { de } from 'date-fns/locale'; import { de, es, it, nl } from 'date-fns/locale';
import { ghostfolioScraperApiSymbolPrefix, locale } from './config'; import { ghostfolioScraperApiSymbolPrefix, locale } from './config';
import { Benchmark } from './interfaces'; import { Benchmark } from './interfaces';
@ -75,6 +75,12 @@ export function getCssVariable(aCssVariable: string) {
export function getDateFnsLocale(aLanguageCode: string) { export function getDateFnsLocale(aLanguageCode: string) {
if (aLanguageCode === 'de') { if (aLanguageCode === 'de') {
return de; return de;
} else if (aLanguageCode === 'es') {
return es;
} else if (aLanguageCode === 'it') {
return it;
} else if (aLanguageCode === 'nl') {
return nl;
} }
return undefined; return undefined;

View File

@ -2,5 +2,8 @@ export interface HistoricalDataItem {
averagePrice?: number; averagePrice?: number;
date: string; date: string;
grossPerformancePercent?: number; grossPerformancePercent?: number;
value: number; netPerformance?: number;
netPerformanceInPercentage?: number;
totalInvestment?: number;
value?: number;
} }

View File

@ -1,4 +1,7 @@
import { PortfolioPosition } from '@ghostfolio/common/interfaces'; import {
PortfolioPosition,
PortfolioSummary
} from '@ghostfolio/common/interfaces';
export interface PortfolioDetails { export interface PortfolioDetails {
accounts: { accounts: {
@ -13,5 +16,6 @@ export interface PortfolioDetails {
filteredValueInBaseCurrency?: number; filteredValueInBaseCurrency?: number;
filteredValueInPercentage: number; filteredValueInPercentage: number;
holdings: { [symbol: string]: PortfolioPosition }; holdings: { [symbol: string]: PortfolioPosition };
summary: PortfolioSummary;
totalValueInBaseCurrency?: number; totalValueInBaseCurrency?: number;
} }

View File

@ -1,6 +1,5 @@
import { InvestmentItem } from './investment-item.interface'; import { InvestmentItem } from './investment-item.interface';
export interface PortfolioInvestments { export interface PortfolioInvestments {
firstOrderDate: Date;
investments: InvestmentItem[]; investments: InvestmentItem[];
} }

View File

@ -5,4 +5,5 @@ export interface PortfolioPerformance {
currentNetPerformance: number; currentNetPerformance: number;
currentNetPerformancePercent: number; currentNetPerformancePercent: number;
currentValue: number; currentValue: number;
totalInvestment: number;
} }

View File

@ -3,9 +3,10 @@ import { PortfolioPerformance } from './portfolio-performance.interface';
export interface PortfolioSummary extends PortfolioPerformance { export interface PortfolioSummary extends PortfolioPerformance {
annualizedPerformancePercent: number; annualizedPerformancePercent: number;
cash: number; cash: number;
dividend: number;
committedFunds: number; committedFunds: number;
dividend: number;
emergencyFund: number; emergencyFund: number;
excludedAccountsAndActivities: number;
fees: number; fees: number;
firstOrderDate: Date; firstOrderDate: Date;
items: number; items: number;

Some files were not shown because too many files have changed in this diff Show More