2021-11-07 09:42:36 +01:00
|
|
|
import { SymbolProfileService } from '@ghostfolio/api/services/symbol-profile.service';
|
2021-12-04 21:05:11 +01:00
|
|
|
import {
|
|
|
|
PROPERTY_LAST_DATA_GATHERING,
|
2021-12-25 14:18:46 +01:00
|
|
|
PROPERTY_LOCKED_DATA_GATHERING
|
2021-12-04 21:05:11 +01:00
|
|
|
} from '@ghostfolio/common/config';
|
2021-09-25 16:44:24 +02:00
|
|
|
import { DATE_FORMAT, resetHours } from '@ghostfolio/common/helper';
|
2022-02-28 21:35:52 +01:00
|
|
|
import { UniqueAsset } from '@ghostfolio/common/interfaces';
|
2021-11-07 21:25:18 +01:00
|
|
|
import { Inject, Injectable, Logger } from '@nestjs/common';
|
2021-12-25 14:18:46 +01:00
|
|
|
import { DataSource } from '@prisma/client';
|
2021-04-13 21:53:58 +02:00
|
|
|
import {
|
|
|
|
differenceInHours,
|
|
|
|
format,
|
|
|
|
getDate,
|
|
|
|
getMonth,
|
|
|
|
getYear,
|
|
|
|
isBefore,
|
|
|
|
subDays
|
|
|
|
} from 'date-fns';
|
|
|
|
|
2021-08-14 16:55:40 +02:00
|
|
|
import { DataProviderService } from './data-provider/data-provider.service';
|
2021-10-24 10:49:17 +02:00
|
|
|
import { DataEnhancerInterface } from './data-provider/interfaces/data-enhancer.interface';
|
2021-09-24 21:09:48 +02:00
|
|
|
import { ExchangeRateDataService } from './exchange-rate-data.service';
|
2021-05-27 20:50:10 +02:00
|
|
|
import { IDataGatheringItem } from './interfaces/interfaces';
|
2021-04-13 21:53:58 +02:00
|
|
|
import { PrismaService } from './prisma.service';
|
|
|
|
|
|
|
|
@Injectable()
|
|
|
|
export class DataGatheringService {
|
2021-11-13 11:32:28 +01:00
|
|
|
private dataGatheringProgress: number;
|
|
|
|
|
2021-04-13 21:53:58 +02:00
|
|
|
public constructor(
|
2021-10-24 10:49:17 +02:00
|
|
|
@Inject('DataEnhancers')
|
|
|
|
private readonly dataEnhancers: DataEnhancerInterface[],
|
2021-04-18 19:06:54 +02:00
|
|
|
private readonly dataProviderService: DataProviderService,
|
2021-09-24 21:09:48 +02:00
|
|
|
private readonly exchangeRateDataService: ExchangeRateDataService,
|
2021-11-07 09:42:36 +01:00
|
|
|
private readonly prismaService: PrismaService,
|
|
|
|
private readonly symbolProfileService: SymbolProfileService
|
2021-04-13 21:53:58 +02:00
|
|
|
) {}
|
|
|
|
|
|
|
|
public async gather7Days() {
|
|
|
|
const isDataGatheringNeeded = await this.isDataGatheringNeeded();
|
|
|
|
|
|
|
|
if (isDataGatheringNeeded) {
|
2022-03-07 17:20:07 +01:00
|
|
|
Logger.log('7d data gathering has been started.', 'DataGatheringService');
|
2021-08-22 22:19:10 +02:00
|
|
|
console.time('data-gathering-7d');
|
2021-04-13 21:53:58 +02:00
|
|
|
|
2021-08-07 22:38:07 +02:00
|
|
|
await this.prismaService.property.create({
|
2021-04-13 21:53:58 +02:00
|
|
|
data: {
|
2021-12-04 21:05:11 +01:00
|
|
|
key: PROPERTY_LOCKED_DATA_GATHERING,
|
2021-04-13 21:53:58 +02:00
|
|
|
value: new Date().toISOString()
|
|
|
|
}
|
|
|
|
});
|
|
|
|
|
|
|
|
const symbols = await this.getSymbols7D();
|
|
|
|
|
|
|
|
try {
|
|
|
|
await this.gatherSymbols(symbols);
|
|
|
|
|
2021-08-07 22:38:07 +02:00
|
|
|
await this.prismaService.property.upsert({
|
2021-04-13 21:53:58 +02:00
|
|
|
create: {
|
2021-12-04 21:05:11 +01:00
|
|
|
key: PROPERTY_LAST_DATA_GATHERING,
|
2021-04-13 21:53:58 +02:00
|
|
|
value: new Date().toISOString()
|
|
|
|
},
|
|
|
|
update: { value: new Date().toISOString() },
|
2021-12-04 21:05:11 +01:00
|
|
|
where: { key: PROPERTY_LAST_DATA_GATHERING }
|
2021-04-13 21:53:58 +02:00
|
|
|
});
|
|
|
|
} catch (error) {
|
2022-03-07 17:20:07 +01:00
|
|
|
Logger.error(error, 'DataGatheringService');
|
2021-04-13 21:53:58 +02:00
|
|
|
}
|
|
|
|
|
2021-08-07 22:38:07 +02:00
|
|
|
await this.prismaService.property.delete({
|
2021-04-13 21:53:58 +02:00
|
|
|
where: {
|
2021-12-04 21:05:11 +01:00
|
|
|
key: PROPERTY_LOCKED_DATA_GATHERING
|
2021-04-13 21:53:58 +02:00
|
|
|
}
|
|
|
|
});
|
|
|
|
|
2022-03-07 17:20:07 +01:00
|
|
|
Logger.log(
|
|
|
|
'7d data gathering has been completed.',
|
|
|
|
'DataGatheringService'
|
|
|
|
);
|
2021-08-22 22:19:10 +02:00
|
|
|
console.timeEnd('data-gathering-7d');
|
2021-04-13 21:53:58 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
public async gatherMax() {
|
2021-08-07 22:38:07 +02:00
|
|
|
const isDataGatheringLocked = await this.prismaService.property.findUnique({
|
2021-12-04 21:05:11 +01:00
|
|
|
where: { key: PROPERTY_LOCKED_DATA_GATHERING }
|
2021-04-18 19:06:54 +02:00
|
|
|
});
|
2021-04-13 21:53:58 +02:00
|
|
|
|
2021-04-18 19:06:54 +02:00
|
|
|
if (!isDataGatheringLocked) {
|
2022-03-07 17:20:07 +01:00
|
|
|
Logger.log(
|
|
|
|
'Max data gathering has been started.',
|
|
|
|
'DataGatheringService'
|
|
|
|
);
|
2021-08-22 22:19:10 +02:00
|
|
|
console.time('data-gathering-max');
|
2021-04-13 21:53:58 +02:00
|
|
|
|
2021-08-07 22:38:07 +02:00
|
|
|
await this.prismaService.property.create({
|
2021-04-13 21:53:58 +02:00
|
|
|
data: {
|
2021-12-04 21:05:11 +01:00
|
|
|
key: PROPERTY_LOCKED_DATA_GATHERING,
|
2021-04-13 21:53:58 +02:00
|
|
|
value: new Date().toISOString()
|
|
|
|
}
|
|
|
|
});
|
|
|
|
|
|
|
|
const symbols = await this.getSymbolsMax();
|
|
|
|
|
|
|
|
try {
|
|
|
|
await this.gatherSymbols(symbols);
|
|
|
|
|
2021-08-07 22:38:07 +02:00
|
|
|
await this.prismaService.property.upsert({
|
2021-04-13 21:53:58 +02:00
|
|
|
create: {
|
2021-12-04 21:05:11 +01:00
|
|
|
key: PROPERTY_LAST_DATA_GATHERING,
|
2021-04-13 21:53:58 +02:00
|
|
|
value: new Date().toISOString()
|
|
|
|
},
|
|
|
|
update: { value: new Date().toISOString() },
|
2021-12-04 21:05:11 +01:00
|
|
|
where: { key: PROPERTY_LAST_DATA_GATHERING }
|
2021-04-13 21:53:58 +02:00
|
|
|
});
|
|
|
|
} catch (error) {
|
2022-03-07 17:20:07 +01:00
|
|
|
Logger.error(error, 'DataGatheringService');
|
2021-04-13 21:53:58 +02:00
|
|
|
}
|
|
|
|
|
2021-08-07 22:38:07 +02:00
|
|
|
await this.prismaService.property.delete({
|
2021-04-13 21:53:58 +02:00
|
|
|
where: {
|
2021-12-04 21:05:11 +01:00
|
|
|
key: PROPERTY_LOCKED_DATA_GATHERING
|
2021-04-13 21:53:58 +02:00
|
|
|
}
|
|
|
|
});
|
|
|
|
|
2022-03-07 17:20:07 +01:00
|
|
|
Logger.log(
|
|
|
|
'Max data gathering has been completed.',
|
|
|
|
'DataGatheringService'
|
|
|
|
);
|
2021-08-22 22:19:10 +02:00
|
|
|
console.timeEnd('data-gathering-max');
|
2021-04-13 21:53:58 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-02-28 21:35:52 +01:00
|
|
|
public async gatherSymbol({ dataSource, symbol }: UniqueAsset) {
|
2021-11-30 21:06:10 +01:00
|
|
|
const isDataGatheringLocked = await this.prismaService.property.findUnique({
|
2021-12-04 21:05:11 +01:00
|
|
|
where: { key: PROPERTY_LOCKED_DATA_GATHERING }
|
2021-11-30 21:06:10 +01:00
|
|
|
});
|
|
|
|
|
|
|
|
if (!isDataGatheringLocked) {
|
2022-03-07 17:20:07 +01:00
|
|
|
Logger.log(
|
|
|
|
`Symbol data gathering for ${symbol} has been started.`,
|
|
|
|
'DataGatheringService'
|
|
|
|
);
|
2021-11-30 21:06:10 +01:00
|
|
|
console.time('data-gathering-symbol');
|
|
|
|
|
|
|
|
await this.prismaService.property.create({
|
|
|
|
data: {
|
2021-12-04 21:05:11 +01:00
|
|
|
key: PROPERTY_LOCKED_DATA_GATHERING,
|
2021-11-30 21:06:10 +01:00
|
|
|
value: new Date().toISOString()
|
|
|
|
}
|
|
|
|
});
|
|
|
|
|
|
|
|
const symbols = (await this.getSymbolsMax()).filter(
|
|
|
|
(dataGatheringItem) => {
|
|
|
|
return (
|
|
|
|
dataGatheringItem.dataSource === dataSource &&
|
|
|
|
dataGatheringItem.symbol === symbol
|
|
|
|
);
|
|
|
|
}
|
|
|
|
);
|
|
|
|
|
|
|
|
try {
|
|
|
|
await this.gatherSymbols(symbols);
|
|
|
|
|
|
|
|
await this.prismaService.property.upsert({
|
|
|
|
create: {
|
2021-12-04 21:05:11 +01:00
|
|
|
key: PROPERTY_LAST_DATA_GATHERING,
|
2021-11-30 21:06:10 +01:00
|
|
|
value: new Date().toISOString()
|
|
|
|
},
|
|
|
|
update: { value: new Date().toISOString() },
|
2021-12-04 21:05:11 +01:00
|
|
|
where: { key: PROPERTY_LAST_DATA_GATHERING }
|
2021-11-30 21:06:10 +01:00
|
|
|
});
|
|
|
|
} catch (error) {
|
2022-03-07 17:20:07 +01:00
|
|
|
Logger.error(error, 'DataGatheringService');
|
2021-11-30 21:06:10 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
await this.prismaService.property.delete({
|
|
|
|
where: {
|
2021-12-04 21:05:11 +01:00
|
|
|
key: PROPERTY_LOCKED_DATA_GATHERING
|
2021-11-30 21:06:10 +01:00
|
|
|
}
|
|
|
|
});
|
|
|
|
|
2022-03-07 17:20:07 +01:00
|
|
|
Logger.log(
|
|
|
|
`Symbol data gathering for ${symbol} has been completed.`,
|
|
|
|
'DataGatheringService'
|
|
|
|
);
|
2021-11-30 21:06:10 +01:00
|
|
|
console.timeEnd('data-gathering-symbol');
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-12-11 11:45:13 +01:00
|
|
|
public async gatherSymbolForDate({
|
|
|
|
dataSource,
|
|
|
|
date,
|
|
|
|
symbol
|
|
|
|
}: {
|
|
|
|
dataSource: DataSource;
|
|
|
|
date: Date;
|
|
|
|
symbol: string;
|
|
|
|
}) {
|
|
|
|
try {
|
|
|
|
const historicalData = await this.dataProviderService.getHistoricalRaw(
|
|
|
|
[{ dataSource, symbol }],
|
|
|
|
date,
|
|
|
|
date
|
|
|
|
);
|
|
|
|
|
|
|
|
const marketPrice =
|
|
|
|
historicalData[symbol][format(date, DATE_FORMAT)].marketPrice;
|
|
|
|
|
|
|
|
if (marketPrice) {
|
|
|
|
return await this.prismaService.marketData.upsert({
|
|
|
|
create: {
|
|
|
|
dataSource,
|
|
|
|
date,
|
|
|
|
marketPrice,
|
|
|
|
symbol
|
|
|
|
},
|
|
|
|
update: { marketPrice },
|
|
|
|
where: { date_symbol: { date, symbol } }
|
|
|
|
});
|
|
|
|
}
|
|
|
|
} catch (error) {
|
2022-03-07 17:20:07 +01:00
|
|
|
Logger.error(error, 'DataGatheringService');
|
2021-12-11 11:45:13 +01:00
|
|
|
} finally {
|
|
|
|
return undefined;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-05-07 20:00:51 +02:00
|
|
|
public async gatherAssetProfiles(aUniqueAssets?: UniqueAsset[]) {
|
|
|
|
let uniqueAssets = aUniqueAssets?.filter((dataGatheringItem) => {
|
|
|
|
return dataGatheringItem.dataSource !== 'MANUAL';
|
|
|
|
});
|
|
|
|
|
|
|
|
if (!uniqueAssets) {
|
|
|
|
uniqueAssets = await this.getUniqueAssets();
|
|
|
|
}
|
|
|
|
|
2022-03-07 17:20:07 +01:00
|
|
|
Logger.log(
|
2022-05-07 20:00:51 +02:00
|
|
|
`Asset profile data gathering has been started for ${uniqueAssets
|
|
|
|
.map(({ dataSource, symbol }) => {
|
|
|
|
return `${symbol} (${dataSource})`;
|
|
|
|
})
|
|
|
|
.join(',')}.`,
|
2022-03-07 17:20:07 +01:00
|
|
|
'DataGatheringService'
|
|
|
|
);
|
2021-07-24 21:13:48 +02:00
|
|
|
|
2022-02-27 17:03:00 +01:00
|
|
|
const assetProfiles = await this.dataProviderService.getAssetProfiles(
|
2022-05-07 20:00:51 +02:00
|
|
|
uniqueAssets
|
2022-02-27 17:03:00 +01:00
|
|
|
);
|
2022-05-26 18:59:29 +02:00
|
|
|
const symbolProfiles =
|
|
|
|
await this.symbolProfileService.getSymbolProfilesBySymbols(
|
|
|
|
uniqueAssets.map(({ symbol }) => {
|
|
|
|
return symbol;
|
|
|
|
})
|
|
|
|
);
|
2021-07-24 21:13:48 +02:00
|
|
|
|
2022-02-27 17:03:00 +01:00
|
|
|
for (const [symbol, assetProfile] of Object.entries(assetProfiles)) {
|
2021-11-07 09:42:36 +01:00
|
|
|
const symbolMapping = symbolProfiles.find((symbolProfile) => {
|
|
|
|
return symbolProfile.symbol === symbol;
|
|
|
|
})?.symbolMapping;
|
|
|
|
|
2021-10-24 10:49:17 +02:00
|
|
|
for (const dataEnhancer of this.dataEnhancers) {
|
|
|
|
try {
|
2022-02-27 17:03:00 +01:00
|
|
|
assetProfiles[symbol] = await dataEnhancer.enhance({
|
|
|
|
response: assetProfile,
|
2021-12-26 10:07:51 +01:00
|
|
|
symbol: symbolMapping?.[dataEnhancer.getName()] ?? symbol
|
2021-10-24 10:49:17 +02:00
|
|
|
});
|
|
|
|
} catch (error) {
|
2022-02-27 17:03:00 +01:00
|
|
|
Logger.error(
|
|
|
|
`Failed to enhance data for symbol ${symbol} by ${dataEnhancer.getName()}`,
|
2022-03-07 17:20:07 +01:00
|
|
|
error,
|
|
|
|
'DataGatheringService'
|
2022-02-27 17:03:00 +01:00
|
|
|
);
|
2021-10-24 10:49:17 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
const {
|
2021-10-11 13:32:21 -04:00
|
|
|
assetClass,
|
|
|
|
assetSubClass,
|
|
|
|
countries,
|
|
|
|
currency,
|
|
|
|
dataSource,
|
|
|
|
name,
|
2022-02-27 17:03:00 +01:00
|
|
|
sectors,
|
|
|
|
url
|
|
|
|
} = assetProfiles[symbol];
|
2021-10-24 10:49:17 +02:00
|
|
|
|
2021-07-24 21:13:48 +02:00
|
|
|
try {
|
2021-08-07 22:38:07 +02:00
|
|
|
await this.prismaService.symbolProfile.upsert({
|
2021-07-24 21:13:48 +02:00
|
|
|
create: {
|
2021-08-08 19:27:58 +02:00
|
|
|
assetClass,
|
2021-08-22 22:19:10 +02:00
|
|
|
assetSubClass,
|
2021-08-24 20:24:18 +02:00
|
|
|
countries,
|
2021-07-24 21:13:48 +02:00
|
|
|
currency,
|
|
|
|
dataSource,
|
|
|
|
name,
|
2021-10-11 13:32:21 -04:00
|
|
|
sectors,
|
2022-02-27 17:03:00 +01:00
|
|
|
symbol,
|
|
|
|
url
|
2021-07-24 21:13:48 +02:00
|
|
|
},
|
|
|
|
update: {
|
2021-08-08 19:27:58 +02:00
|
|
|
assetClass,
|
2021-08-22 22:19:10 +02:00
|
|
|
assetSubClass,
|
2021-08-24 20:24:18 +02:00
|
|
|
countries,
|
2021-07-24 21:13:48 +02:00
|
|
|
currency,
|
2021-10-11 13:32:21 -04:00
|
|
|
name,
|
2022-02-27 17:03:00 +01:00
|
|
|
sectors,
|
|
|
|
url
|
2021-07-24 21:13:48 +02:00
|
|
|
},
|
|
|
|
where: {
|
|
|
|
dataSource_symbol: {
|
|
|
|
dataSource,
|
|
|
|
symbol
|
|
|
|
}
|
|
|
|
}
|
|
|
|
});
|
|
|
|
} catch (error) {
|
2022-03-07 17:20:07 +01:00
|
|
|
Logger.error(
|
|
|
|
`${symbol}: ${error?.meta?.cause}`,
|
|
|
|
error,
|
|
|
|
'DataGatheringService'
|
|
|
|
);
|
2021-07-24 21:13:48 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-03-07 17:20:07 +01:00
|
|
|
Logger.log(
|
2022-05-07 20:00:51 +02:00
|
|
|
`Asset profile data gathering has been completed for ${uniqueAssets
|
|
|
|
.map(({ dataSource, symbol }) => {
|
|
|
|
return `${symbol} (${dataSource})`;
|
|
|
|
})
|
|
|
|
.join(',')}.`,
|
2022-03-07 17:20:07 +01:00
|
|
|
'DataGatheringService'
|
|
|
|
);
|
2021-07-24 21:13:48 +02:00
|
|
|
}
|
|
|
|
|
2021-05-27 20:50:10 +02:00
|
|
|
public async gatherSymbols(aSymbolsWithStartDate: IDataGatheringItem[]) {
|
2021-04-13 21:53:58 +02:00
|
|
|
let hasError = false;
|
2021-11-13 11:32:28 +01:00
|
|
|
let symbolCounter = 0;
|
2021-04-13 21:53:58 +02:00
|
|
|
|
2021-05-27 20:50:10 +02:00
|
|
|
for (const { dataSource, date, symbol } of aSymbolsWithStartDate) {
|
2022-02-27 17:03:00 +01:00
|
|
|
if (dataSource === 'MANUAL') {
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
2021-11-13 11:32:28 +01:00
|
|
|
this.dataGatheringProgress = symbolCounter / aSymbolsWithStartDate.length;
|
|
|
|
|
2021-04-13 21:53:58 +02:00
|
|
|
try {
|
|
|
|
const historicalData = await this.dataProviderService.getHistoricalRaw(
|
2021-05-27 20:50:10 +02:00
|
|
|
[{ dataSource, symbol }],
|
2021-04-13 21:53:58 +02:00
|
|
|
date,
|
|
|
|
new Date()
|
|
|
|
);
|
|
|
|
|
|
|
|
let currentDate = date;
|
|
|
|
let lastMarketPrice: number;
|
|
|
|
|
|
|
|
while (
|
|
|
|
isBefore(
|
|
|
|
currentDate,
|
|
|
|
new Date(
|
|
|
|
Date.UTC(
|
|
|
|
getYear(new Date()),
|
|
|
|
getMonth(new Date()),
|
|
|
|
getDate(new Date()),
|
|
|
|
0
|
|
|
|
)
|
|
|
|
)
|
|
|
|
)
|
|
|
|
) {
|
|
|
|
if (
|
2021-07-28 16:11:19 +02:00
|
|
|
historicalData[symbol]?.[format(currentDate, DATE_FORMAT)]
|
2021-04-13 21:53:58 +02:00
|
|
|
?.marketPrice
|
|
|
|
) {
|
|
|
|
lastMarketPrice =
|
2021-07-28 16:11:19 +02:00
|
|
|
historicalData[symbol]?.[format(currentDate, DATE_FORMAT)]
|
2021-04-13 21:53:58 +02:00
|
|
|
?.marketPrice;
|
|
|
|
}
|
|
|
|
|
2021-12-26 09:15:10 +01:00
|
|
|
if (lastMarketPrice) {
|
|
|
|
try {
|
|
|
|
await this.prismaService.marketData.create({
|
|
|
|
data: {
|
|
|
|
dataSource,
|
|
|
|
symbol,
|
2022-04-25 18:12:42 +02:00
|
|
|
date: new Date(
|
|
|
|
Date.UTC(
|
|
|
|
getYear(currentDate),
|
|
|
|
getMonth(currentDate),
|
|
|
|
getDate(currentDate),
|
|
|
|
0
|
|
|
|
)
|
|
|
|
),
|
2021-12-26 09:15:10 +01:00
|
|
|
marketPrice: lastMarketPrice
|
|
|
|
}
|
|
|
|
});
|
|
|
|
} catch {}
|
|
|
|
} else {
|
|
|
|
Logger.warn(
|
2022-02-27 17:03:00 +01:00
|
|
|
`Failed to gather data for symbol ${symbol} from ${dataSource} at ${format(
|
2021-12-26 09:15:10 +01:00
|
|
|
currentDate,
|
|
|
|
DATE_FORMAT
|
2022-03-07 17:20:07 +01:00
|
|
|
)}.`,
|
|
|
|
'DataGatheringService'
|
2021-12-26 09:15:10 +01:00
|
|
|
);
|
|
|
|
}
|
2021-04-13 21:53:58 +02:00
|
|
|
|
|
|
|
// Count month one up for iteration
|
|
|
|
currentDate = new Date(
|
|
|
|
Date.UTC(
|
|
|
|
getYear(currentDate),
|
|
|
|
getMonth(currentDate),
|
|
|
|
getDate(currentDate) + 1,
|
|
|
|
0
|
|
|
|
)
|
|
|
|
);
|
|
|
|
}
|
|
|
|
} catch (error) {
|
|
|
|
hasError = true;
|
2022-03-07 17:20:07 +01:00
|
|
|
Logger.error(error, 'DataGatheringService');
|
2021-04-13 21:53:58 +02:00
|
|
|
}
|
2021-11-13 11:32:28 +01:00
|
|
|
|
|
|
|
if (symbolCounter > 0 && symbolCounter % 100 === 0) {
|
|
|
|
Logger.log(
|
|
|
|
`Data gathering progress: ${(
|
|
|
|
this.dataGatheringProgress * 100
|
2022-03-07 17:20:07 +01:00
|
|
|
).toFixed(2)}%`,
|
|
|
|
'DataGatheringService'
|
2021-11-13 11:32:28 +01:00
|
|
|
);
|
|
|
|
}
|
|
|
|
|
|
|
|
symbolCounter += 1;
|
2021-04-13 21:53:58 +02:00
|
|
|
}
|
|
|
|
|
2021-09-24 21:09:48 +02:00
|
|
|
await this.exchangeRateDataService.initialize();
|
|
|
|
|
2021-04-13 21:53:58 +02:00
|
|
|
if (hasError) {
|
|
|
|
throw '';
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-11-13 11:32:28 +01:00
|
|
|
public async getDataGatheringProgress() {
|
|
|
|
const isInProgress = await this.getIsInProgress();
|
|
|
|
|
|
|
|
if (isInProgress) {
|
|
|
|
return this.dataGatheringProgress;
|
|
|
|
}
|
|
|
|
|
|
|
|
return undefined;
|
|
|
|
}
|
|
|
|
|
2021-08-09 21:11:35 +02:00
|
|
|
public async getIsInProgress() {
|
|
|
|
return await this.prismaService.property.findUnique({
|
2021-12-04 21:05:11 +01:00
|
|
|
where: { key: PROPERTY_LOCKED_DATA_GATHERING }
|
2021-08-09 21:11:35 +02:00
|
|
|
});
|
|
|
|
}
|
|
|
|
|
|
|
|
public async getLastDataGathering() {
|
|
|
|
const lastDataGathering = await this.prismaService.property.findUnique({
|
2021-12-04 21:05:11 +01:00
|
|
|
where: { key: PROPERTY_LAST_DATA_GATHERING }
|
2021-08-09 21:11:35 +02:00
|
|
|
});
|
|
|
|
|
|
|
|
if (lastDataGathering?.value) {
|
|
|
|
return new Date(lastDataGathering.value);
|
|
|
|
}
|
|
|
|
|
|
|
|
return undefined;
|
|
|
|
}
|
|
|
|
|
2021-11-28 19:46:34 +01:00
|
|
|
public async getSymbolsMax(): Promise<IDataGatheringItem[]> {
|
|
|
|
const startDate =
|
|
|
|
(
|
|
|
|
await this.prismaService.order.findFirst({
|
|
|
|
orderBy: [{ date: 'asc' }]
|
|
|
|
})
|
|
|
|
)?.date ?? new Date();
|
|
|
|
|
|
|
|
const currencyPairsToGather = this.exchangeRateDataService
|
|
|
|
.getCurrencyPairs()
|
|
|
|
.map(({ dataSource, symbol }) => {
|
|
|
|
return {
|
|
|
|
dataSource,
|
|
|
|
symbol,
|
|
|
|
date: startDate
|
|
|
|
};
|
|
|
|
});
|
|
|
|
|
|
|
|
const symbolProfilesToGather = (
|
|
|
|
await this.prismaService.symbolProfile.findMany({
|
|
|
|
orderBy: [{ symbol: 'asc' }],
|
|
|
|
select: {
|
|
|
|
dataSource: true,
|
|
|
|
Order: {
|
|
|
|
orderBy: [{ date: 'asc' }],
|
|
|
|
select: { date: true },
|
|
|
|
take: 1
|
|
|
|
},
|
|
|
|
scraperConfiguration: true,
|
|
|
|
symbol: true
|
2022-02-10 09:39:10 +01:00
|
|
|
},
|
|
|
|
where: {
|
|
|
|
dataSource: {
|
|
|
|
not: 'MANUAL'
|
|
|
|
}
|
2021-11-28 19:46:34 +01:00
|
|
|
}
|
|
|
|
})
|
|
|
|
).map((symbolProfile) => {
|
|
|
|
return {
|
|
|
|
...symbolProfile,
|
|
|
|
date: symbolProfile.Order?.[0]?.date ?? startDate
|
|
|
|
};
|
|
|
|
});
|
|
|
|
|
2021-12-25 14:18:46 +01:00
|
|
|
return [...currencyPairsToGather, ...symbolProfilesToGather];
|
2021-11-28 19:46:34 +01:00
|
|
|
}
|
|
|
|
|
2022-05-07 20:00:51 +02:00
|
|
|
public async getUniqueAssets(): Promise<UniqueAsset[]> {
|
|
|
|
const symbolProfiles = await this.prismaService.symbolProfile.findMany({
|
|
|
|
orderBy: [{ symbol: 'asc' }]
|
|
|
|
});
|
|
|
|
|
|
|
|
return symbolProfiles
|
|
|
|
.filter(({ dataSource }) => {
|
|
|
|
return (
|
|
|
|
dataSource !== DataSource.GHOSTFOLIO &&
|
|
|
|
dataSource !== DataSource.MANUAL &&
|
|
|
|
dataSource !== DataSource.RAKUTEN
|
|
|
|
);
|
|
|
|
})
|
|
|
|
.map(({ dataSource, symbol }) => {
|
|
|
|
return {
|
|
|
|
dataSource,
|
|
|
|
symbol
|
|
|
|
};
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
2021-08-09 21:11:35 +02:00
|
|
|
public async reset() {
|
2022-03-07 17:20:07 +01:00
|
|
|
Logger.log('Data gathering has been reset.', 'DataGatheringService');
|
2021-08-09 21:11:35 +02:00
|
|
|
|
|
|
|
await this.prismaService.property.deleteMany({
|
|
|
|
where: {
|
2021-12-04 21:05:11 +01:00
|
|
|
OR: [
|
|
|
|
{ key: PROPERTY_LAST_DATA_GATHERING },
|
|
|
|
{ key: PROPERTY_LOCKED_DATA_GATHERING }
|
|
|
|
]
|
2021-08-09 21:11:35 +02:00
|
|
|
}
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
2021-05-27 20:50:10 +02:00
|
|
|
private async getSymbols7D(): Promise<IDataGatheringItem[]> {
|
2021-04-13 21:53:58 +02:00
|
|
|
const startDate = subDays(resetHours(new Date()), 7);
|
|
|
|
|
2022-02-03 20:58:59 +01:00
|
|
|
const symbolProfiles = await this.prismaService.symbolProfile.findMany({
|
|
|
|
orderBy: [{ symbol: 'asc' }],
|
|
|
|
select: {
|
|
|
|
dataSource: true,
|
|
|
|
scraperConfiguration: true,
|
|
|
|
symbol: true
|
2022-02-10 09:39:10 +01:00
|
|
|
},
|
|
|
|
where: {
|
|
|
|
dataSource: {
|
|
|
|
not: 'MANUAL'
|
|
|
|
}
|
2022-02-03 20:58:59 +01:00
|
|
|
}
|
|
|
|
});
|
|
|
|
|
2021-12-25 14:18:46 +01:00
|
|
|
// Only consider symbols with incomplete market data for the last
|
|
|
|
// 7 days
|
2022-02-03 20:58:59 +01:00
|
|
|
const symbolsNotToGather = (
|
2021-12-25 14:18:46 +01:00
|
|
|
await this.prismaService.marketData.groupBy({
|
|
|
|
_count: true,
|
|
|
|
by: ['symbol'],
|
2022-04-25 22:37:56 +02:00
|
|
|
orderBy: [{ symbol: 'asc' }],
|
2021-12-25 14:18:46 +01:00
|
|
|
where: {
|
|
|
|
date: { gt: startDate }
|
|
|
|
}
|
|
|
|
})
|
|
|
|
)
|
|
|
|
.filter((group) => {
|
2022-02-03 20:58:59 +01:00
|
|
|
return group._count >= 6;
|
2021-12-25 14:18:46 +01:00
|
|
|
})
|
|
|
|
.map((group) => {
|
|
|
|
return group.symbol;
|
|
|
|
});
|
|
|
|
|
2022-02-03 20:58:59 +01:00
|
|
|
const symbolProfilesToGather = symbolProfiles
|
2021-12-26 09:15:10 +01:00
|
|
|
.filter(({ symbol }) => {
|
2022-02-03 20:58:59 +01:00
|
|
|
return !symbolsNotToGather.includes(symbol);
|
2021-12-25 14:18:46 +01:00
|
|
|
})
|
|
|
|
.map((symbolProfile) => {
|
|
|
|
return {
|
|
|
|
...symbolProfile,
|
|
|
|
date: startDate
|
|
|
|
};
|
|
|
|
});
|
2021-04-13 21:53:58 +02:00
|
|
|
|
2021-09-24 21:09:48 +02:00
|
|
|
const currencyPairsToGather = this.exchangeRateDataService
|
|
|
|
.getCurrencyPairs()
|
2021-12-26 09:15:10 +01:00
|
|
|
.filter(({ symbol }) => {
|
2022-02-03 20:58:59 +01:00
|
|
|
return !symbolsNotToGather.includes(symbol);
|
2021-12-26 09:15:10 +01:00
|
|
|
})
|
2021-09-24 21:09:48 +02:00
|
|
|
.map(({ dataSource, symbol }) => {
|
2021-05-27 20:50:10 +02:00
|
|
|
return {
|
|
|
|
dataSource,
|
|
|
|
symbol,
|
|
|
|
date: startDate
|
|
|
|
};
|
2021-09-24 21:09:48 +02:00
|
|
|
});
|
2021-04-13 21:53:58 +02:00
|
|
|
|
2021-12-25 14:18:46 +01:00
|
|
|
return [...currencyPairsToGather, ...symbolProfilesToGather];
|
2021-04-13 21:53:58 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
private async isDataGatheringNeeded() {
|
2021-08-09 21:11:35 +02:00
|
|
|
const lastDataGathering = await this.getLastDataGathering();
|
2021-04-13 21:53:58 +02:00
|
|
|
|
2021-08-07 22:38:07 +02:00
|
|
|
const isDataGatheringLocked = await this.prismaService.property.findUnique({
|
2021-12-04 21:05:11 +01:00
|
|
|
where: { key: PROPERTY_LOCKED_DATA_GATHERING }
|
2021-04-13 21:53:58 +02:00
|
|
|
});
|
|
|
|
|
2021-08-09 21:11:35 +02:00
|
|
|
const diffInHours = differenceInHours(new Date(), lastDataGathering);
|
2021-04-13 21:53:58 +02:00
|
|
|
|
|
|
|
return (diffInHours >= 1 || !lastDataGathering) && !isDataGatheringLocked;
|
|
|
|
}
|
|
|
|
}
|