Compare commits

..

1 Commits

Author SHA1 Message Date
b23f3a8a81 Release 0.93.0 2021-04-26 21:55:51 +02:00
21 changed files with 70 additions and 157 deletions

View File

@ -5,18 +5,6 @@ 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.95.0 - 28.04.2021
### Added
- Added a data source attribute to the transactions model
## 0.94.0 - 27.04.2021
### Added
- Added the generic scraper symbols to the symbol lookup results
## 0.93.0 - 26.04.2021
### Changed

View File

@ -39,7 +39,6 @@ export class ExperimentalService {
accountId: undefined,
accountUserId: undefined,
createdAt: new Date(),
dataSource: undefined,
date: parseISO(order.date),
fee: 0,
id: undefined,

View File

@ -1,4 +1,4 @@
import { Currency, DataSource, Type } from '@prisma/client';
import { Currency, Type } from '@prisma/client';
import { IsISO8601, IsNumber, IsString, ValidateIf } from 'class-validator';
export class CreateOrderDto {
@ -8,9 +8,6 @@ export class CreateOrderDto {
@IsString()
currency: Currency;
@IsString()
dataSource: DataSource;
@IsISO8601()
date: string;

View File

@ -1,4 +1,4 @@
import { Currency, DataSource, Type } from '@prisma/client';
import { Currency, Type } from '@prisma/client';
import { IsISO8601, IsNumber, IsString, ValidateIf } from 'class-validator';
export class UpdateOrderDto {
@ -8,9 +8,6 @@ export class UpdateOrderDto {
@IsString()
currency: Currency;
@IsString()
dataSource: DataSource;
@IsISO8601()
date: string;

View File

@ -1,8 +1,6 @@
import { Currency } from '@prisma/client';
export interface PortfolioPositionDetail {
averagePrice: number;
currency: Currency;
currency: string;
firstBuyDate: string;
grossPerformance: number;
grossPerformancePercent: number;

View File

@ -1,7 +1,6 @@
import { Currency, DataSource } from '@prisma/client';
import { Currency } from '@prisma/client';
export interface SymbolItem {
currency: Currency;
dataSource: DataSource;
marketPrice: number;
}

View File

@ -1,6 +1,4 @@
import { DataGatheringService } from '@ghostfolio/api/services/data-gathering.service';
import { DataProviderService } from '@ghostfolio/api/services/data-provider.service';
import { GhostfolioScraperApiService } from '@ghostfolio/api/services/data-provider/ghostfolio-scraper-api/ghostfolio-scraper-api.service';
import { convertFromYahooSymbol } from '@ghostfolio/api/services/data-provider/yahoo-finance/yahoo-finance.service';
import { Injectable } from '@nestjs/common';
import { Currency } from '@prisma/client';
@ -12,51 +10,31 @@ import { SymbolItem } from './interfaces/symbol-item.interface';
@Injectable()
export class SymbolService {
public constructor(
private readonly dataProviderService: DataProviderService,
private readonly ghostfolioScraperApiService: GhostfolioScraperApiService
private readonly dataProviderService: DataProviderService
) {}
public async get(aSymbol: string): Promise<SymbolItem> {
const response = await this.dataProviderService.get([aSymbol]);
const { currency, dataSource, marketPrice } = response[aSymbol];
const { currency, marketPrice } = response[aSymbol];
return {
dataSource,
marketPrice,
currency: <Currency>(<unknown>currency)
};
}
public async lookup(aQuery = ''): Promise<LookupItem[]> {
const query = aQuery.toLowerCase();
const results: LookupItem[] = [];
if (!query) {
return results;
}
public async lookup(aQuery: string): Promise<LookupItem[]> {
const get = bent(
`https://query1.finance.yahoo.com/v1/finance/search?q=${query}&lang=en-US&region=US&quotesCount=8&newsCount=0&enableFuzzyQuery=false&quotesQueryId=tss_match_phrase_query&multiQuoteQueryId=multi_quote_single_token_query&newsQueryId=news_cie_vespa&enableCb=true&enableNavLinks=false&enableEnhancedTrivialQuery=true`,
`https://query1.finance.yahoo.com/v1/finance/search?q=${aQuery}&lang=en-US&region=US&quotesCount=8&newsCount=0&enableFuzzyQuery=false&quotesQueryId=tss_match_phrase_query&multiQuoteQueryId=multi_quote_single_token_query&newsQueryId=news_cie_vespa&enableCb=true&enableNavLinks=false&enableEnhancedTrivialQuery=true`,
'GET',
'json',
200
);
// Add custom symbols
const scraperConfigurations = await this.ghostfolioScraperApiService.getScraperConfigurations();
scraperConfigurations.forEach((scraperConfiguration) => {
if (scraperConfiguration.name.toLowerCase().startsWith(query)) {
results.push({
name: scraperConfiguration.name,
symbol: scraperConfiguration.symbol
});
}
});
try {
const { quotes } = await get();
const searchResult = quotes
return quotes
.filter(({ isYahooFinance }) => {
return isYahooFinance;
})
@ -81,8 +59,6 @@ export class SymbolService {
symbol: convertFromYahooSymbol(symbol)
};
});
return results.concat(searchResult);
} catch (error) {
console.error(error);

View File

@ -18,7 +18,6 @@ import {
import { ConfigurationService } from './configuration.service';
import { DataProviderService } from './data-provider.service';
import { GhostfolioScraperApiService } from './data-provider/ghostfolio-scraper-api/ghostfolio-scraper-api.service';
import { PrismaService } from './prisma.service';
@Injectable()
@ -26,7 +25,6 @@ export class DataGatheringService {
public constructor(
private readonly configurationService: ConfigurationService,
private readonly dataProviderService: DataProviderService,
private readonly ghostfolioScraperApi: GhostfolioScraperApiService,
private prisma: PrismaService
) {}
@ -185,17 +183,6 @@ export class DataGatheringService {
}
}
public async getCustomSymbolsToGather(startDate?: Date) {
const scraperConfigurations = await this.ghostfolioScraperApi.getScraperConfigurations();
return scraperConfigurations.map((scraperConfiguration) => {
return {
date: startDate,
symbol: scraperConfiguration.symbol
};
});
}
private getBenchmarksToGather(startDate: Date) {
const benchmarksToGather = benchmarks.map((symbol) => {
return {
@ -214,6 +201,32 @@ export class DataGatheringService {
return benchmarksToGather;
}
private async getCustomSymbolsToGather(startDate: Date) {
const customSymbolsToGather = [];
if (this.configurationService.get('ENABLE_FEATURE_CUSTOM_SYMBOLS')) {
try {
const {
value: scraperConfigString
} = await this.prisma.property.findFirst({
select: {
value: true
},
where: { key: 'SCRAPER_CONFIG' }
});
JSON.parse(scraperConfigString).forEach((item) => {
customSymbolsToGather.push({
date: startDate,
symbol: item.symbol
});
});
} catch {}
}
return customSymbolsToGather;
}
private async getSymbols7D(): Promise<{ date: Date; symbol: string }[]> {
const startDate = subDays(resetHours(new Date()), 7);

View File

@ -1,4 +1,3 @@
import { DataSource } from '.prisma/client';
import { getYesterday } from '@ghostfolio/helper';
import { Injectable } from '@nestjs/common';
import * as bent from 'bent';
@ -13,7 +12,6 @@ import {
MarketState
} from '../../interfaces/interfaces';
import { PrismaService } from '../../prisma.service';
import { ScraperConfig } from './interfaces/scraper-config.interface';
@Injectable()
export class GhostfolioScraperApiService implements DataProviderInterface {
@ -31,7 +29,7 @@ export class GhostfolioScraperApiService implements DataProviderInterface {
try {
const symbol = aSymbols[0];
const scraperConfig = await this.getScraperConfigurationBySymbol(symbol);
const scraperConfig = await this.getScraperConfig(symbol);
const { marketPrice } = await this.prisma.marketData.findFirst({
orderBy: {
@ -46,7 +44,6 @@ export class GhostfolioScraperApiService implements DataProviderInterface {
[symbol]: {
marketPrice,
currency: scraperConfig?.currency,
dataSource: DataSource.GHOSTFOLIO,
marketState: MarketState.delayed,
name: scraperConfig?.name
}
@ -73,17 +70,15 @@ export class GhostfolioScraperApiService implements DataProviderInterface {
try {
const symbol = aSymbols[0];
const scraperConfiguration = await this.getScraperConfigurationBySymbol(
symbol
);
const scraperConfig = await this.getScraperConfig(symbol);
const get = bent(scraperConfiguration?.url, 'GET', 'string', 200, {});
const get = bent(scraperConfig?.url, 'GET', 'string', 200, {});
const html = await get();
const $ = cheerio.load(html);
const value = this.extractNumberFromString(
$(scraperConfiguration?.selector).text()
$(scraperConfig?.selector).text()
);
return {
@ -100,23 +95,6 @@ export class GhostfolioScraperApiService implements DataProviderInterface {
return {};
}
public async getScraperConfigurations(): Promise<ScraperConfig[]> {
try {
const {
value: scraperConfigString
} = await this.prisma.property.findFirst({
select: {
value: true
},
where: { key: 'SCRAPER_CONFIG' }
});
return JSON.parse(scraperConfigString);
} catch {}
return [];
}
private extractNumberFromString(aString: string): number {
try {
const [numberString] = aString.match(
@ -128,10 +106,22 @@ export class GhostfolioScraperApiService implements DataProviderInterface {
}
}
private async getScraperConfigurationBySymbol(aSymbol: string) {
const scraperConfigurations = await this.getScraperConfigurations();
return scraperConfigurations.find((scraperConfiguration) => {
return scraperConfiguration.symbol === aSymbol;
});
private async getScraperConfig(aSymbol: string) {
try {
const {
value: scraperConfigString
} = await this.prisma.property.findFirst({
select: {
value: true
},
where: { key: 'SCRAPER_CONFIG' }
});
return JSON.parse(scraperConfigString).find((item) => {
return item.symbol === aSymbol;
});
} catch {}
return {};
}
}

View File

@ -1,9 +0,0 @@
import { Currency } from '.prisma/client';
export interface ScraperConfig {
currency: Currency;
name: string;
selector: string;
symbol: string;
url: string;
}

View File

@ -1,4 +1,3 @@
import { DataSource } from '.prisma/client';
import { getToday, getYesterday } from '@ghostfolio/helper';
import { Injectable } from '@nestjs/common';
import * as bent from 'bent';
@ -40,7 +39,6 @@ export class RakutenRapidApiService implements DataProviderInterface {
return {
'GF.FEAR_AND_GREED_INDEX': {
currency: undefined,
dataSource: DataSource.RAKUTEN,
marketPrice: fgi.now.value,
marketState: MarketState.open,
name: RakutenRapidApiService.FEAR_AND_GREED_INDEX_NAME

View File

@ -1,4 +1,3 @@
import { DataSource } from '.prisma/client';
import { isCrypto, isCurrency, parseCurrency } from '@ghostfolio/helper';
import { Injectable } from '@nestjs/common';
import { format } from 'date-fns';
@ -50,7 +49,6 @@ export class YahooFinanceService implements DataProviderInterface {
response[symbol] = {
currency: parseCurrency(value.price?.currency),
dataSource: DataSource.YAHOO,
exchange: this.parseExchange(value.price?.exchangeName),
marketState:
value.price?.marketState === 'REGULAR' || isCrypto(symbol)

View File

@ -1,4 +1,4 @@
import { Currency, DataSource, Platform } from '@prisma/client';
import { Currency, Platform } from '@prisma/client';
import { OrderType } from '../../models/order-type';
@ -51,7 +51,6 @@ export interface IDataProviderHistoricalResponse {
export interface IDataProviderResponse {
currency: Currency;
dataSource: DataSource;
exchange?: string;
industry?: Industry;
marketChange?: number;

View File

@ -78,9 +78,8 @@ export class CreateOrUpdateTransactionDialog {
this.dataService
.fetchSymbolItem(this.data.transaction.symbol)
.pipe(takeUntil(this.unsubscribeSubject))
.subscribe(({ currency, dataSource, marketPrice }) => {
.subscribe(({ currency, marketPrice }) => {
this.data.transaction.currency = currency;
this.data.transaction.dataSource = dataSource;
this.data.transaction.unitPrice = marketPrice;
this.isLoading = false;
@ -91,7 +90,6 @@ export class CreateOrUpdateTransactionDialog {
public onUpdateSymbolByTyping(value: string) {
this.data.transaction.currency = null;
this.data.transaction.dataSource = null;
this.data.transaction.unitPrice = null;
this.data.transaction.symbol = value;

View File

@ -25,7 +25,7 @@
class="autocomplete"
[value]="lookupItem.symbol"
>
<span class="mr-2 symbol">{{ lookupItem.symbol | gfSymbol }}</span
<span class="mr-2 symbol">{{ lookupItem.symbol }}</span
><span><b>{{ lookupItem.name }}</b></span>
</mat-option>
</ng-container>
@ -58,18 +58,6 @@
</mat-select>
</mat-form-field>
</div>
<div class="d-none">
<mat-form-field appearance="outline" class="w-100">
<mat-label i18n>Data Source</mat-label>
<input
disabled
matInput
name="dataSource"
required
[(ngModel)]="data.transaction.dataSource"
/>
</mat-form-field>
</div>
<div>
<mat-form-field appearance="outline" class="w-100">
<mat-label i18n>Date</mat-label>
@ -125,7 +113,6 @@
<mat-form-field appearance="outline" class="w-100">
<mat-label i18n>Account</mat-label>
<mat-select
disabled
name="accountId"
required
[(value)]="data.transaction.accountId"

View File

@ -9,7 +9,6 @@ import { MatFormFieldModule } from '@angular/material/form-field';
import { MatInputModule } from '@angular/material/input';
import { MatProgressSpinnerModule } from '@angular/material/progress-spinner';
import { MatSelectModule } from '@angular/material/select';
import { GfSymbolModule } from '@ghostfolio/client/pipes/symbol/symbol.module';
import { CreateOrUpdateTransactionDialog } from './create-or-update-transaction-dialog.component';
@ -18,7 +17,6 @@ import { CreateOrUpdateTransactionDialog } from './create-or-update-transaction-
exports: [],
imports: [
CommonModule,
GfSymbolModule,
FormsModule,
MatAutocompleteModule,
MatButtonModule,

View File

@ -1,6 +1,5 @@
import { Account } from '@prisma/client';
import { Order } from '../../interfaces/order.interface';
import { Account } from '@prisma/client';
export interface CreateOrUpdateTransactionDialogParams {
accountId: string;

View File

@ -1,9 +1,6 @@
import { Currency, DataSource } from '.prisma/client';
export interface Order {
accountId: string;
currency: Currency;
dataSource: DataSource;
currency: string;
date: Date;
fee: number;
id: string;

View File

@ -125,7 +125,6 @@ export class TransactionsPageComponent implements OnInit {
public openUpdateTransactionDialog({
accountId,
currency,
dataSource,
date,
fee,
id,
@ -141,7 +140,6 @@ export class TransactionsPageComponent implements OnInit {
transaction: {
accountId,
currency,
dataSource,
date,
fee,
id,
@ -179,9 +177,9 @@ export class TransactionsPageComponent implements OnInit {
private openCreateTransactionDialog(): void {
const dialogRef = this.dialog.open(CreateOrUpdateTransactionDialog, {
data: {
accounts: this.user?.accounts,
accounts: this.user.accounts,
transaction: {
accountId: this.user?.accounts.find((account) => {
accountId: this.user.accounts.find((account) => {
return account.isDefault;
})?.id,
currency: null,

View File

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

View File

@ -59,23 +59,22 @@ model MarketData {
}
model Order {
Account Account? @relation(fields: [accountId, accountUserId], references: [id, userId])
Account Account? @relation(fields: [accountId, accountUserId], references: [id, userId])
accountId String?
accountUserId String?
createdAt DateTime @default(now())
createdAt DateTime @default(now())
currency Currency
dataSource DataSource @default(YAHOO)
date DateTime
fee Float
id String @default(uuid())
Platform Platform? @relation(fields: [platformId], references: [id])
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])
updatedAt DateTime @updatedAt
User User @relation(fields: [userId], references: [id])
userId String
@@id([id, userId])
@ -129,12 +128,6 @@ enum Currency {
USD
}
enum DataSource {
GHOSTFOLIO
RAKUTEN
YAHOO
}
enum Provider {
ANONYMOUS
GOOGLE