Compare commits

...

5 Commits

19 changed files with 297 additions and 154 deletions

View File

@ -5,14 +5,28 @@ All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
## 0.91.0 - 25.04.2021
### Added
- Extended the support for feature flags to simplify the initial project setup
- Prepared for multi accounts support
### Changed
- Improved the styling of the rules in the _X-ray_ section
## 0.90.0 - 22.04.2021
### Added
- Improved the user table of the admin control panel
- Added the symbol logo to the position detail dialog
- Introduced a third option for the market state: `delayed` (besides `open` and `closed`)
### Changed
- Improved the user table of the admin control panel
## 0.89.0 - 21.04.2021
### Added
@ -92,7 +106,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
### Fixed
- Fixed an issue in the portfolio update on deleting a transaction
- Fixed an issue in the _X-Ray_ section (missing redirection on logout)
- Fixed an issue in the _X-ray_ section (missing redirection on logout)
## 0.82.0 - 10.04.2021
@ -165,7 +179,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
### Changed
- Grouped the _X-Ray_ section visually in _Currency Cluster Risk_ and _Platform Cluster Risk_
- Grouped the _X-ray_ section visually in _Currency Cluster Risk_ and _Platform Cluster Risk_
## 0.76.0 - 02.04.2021
@ -177,7 +191,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
### Fixed
- Fixed an issue in the _X-Ray_ section (empty portfolio)
- Fixed an issue in the _X-ray_ section (empty portfolio)
## 0.75.0 - 01.04.2021
@ -190,7 +204,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
### Added
- Added a _Create Account_ message in the _Live Demo_
- Added skeleton loaders to the _X-Ray_ section
- Added skeleton loaders to the _X-ray_ section
### Changed
@ -206,7 +220,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
### Changed
- Improved the intro text in the _X-Ray_ section
- Improved the intro text in the _X-ray_ section
### Fixed
@ -222,7 +236,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
### Added
- Added an intro text to the _X-Ray_ section
- Added an intro text to the _X-ray_ section
### Changed
@ -241,7 +255,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
### Changed
- Improved the styling in the _X-Ray_ section
- Improved the styling in the _X-ray_ section
## 0.70.0 - 27.03.2021
@ -249,7 +263,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- Added the current _Fear & Greed Index_ as text
- Extended the landing page text: _Ghostfolio_ empowers busy folks...
- Added the first static portfolio analysis rule in the brand new _X-Ray_ section
- Added the first static portfolio analysis rule in the brand new _X-ray_ section
### Changed

View File

@ -26,7 +26,11 @@ export class InfoService {
const globalPermissions: string[] = [];
if (this.configurationService.get('ENABLE_FEATURE_SOCIAL_LOGIN')) {
globalPermissions.push(permissions.useSocialLogin);
globalPermissions.push(permissions.enableSocialLogin);
}
if (this.configurationService.get('ENABLE_FEATURE_SUBSCRIPTION')) {
globalPermissions.push(permissions.enableSubscription);
}
return {

View File

@ -116,7 +116,15 @@ export class UserService {
public async createUser(data?: Prisma.UserCreateInput): Promise<User> {
let user = await this.prisma.user.create({
data
data: {
...data,
Account: {
create: {
isDefault: true,
name: 'Default Account'
}
}
}
});
if (data.provider === Provider.ANONYMOUS) {

View File

@ -15,6 +15,7 @@ export class ConfigurationService {
ENABLE_FEATURE_CUSTOM_SYMBOLS: bool({ default: false }),
ENABLE_FEATURE_FEAR_AND_GREED_INDEX: bool({ default: false }),
ENABLE_FEATURE_SOCIAL_LOGIN: bool({ default: false }),
ENABLE_FEATURE_SUBSCRIPTION: bool({ default: false }),
GOOGLE_CLIENT_ID: str({ default: 'dummyClientId' }),
GOOGLE_SECRET: str({ default: 'dummySecret' }),
JWT_SECRET_KEY: str({}),

View File

@ -15,6 +15,8 @@ import { PrismaService } from '../../prisma.service';
@Injectable()
export class GhostfolioScraperApiService implements DataProviderInterface {
private static NUMERIC_REGEXP = /[-]{0,1}[\d]*[.,]{0,1}[\d]+/g;
public constructor(private prisma: PrismaService) {}
public async get(
@ -75,12 +77,9 @@ export class GhostfolioScraperApiService implements DataProviderInterface {
const html = await get();
const $ = cheerio.load(html);
const string = $(scraperConfig?.selector)
.text()
.replace('CHF', '')
.trim();
const value = parseFloat(string);
const value = this.extractNumberFromString(
$(scraperConfig?.selector).text()
);
return {
[symbol]: {
@ -96,6 +95,17 @@ export class GhostfolioScraperApiService implements DataProviderInterface {
return {};
}
private extractNumberFromString(aString: string): number {
try {
const [numberString] = aString.match(
GhostfolioScraperApiService.NUMERIC_REGEXP
);
return parseFloat(numberString.trim());
} catch {
return undefined;
}
}
private async getScraperConfig(aSymbol: string) {
try {
const {

View File

@ -7,6 +7,7 @@ export interface Environment extends CleanedEnvAccessors {
ENABLE_FEATURE_CUSTOM_SYMBOLS: boolean;
ENABLE_FEATURE_FEAR_AND_GREED_INDEX: boolean;
ENABLE_FEATURE_SOCIAL_LOGIN: boolean;
ENABLE_FEATURE_SUBSCRIPTION: boolean;
GOOGLE_CLIENT_ID: string;
GOOGLE_SECRET: string;
JWT_SECRET_KEY: string;

View File

@ -2,7 +2,8 @@ import { Injectable, OnModuleDestroy, OnModuleInit } from '@nestjs/common';
import { PrismaClient } from '@prisma/client';
@Injectable()
export class PrismaService extends PrismaClient
export class PrismaService
extends PrismaClient
implements OnModuleInit, OnModuleDestroy {
async onModuleInit() {
await this.$connect();

View File

@ -37,7 +37,7 @@
>Transactions</a
>
<a
*ngIf="canAccessAdminAccessControl"
*ngIf="hasPermissionToAccessAdminControl"
class="d-none d-sm-block mx-1"
[routerLink]="['/admin']"
i18n
@ -150,7 +150,7 @@
>Account</a
>
<a
*ngIf="canAccessAdminAccessControl"
*ngIf="hasPermissionForAdminControl"
class="d-block d-sm-none"
[routerLink]="['/admin']"
i18n

View File

@ -27,8 +27,8 @@ export class HeaderComponent implements OnChanges {
@Input() info: InfoItem;
@Input() user: User;
public canAccessAdminAccessControl: boolean;
public hasPermissionToUseSocialLogin: boolean;
public hasPermissionToAccessAdminControl: boolean;
public hasPermissionForSocialLogin: boolean;
public impersonationId: string;
private unsubscribeSubject = new Subject<void>();
@ -49,15 +49,15 @@ export class HeaderComponent implements OnChanges {
public ngOnChanges() {
if (this.user) {
this.canAccessAdminAccessControl = hasPermission(
this.hasPermissionToAccessAdminControl = hasPermission(
this.user.permissions,
permissions.accessAdminControl
);
}
this.hasPermissionToUseSocialLogin = hasPermission(
this.hasPermissionForSocialLogin = hasPermission(
this.info?.globalPermissions,
permissions.useSocialLogin
permissions.enableSocialLogin
);
}
@ -81,7 +81,7 @@ export class HeaderComponent implements OnChanges {
autoFocus: false,
data: {
accessToken: '',
hasPermissionToUseSocialLogin: this.hasPermissionToUseSocialLogin
hasPermissionToUseSocialLogin: this.hasPermissionForSocialLogin
},
width: '30rem'
});

View File

@ -1,18 +1,18 @@
<div class="py-3">
<div class="flex-nowrap no-gutters row">
<div class="align-items-center flex-nowrap no-gutters row">
<div *ngIf="isLoading">
<ngx-skeleton-loader
animation="pulse"
class="mr-3"
class="mr-2"
[theme]="{
height: '3rem',
width: '3rem'
height: '2rem',
width: '2rem'
}"
></ngx-skeleton-loader>
</div>
<div
*ngIf="!isLoading"
class="align-items-center d-flex icon-container mr-3 px-3"
class="align-items-center d-flex icon-container mr-2 px-2"
[ngClass]="{ okay: rule?.value === true, warn: rule?.value === false }"
>
<ion-icon

View File

@ -2,15 +2,16 @@
display: block;
.icon-container {
background-color: rgba(var(--dark-primary-text), 0.05);
border-radius: 0.25rem;
height: 3rem;
height: 2rem;
&.okay {
background-color: var(--success);
color: var(--success);
}
&.warn {
background-color: var(--warning);
color: var(--danger);
}
}
@ -21,6 +22,6 @@
:host-context(.is-dark-theme) {
.icon-container {
color: rgba(var(--dark-primary-text));
background-color: rgba(var(--light-primary-text), 0.05);
}
}

View File

@ -1,8 +1,9 @@
import { ChangeDetectorRef, Component, OnInit } from '@angular/core';
import { InfoItem } from '@ghostfolio/api/app/info/interfaces/info-item.interface';
import { User } from '@ghostfolio/api/app/user/interfaces/user.interface';
import { DataService } from '@ghostfolio/client/services/data.service';
import { TokenStorageService } from '@ghostfolio/client/services/token-storage.service';
import { baseCurrency } from '@ghostfolio/helper';
import { baseCurrency, hasPermission, permissions } from '@ghostfolio/helper';
import { Subject } from 'rxjs';
import { takeUntil } from 'rxjs/operators';
@ -15,6 +16,7 @@ import { environment } from '../../../environments/environment';
})
export class AboutPageComponent implements OnInit {
public baseCurrency = baseCurrency;
public hasPermissionForSubscription: boolean;
public isLoggedIn: boolean;
public lastPublish = environment.lastPublish;
public user: User;
@ -35,6 +37,13 @@ export class AboutPageComponent implements OnInit {
* Initializes the controller
*/
public ngOnInit() {
this.dataService.fetchInfo().subscribe((info) => {
this.hasPermissionForSubscription = hasPermission(
info.globalPermissions,
permissions.enableSubscription
);
});
this.isLoggedIn = !!this.tokenStorageService.getToken();
if (this.isLoggedIn)

View File

@ -61,7 +61,7 @@
</div>
</div>
<div class="mb-5 row">
<div *ngIf="hasPermissionForSubscription" class="mb-5 row">
<div class="col">
<h3 class="mb-3 text-center" i18n>Pricing Plans</h3>
<div class="row">

View File

@ -3,7 +3,11 @@ import { Access } from '@ghostfolio/api/app/access/interfaces/access.interface';
import { User } from '@ghostfolio/api/app/user/interfaces/user.interface';
import { DataService } from '@ghostfolio/client/services/data.service';
import { TokenStorageService } from '@ghostfolio/client/services/token-storage.service';
import { DEFAULT_DATE_FORMAT } from '@ghostfolio/helper';
import {
DEFAULT_DATE_FORMAT,
hasPermission,
permissions
} from '@ghostfolio/helper';
import { Subject } from 'rxjs';
import { takeUntil } from 'rxjs/operators';
@ -19,6 +23,7 @@ export class AccountPageComponent implements OnDestroy, OnInit {
public baseCurrency: Currency;
public currencies: Currency[] = [];
public defaultDateFormat = DEFAULT_DATE_FORMAT;
public hasPermissionForSubscription: boolean;
public user: User;
private unsubscribeSubject = new Subject<void>();
@ -34,8 +39,13 @@ export class AccountPageComponent implements OnDestroy, OnInit {
this.dataService
.fetchInfo()
.pipe(takeUntil(this.unsubscribeSubject))
.subscribe(({ currencies }) => {
.subscribe(({ currencies, globalPermissions }) => {
this.currencies = currencies;
this.hasPermissionForSubscription = hasPermission(
globalPermissions,
permissions.enableSubscription
);
});
this.tokenStorageService
@ -70,6 +80,11 @@ export class AccountPageComponent implements OnDestroy, OnInit {
});
}
public ngOnDestroy() {
this.unsubscribeSubject.next();
this.unsubscribeSubject.complete();
}
private update() {
this.dataService
.fetchAccesses()
@ -80,9 +95,4 @@ export class AccountPageComponent implements OnDestroy, OnInit {
this.cd.markForCheck();
});
}
public ngOnDestroy() {
this.unsubscribeSubject.next();
this.unsubscribeSubject.complete();
}
}

View File

@ -15,7 +15,7 @@
<div class="w-50" i18n>Alias</div>
<div class="w-50">{{ user.alias }}</div>
</div>
<div class="d-flex py-1">
<div *ngIf="hasPermissionForSubscription" class="d-flex py-1">
<div class="w-50" i18n>Membership</div>
<div class="w-50">
<div class="align-items-center d-flex mb-1">

View File

@ -10,10 +10,11 @@ export const permissions = {
createAccount: 'createAccount',
createOrder: 'createOrder',
deleteOrder: 'deleteOrder',
enableSocialLogin: 'enableSocialLogin',
enableSubscription: 'enableSubscription',
readForeignPortfolio: 'readForeignPortfolio',
updateOrder: 'updateOrder',
updateUserSettings: 'updateUserSettings',
useSocialLogin: 'useSocialLogin'
updateUserSettings: 'updateUserSettings'
};
export function hasPermission(

View File

@ -1,6 +1,6 @@
{
"name": "ghostfolio",
"version": "0.90.0",
"version": "0.91.0",
"homepage": "https://ghostfol.io",
"license": "AGPL-3.0",
"scripts": {
@ -15,6 +15,9 @@
"angular": "node --max_old_space_size=32768 ./node_modules/@angular/cli/bin/ng",
"build:all": "ng build --prod api && ng build --prod client && yarn replace-placeholders-in-build",
"clean": "rimraf dist",
"database:format-schema": "prisma format",
"database:generate-typings": "prisma generate",
"database:gui": "prisma studio",
"database:push": "prisma db push --preview-feature",
"database:seed": "prisma db seed --preview-feature",
"dep-graph": "nx dep-graph",

View File

@ -24,6 +24,22 @@ model Access {
@@id([id, userId])
}
model Account {
accountType AccountType @default(SECURITIES)
createdAt DateTime @default(now())
id String @default(uuid())
isDefault Boolean @default(false)
name String?
Order Order[]
Platform Platform? @relation(fields: [platformId], references: [id])
platformId String?
updatedAt DateTime @updatedAt
User User @relation(fields: [userId], references: [id])
userId String
@@id([id, userId])
}
model Analytics {
activityCount Int @default(0)
updatedAt DateTime @updatedAt
@ -43,29 +59,33 @@ model MarketData {
}
model Order {
createdAt DateTime @default(now())
currency Currency
date DateTime
fee Float
id String @default(uuid())
Platform Platform? @relation(fields: [platformId], references: [id])
platformId String?
quantity Float
symbol String
type Type
unitPrice Float
updatedAt DateTime @updatedAt
User User @relation(fields: [userId], references: [id])
userId String
Account Account? @relation(fields: [accountId, accountUserId], references: [id, userId])
accountId String?
accountUserId String?
createdAt DateTime @default(now())
currency Currency
date DateTime
fee Float
id String @default(uuid())
Platform Platform? @relation(fields: [platformId], references: [id])
platformId String?
quantity Float
symbol String
type Type
unitPrice Float
updatedAt DateTime @updatedAt
User User @relation(fields: [userId], references: [id])
userId String
@@id([id, userId])
}
model Platform {
id String @id @default(uuid())
name String?
url String @unique
Order Order[]
Account Account[]
id String @id @default(uuid())
name String?
Order Order[]
url String @unique
}
model Property {
@ -84,6 +104,7 @@ model User {
Access Access[] @relation("accessGet")
AccessGive Access[] @relation(name: "accessGive")
accessToken String?
Account Account[]
alias String?
Analytics Analytics?
createdAt DateTime @default(now())
@ -96,6 +117,10 @@ model User {
updatedAt DateTime @updatedAt
}
enum AccountType {
SECURITIES
}
enum Currency {
CHF
EUR

View File

@ -1,4 +1,10 @@
import { Currency, PrismaClient, Role, Type } from '@prisma/client';
import {
AccountType,
Currency,
PrismaClient,
Role,
Type
} from '@prisma/client';
const prisma = new PrismaClient();
async function main() {
@ -88,106 +94,155 @@ async function main() {
create: {
accessToken:
'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpZCI6IjliMTEyYjRkLTNiN2QtNGJhZC05YmRkLTNiMGY3YjRkYWMyZiIsImlhdCI6MTYxODUxMjAxNCwiZXhwIjoxNjIxMTA0MDE0fQ.l3WUxpI0hxuQtdPrD0kd7sem6S2kx_7CrdNvkmlKuWw',
alias: 'Demo',
id: '9b112b4d-3b7d-4bad-9bdd-3b0f7b4dac2f',
role: Role.DEMO,
Order: {
Account: {
create: [
{
currency: Currency.USD,
date: new Date(Date.UTC(2017, 0, 3, 0, 0, 0)),
fee: 30,
id: 'cf7c0418-8535-4089-ae3d-5dbfa0aec2e1',
platformId: platformDegiro.id,
quantity: 50,
symbol: 'TSLA',
type: Type.BUY,
unitPrice: 42.97
accountType: AccountType.SECURITIES,
id: 'd804de69-0429-42dc-b6ca-b308fd7dd926',
name: 'Coinbase Account',
platformId: platformCoinbase.id
},
{
currency: Currency.USD,
date: new Date(Date.UTC(2017, 7, 16, 0, 0, 0)),
fee: 29.9,
id: 'a1c5d73a-8631-44e5-ac44-356827a5212c',
platformId: platformCoinbase.id,
quantity: 0.5614682,
symbol: 'BTCUSD',
type: Type.BUY,
unitPrice: 3562.089535970158
accountType: AccountType.SECURITIES,
id: '65cfb79d-b6c7-4591-9d46-73426bc62094',
name: 'DEGIRO Account',
platformId: platformDegiro.id
},
{
currency: Currency.USD,
date: new Date(Date.UTC(2018, 9, 1, 0, 0, 0)),
fee: 80.79,
id: '71c08e2a-4a86-44ae-a890-c337de5d5f9b',
platformId: platformInteractiveBrokers.id,
quantity: 5,
symbol: 'AMZN',
type: Type.BUY,
unitPrice: 2021.99
},
{
currency: Currency.USD,
date: new Date(Date.UTC(2019, 2, 1, 0, 0, 0)),
fee: 19.9,
id: '385f2c2c-d53e-4937-b0e5-e92ef6020d4e',
platformId: platformInteractiveBrokers.id,
quantity: 10,
symbol: 'VTI',
type: Type.BUY,
unitPrice: 144.38
},
{
currency: Currency.USD,
date: new Date(Date.UTC(2019, 8, 3, 0, 0, 0)),
fee: 19.9,
id: '185f2c2c-d53e-4937-b0e5-a93ef6020d4e',
platformId: platformInteractiveBrokers.id,
quantity: 10,
symbol: 'VTI',
type: Type.BUY,
unitPrice: 147.99
},
{
currency: Currency.USD,
date: new Date(Date.UTC(2020, 2, 2, 0, 0, 0)),
fee: 19.9,
id: '347b0430-a84f-4031-a0f9-390399066ad6',
platformId: platformInteractiveBrokers.id,
quantity: 10,
symbol: 'VTI',
type: Type.BUY,
unitPrice: 151.41
},
{
currency: Currency.USD,
date: new Date(Date.UTC(2020, 8, 1, 0, 0, 0)),
fee: 19.9,
id: '67ec3f47-3189-4b63-ba05-60d3a06b302f',
platformId: platformInteractiveBrokers.id,
quantity: 10,
symbol: 'VTI',
type: Type.BUY,
unitPrice: 177.69
},
{
currency: Currency.USD,
date: new Date(Date.UTC(2020, 2, 1, 0, 0, 0)),
fee: 19.9,
id: 'd01c6fbc-fa8d-47e6-8e80-66f882d2bfd2',
platformId: platformInteractiveBrokers.id,
quantity: 10,
symbol: 'VTI',
type: Type.BUY,
unitPrice: 203.15
accountType: AccountType.SECURITIES,
id: '480269ce-e12a-4fd1-ac88-c4b0ff3f899c',
isDefault: true,
name: 'Interactive Brokers Account',
platformId: platformInteractiveBrokers.id
}
]
}
},
alias: 'Demo',
id: '9b112b4d-3b7d-4bad-9bdd-3b0f7b4dac2f',
role: Role.DEMO
},
update: {},
where: { id: '9b112b4d-3b7d-4bad-9bdd-3b0f7b4dac2f' }
});
await prisma.order.createMany({
data: [
{
accountId: '65cfb79d-b6c7-4591-9d46-73426bc62094',
accountUserId: userDemo.id,
currency: Currency.USD,
date: new Date(Date.UTC(2017, 0, 3, 0, 0, 0)),
fee: 30,
id: 'cf7c0418-8535-4089-ae3d-5dbfa0aec2e1',
platformId: platformDegiro.id,
quantity: 50,
symbol: 'TSLA',
type: Type.BUY,
unitPrice: 42.97,
userId: userDemo.id
},
{
accountId: 'd804de69-0429-42dc-b6ca-b308fd7dd926',
accountUserId: userDemo.id,
currency: Currency.USD,
date: new Date(Date.UTC(2017, 7, 16, 0, 0, 0)),
fee: 29.9,
id: 'a1c5d73a-8631-44e5-ac44-356827a5212c',
platformId: platformCoinbase.id,
quantity: 0.5614682,
symbol: 'BTCUSD',
type: Type.BUY,
unitPrice: 3562.089535970158,
userId: userDemo.id
},
{
accountId: '480269ce-e12a-4fd1-ac88-c4b0ff3f899c',
accountUserId: userDemo.id,
currency: Currency.USD,
date: new Date(Date.UTC(2018, 9, 1, 0, 0, 0)),
fee: 80.79,
id: '71c08e2a-4a86-44ae-a890-c337de5d5f9b',
platformId: platformInteractiveBrokers.id,
quantity: 5,
symbol: 'AMZN',
type: Type.BUY,
unitPrice: 2021.99,
userId: userDemo.id
},
{
accountId: '480269ce-e12a-4fd1-ac88-c4b0ff3f899c',
accountUserId: userDemo.id,
currency: Currency.USD,
date: new Date(Date.UTC(2019, 2, 1, 0, 0, 0)),
fee: 19.9,
id: '385f2c2c-d53e-4937-b0e5-e92ef6020d4e',
platformId: platformInteractiveBrokers.id,
quantity: 10,
symbol: 'VTI',
type: Type.BUY,
unitPrice: 144.38,
userId: userDemo.id
},
{
accountId: '480269ce-e12a-4fd1-ac88-c4b0ff3f899c',
accountUserId: userDemo.id,
currency: Currency.USD,
date: new Date(Date.UTC(2019, 8, 3, 0, 0, 0)),
fee: 19.9,
id: '185f2c2c-d53e-4937-b0e5-a93ef6020d4e',
platformId: platformInteractiveBrokers.id,
quantity: 10,
symbol: 'VTI',
type: Type.BUY,
unitPrice: 147.99,
userId: userDemo.id
},
{
accountId: '480269ce-e12a-4fd1-ac88-c4b0ff3f899c',
accountUserId: userDemo.id,
currency: Currency.USD,
date: new Date(Date.UTC(2020, 2, 2, 0, 0, 0)),
fee: 19.9,
id: '347b0430-a84f-4031-a0f9-390399066ad6',
platformId: platformInteractiveBrokers.id,
quantity: 10,
symbol: 'VTI',
type: Type.BUY,
unitPrice: 151.41,
userId: userDemo.id
},
{
accountId: '480269ce-e12a-4fd1-ac88-c4b0ff3f899c',
accountUserId: userDemo.id,
currency: Currency.USD,
date: new Date(Date.UTC(2020, 8, 1, 0, 0, 0)),
fee: 19.9,
id: '67ec3f47-3189-4b63-ba05-60d3a06b302f',
platformId: platformInteractiveBrokers.id,
quantity: 10,
symbol: 'VTI',
type: Type.BUY,
unitPrice: 177.69,
userId: userDemo.id
},
{
accountId: '480269ce-e12a-4fd1-ac88-c4b0ff3f899c',
accountUserId: userDemo.id,
currency: Currency.USD,
date: new Date(Date.UTC(2020, 2, 1, 0, 0, 0)),
fee: 19.9,
id: 'd01c6fbc-fa8d-47e6-8e80-66f882d2bfd2',
platformId: platformInteractiveBrokers.id,
quantity: 10,
symbol: 'VTI',
type: Type.BUY,
unitPrice: 203.15,
userId: userDemo.id
}
],
skipDuplicates: true
});
console.log({
platformBitcoinSuisse,
platformBitpanda,