From c3768a882de0bbd75f88c40dcde2661d3f861eec Mon Sep 17 00:00:00 2001 From: Thomas Kaul <4159106+dtslvr@users.noreply.github.com> Date: Fri, 27 May 2022 10:03:37 +0200 Subject: [PATCH] Feature/add benchmarks to twitter bot service (#959) * Extend benchmarks with market condition and adapt twitter bot service * Update changelog --- CHANGELOG.md | 5 ++ .../api/src/app/benchmark/benchmark.module.ts | 1 + .../src/app/benchmark/benchmark.service.ts | 7 +++ .../twitter-bot/twitter-bot.module.ts | 4 +- .../twitter-bot/twitter-bot.service.ts | 55 ++++++++++++++++++- libs/common/src/lib/helper.ts | 13 +++++ .../src/lib/interfaces/benchmark.interface.ts | 1 + .../lib/benchmark/benchmark.component.html | 17 ++++++ .../src/lib/benchmark/benchmark.component.ts | 3 + 9 files changed, 103 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 79c83fbb1..8fd5e5381 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,11 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## Unreleased +### Added + +- Extended the benchmarks of the markets overview by the current market condition (bear and bull market) +- Extended the twitter bot service by benchmarks + ### Changed - Upgraded `prisma` from version `3.12.0` to `3.14.0` diff --git a/apps/api/src/app/benchmark/benchmark.module.ts b/apps/api/src/app/benchmark/benchmark.module.ts index 4d95c4bd7..fa26a3afd 100644 --- a/apps/api/src/app/benchmark/benchmark.module.ts +++ b/apps/api/src/app/benchmark/benchmark.module.ts @@ -11,6 +11,7 @@ import { BenchmarkService } from './benchmark.service'; @Module({ controllers: [BenchmarkController], + exports: [BenchmarkService], imports: [ ConfigurationModule, DataProviderModule, diff --git a/apps/api/src/app/benchmark/benchmark.service.ts b/apps/api/src/app/benchmark/benchmark.service.ts index 5780c768a..f7a10d8e5 100644 --- a/apps/api/src/app/benchmark/benchmark.service.ts +++ b/apps/api/src/app/benchmark/benchmark.service.ts @@ -53,6 +53,9 @@ export class BenchmarkService { .minus(1); return { + marketCondition: this.getMarketCondition( + performancePercentFromAllTimeHigh + ), name: assetProfiles.find(({ dataSource, symbol }) => { return ( dataSource === benchmarkAssets[index].dataSource && @@ -74,4 +77,8 @@ export class BenchmarkService { return benchmarks; } + + private getMarketCondition(aPerformanceInPercent: Big) { + return aPerformanceInPercent.lte(-0.2) ? 'BEAR_MARKET' : 'NEUTRAL_MARKET'; + } } diff --git a/apps/api/src/services/twitter-bot/twitter-bot.module.ts b/apps/api/src/services/twitter-bot/twitter-bot.module.ts index d74d6f10f..02213ef62 100644 --- a/apps/api/src/services/twitter-bot/twitter-bot.module.ts +++ b/apps/api/src/services/twitter-bot/twitter-bot.module.ts @@ -1,11 +1,13 @@ +import { BenchmarkModule } from '@ghostfolio/api/app/benchmark/benchmark.module'; import { SymbolModule } from '@ghostfolio/api/app/symbol/symbol.module'; import { ConfigurationModule } from '@ghostfolio/api/services/configuration.module'; +import { PropertyModule } from '@ghostfolio/api/services/property/property.module'; import { TwitterBotService } from '@ghostfolio/api/services/twitter-bot/twitter-bot.service'; import { Module } from '@nestjs/common'; @Module({ exports: [TwitterBotService], - imports: [ConfigurationModule, SymbolModule], + imports: [BenchmarkModule, ConfigurationModule, PropertyModule, SymbolModule], providers: [TwitterBotService] }) export class TwitterBotModule {} diff --git a/apps/api/src/services/twitter-bot/twitter-bot.service.ts b/apps/api/src/services/twitter-bot/twitter-bot.service.ts index 58052872b..2829896dd 100644 --- a/apps/api/src/services/twitter-bot/twitter-bot.service.ts +++ b/apps/api/src/services/twitter-bot/twitter-bot.service.ts @@ -1,12 +1,20 @@ +import { BenchmarkService } from '@ghostfolio/api/app/benchmark/benchmark.service'; import { SymbolService } from '@ghostfolio/api/app/symbol/symbol.service'; import { ConfigurationService } from '@ghostfolio/api/services/configuration.service'; +import { PropertyService } from '@ghostfolio/api/services/property/property.service'; import { + PROPERTY_BENCHMARKS, ghostfolioFearAndGreedIndexDataSource, ghostfolioFearAndGreedIndexSymbol } from '@ghostfolio/common/config'; -import { resolveFearAndGreedIndex } from '@ghostfolio/common/helper'; +import { + resolveFearAndGreedIndex, + resolveMarketCondition +} from '@ghostfolio/common/helper'; +import { UniqueAsset } from '@ghostfolio/common/interfaces'; import { Injectable, Logger } from '@nestjs/common'; import { isSunday } from 'date-fns'; +import * as roundTo from 'round-to'; import { TwitterApi, TwitterApiReadWrite } from 'twitter-api-v2'; @Injectable() @@ -14,7 +22,9 @@ export class TwitterBotService { private twitterClient: TwitterApiReadWrite; public constructor( + private readonly benchmarkService: BenchmarkService, private readonly configurationService: ConfigurationService, + private readonly propertyService: PropertyService, private readonly symbolService: SymbolService ) { this.twitterClient = new TwitterApi({ @@ -48,7 +58,16 @@ export class TwitterBotService { symbolItem.marketPrice ); - const status = `Current Market Mood: ${emoji} ${text} (${symbolItem.marketPrice}/100)\n\n#FearAndGreed #Markets #ServiceTweet`; + let status = `Current Market Mood: ${emoji} ${text} (${symbolItem.marketPrice}/100)`; + + const benchmarkListing = await this.getBenchmarkListing(3); + + if (benchmarkListing?.length > 1) { + status += '\n\n'; + status += '±% from ATH\n'; + status += benchmarkListing; + } + const { data: createdTweet } = await this.twitterClient.v2.tweet( status ); @@ -62,4 +81,36 @@ export class TwitterBotService { Logger.error(error, 'TwitterBotService'); } } + + private async getBenchmarkListing(aMax: number) { + const benchmarkAssets: UniqueAsset[] = + ((await this.propertyService.getByKey( + PROPERTY_BENCHMARKS + )) as UniqueAsset[]) ?? []; + + const benchmarks = await this.benchmarkService.getBenchmarks( + benchmarkAssets + ); + + const benchmarkListing: string[] = []; + + for (const [index, benchmark] of benchmarks.entries()) { + if (index > aMax - 1) { + break; + } + + benchmarkListing.push( + `${benchmark.name} ${roundTo( + benchmark.performances.allTimeHigh.performancePercent * 100, + 1 + )}%${ + benchmark.marketCondition !== 'NEUTRAL_MARKET' + ? ' ' + resolveMarketCondition(benchmark.marketCondition).emoji + : '' + }` + ); + } + + return benchmarkListing.join('\n'); + } } diff --git a/libs/common/src/lib/helper.ts b/libs/common/src/lib/helper.ts index ad47abfdd..69cfa5928 100644 --- a/libs/common/src/lib/helper.ts +++ b/libs/common/src/lib/helper.ts @@ -3,6 +3,7 @@ import { DataSource } from '@prisma/client'; import { getDate, getMonth, getYear, parse, subDays } from 'date-fns'; import { ghostfolioScraperApiSymbolPrefix, locale } from './config'; +import { Benchmark } from './interfaces'; export function capitalize(aString: string) { return aString.charAt(0).toUpperCase() + aString.slice(1).toLowerCase(); @@ -178,6 +179,18 @@ export function resolveFearAndGreedIndex(aValue: number) { } } +export function resolveMarketCondition( + aMarketCondition: Benchmark['marketCondition'] +) { + if (aMarketCondition === 'BEAR_MARKET') { + return { emoji: '🐻' }; + } else if (aMarketCondition === 'BULL_MARKET') { + return { emoji: '🐮' }; + } else { + return { emoji: '⚪' }; + } +} + export const DATE_FORMAT = 'yyyy-MM-dd'; export function parseDate(date: string) { diff --git a/libs/common/src/lib/interfaces/benchmark.interface.ts b/libs/common/src/lib/interfaces/benchmark.interface.ts index 146fc4b07..906e30759 100644 --- a/libs/common/src/lib/interfaces/benchmark.interface.ts +++ b/libs/common/src/lib/interfaces/benchmark.interface.ts @@ -1,6 +1,7 @@ import { EnhancedSymbolProfile } from './enhanced-symbol-profile.interface'; export interface Benchmark { + marketCondition: 'BEAR_MARKET' | 'BULL_MARKET' | 'NEUTRAL_MARKET'; name: EnhancedSymbolProfile['name']; performances: { allTimeHigh: { diff --git a/libs/ui/src/lib/benchmark/benchmark.component.html b/libs/ui/src/lib/benchmark/benchmark.component.html index e41621fef..59113927f 100644 --- a/libs/ui/src/lib/benchmark/benchmark.component.html +++ b/libs/ui/src/lib/benchmark/benchmark.component.html @@ -29,4 +29,21 @@ from All Time Highfrom ATH +
+
+ {{ resolveMarketCondition(benchmark.marketCondition).emoji }} +
+ +
diff --git a/libs/ui/src/lib/benchmark/benchmark.component.ts b/libs/ui/src/lib/benchmark/benchmark.component.ts index a5f439364..939e3a35c 100644 --- a/libs/ui/src/lib/benchmark/benchmark.component.ts +++ b/libs/ui/src/lib/benchmark/benchmark.component.ts @@ -1,4 +1,5 @@ import { ChangeDetectionStrategy, Component, Input } from '@angular/core'; +import { resolveMarketCondition } from '@ghostfolio/common/helper'; import { Benchmark } from '@ghostfolio/common/interfaces'; @Component({ @@ -11,5 +12,7 @@ export class BenchmarkComponent { @Input() benchmark: Benchmark; @Input() locale: string; + public resolveMarketCondition = resolveMarketCondition; + public constructor() {} }