@ -84,7 +84,8 @@ function createAccount({
currency : 'CHF' ,
platform : { name : 'Platform A' } ,
tags : isExcluded ? [ { id : TAG_ID_EXCLUDE_FROM_ANALYSIS } ] : [ ] ,
value : 2000
value : 2000 ,
valueInBaseCurrency : 1800
} as unknown as AccountWithValue ;
}
@ -105,8 +106,11 @@ function createActivity({
} ,
currency : 'CHF' ,
date : new Date ( '2024-01-01' ) ,
fee : 1.5 ,
quantity : 5 ,
type : 'BUY' ,
unitPrice : 100
unitPrice : 100 ,
valueInBaseCurrency : 512.3456
} as unknown as Activity ;
}
@ -136,6 +140,7 @@ function createHolding({
} ,
dateOfFirstActivity : new Date ( '2024-01-01' ) ,
grossPerformance : 100 ,
marketPrice : 400 ,
netPerformance : 90 ,
quantity : 5 ,
valueInBaseCurrency : 2000
@ -143,20 +148,24 @@ function createHolding({
}
function createPerformance ( {
netPerformance = 200 ,
netPerformancePercentage = 0.1 ,
netPerformancePercentageWithCurrencyEffect = 0.15
netPerformancePercentageWithCurrencyEffect = 0.15 ,
netPerformanceWithCurrencyEffect = 300
} : {
netPerformance? : number ;
netPerformancePercentage? : number ;
netPerformancePercentageWithCurrencyEffect? : number ;
netPerformanceWithCurrencyEffect? : number ;
} = { } ) : PortfolioPerformanceResponse [ 'performance' ] {
return {
netPerformance ,
netPerformancePercentage ,
netPerformancePercentageWithCurrencyEffect ,
netPerformanceWithCurrencyEffect ,
currentNetWorth : 3000 ,
currentValueInBaseCurrency : 2000 ,
dividendInBaseCurrency : 50 ,
netPerformance : 200 ,
netPerformanceWithCurrencyEffect : 300 ,
totalInvestment : 1700 ,
totalInvestmentValueWithCurrencyEffect : 1700
} ;
@ -246,9 +255,8 @@ function createPortfolioTableService({
}
describe ( 'PortfolioTableService' , ( ) = > {
// The tools of the model context protocol are the only callers, and an
// access of that type never grants the scope to read the monetary values,
// hence no table has a column with such a value
// A column with a monetary value is given only with the values, hence the
// names of these columns are given separately
describe ( 'getAccountsTableColumnNames' , ( ) = > {
it ( 'gives no column with a monetary value' , ( ) = > {
expect ( PortfolioTableService . getAccountsTableColumnNames ( ) ) . toEqual ( [
@ -263,6 +271,15 @@ describe('PortfolioTableService', () => {
} ) ;
} ) ;
describe ( 'getAccountsTableValueColumnNames' , ( ) = > {
it ( 'gives the columns with a monetary value' , ( ) = > {
expect ( PortfolioTableService . getAccountsTableValueColumnNames ( ) ) . toEqual ( [
'Balance' ,
'Value in Base Currency'
] ) ;
} ) ;
} ) ;
describe ( 'getActivitiesTableColumnNames' , ( ) = > {
it ( 'gives no column with a monetary value' , ( ) = > {
expect ( PortfolioTableService . getActivitiesTableColumnNames ( ) ) . toEqual ( [
@ -278,6 +295,14 @@ describe('PortfolioTableService', () => {
} ) ;
} ) ;
describe ( 'getActivitiesTableValueColumnNames' , ( ) = > {
it ( 'gives the columns with a quantity or with a monetary value' , ( ) = > {
expect (
PortfolioTableService . getActivitiesTableValueColumnNames ( )
) . toEqual ( [ 'Quantity' , 'Fee' , 'Value in Base Currency' ] ) ;
} ) ;
} ) ;
describe ( 'getHoldingsTableColumnNames' , ( ) = > {
it ( 'gives no column with a monetary value' , ( ) = > {
expect ( PortfolioTableService . getHoldingsTableColumnNames ( ) ) . toEqual ( [
@ -294,6 +319,16 @@ describe('PortfolioTableService', () => {
} ) ;
} ) ;
describe ( 'getHoldingsTableValueColumnNames' , ( ) = > {
it ( 'gives the columns with a quantity or with a monetary value' , ( ) = > {
expect ( PortfolioTableService . getHoldingsTableValueColumnNames ( ) ) . toEqual ( [
'Quantity' ,
'Market Price' ,
'Value in Base Currency'
] ) ;
} ) ;
} ) ;
describe ( 'getPerformanceTableColumnNames' , ( ) = > {
it ( 'gives no column with a monetary value' , ( ) = > {
expect ( PortfolioTableService . getPerformanceTableColumnNames ( ) ) . toEqual ( [
@ -304,6 +339,19 @@ describe('PortfolioTableService', () => {
} ) ;
} ) ;
describe ( 'getPerformanceTableValueColumnNames' , ( ) = > {
it ( 'gives the columns with a monetary value' , ( ) = > {
expect (
PortfolioTableService . getPerformanceTableValueColumnNames ( )
) . toEqual ( [
'Current Value in Base Currency' ,
'Asset Performance in Base Currency' ,
'Currency Performance in Base Currency' ,
'Net Performance in Base Currency'
] ) ;
} ) ;
} ) ;
describe ( 'getWatchlistTableColumnNames' , ( ) = > {
it ( 'gives no column with a monetary value' , ( ) = > {
expect ( PortfolioTableService . getWatchlistTableColumnNames ( ) ) . toEqual ( [
@ -331,9 +379,27 @@ describe('PortfolioTableService', () => {
expect ( result ) . not . toContain ( 'Cash Balance' ) ;
expect ( result ) . not . toContain ( '1000' ) ;
expect ( result ) . not . toContain ( '1800' ) ;
expect ( result ) . not . toContain ( '2000' ) ;
} ) ;
it ( 'gives the balance and the value with the values' , async ( ) = > {
const portfolioTableService = createPortfolioTableService ( {
accounts : [ createAccount ( ) ]
} ) ;
const result = await portfolioTableService . getAccountsTable ( {
userId : 'user-id' ,
withValues : true
} ) ;
const [ row ] = result . split ( '\n' ) . filter ( ( line ) = > {
return line . startsWith ( '| account-a-id' ) ;
} ) ;
expect ( row ) . toMatch ( /\| false \| 1000 \| 1800\.00 \|$/ ) ;
} ) ;
// The accountIds parameter of the tool takes the identifiers, hence the
// table has to give them
it ( 'gives the identifier of an account' , async ( ) = > {
@ -405,6 +471,40 @@ describe('PortfolioTableService', () => {
expect ( rowOfAapl ) . toContain ( ` | ${ encodeDataSource ( DataSource . YAHOO ) } | ` ) ;
expect ( rowOfGold ) . toContain ( ` | ${ DataSource . MANUAL } | ` ) ;
} ) ;
it ( 'gives no quantity, no fee and no value by default' , async ( ) = > {
const result = await createPortfolioTableService ( {
activities : [ createActivity ( ) ]
} ) . getActivitiesTable ( {
take : 50 ,
userCurrency : 'CHF' ,
userId : 'user-id'
} ) ;
const [ row ] = result . split ( '\n' ) . filter ( ( line ) = > {
return line . startsWith ( '| 2024-01-01' ) ;
} ) ;
expect ( result ) . not . toContain ( 'Quantity' ) ;
expect ( row ) . toMatch ( /\| 100 \| Account A \|$/ ) ;
} ) ;
it ( 'gives the quantity, the fee and the value with the values' , async ( ) = > {
const result = await createPortfolioTableService ( {
activities : [ createActivity ( ) ]
} ) . getActivitiesTable ( {
take : 50 ,
userCurrency : 'CHF' ,
userId : 'user-id' ,
withValues : true
} ) ;
const [ row ] = result . split ( '\n' ) . filter ( ( line ) = > {
return line . startsWith ( '| 2024-01-01' ) ;
} ) ;
expect ( row ) . toMatch ( /\| Account A \| 5 \| 1\.5 \| 512\.35 \|$/ ) ;
} ) ;
} ) ;
describe ( 'getHoldingsTable' , ( ) = > {
@ -412,10 +512,12 @@ describe('PortfolioTableService', () => {
holdings : PortfolioPosition [ ] ,
{
configuration ,
withDataSource
withDataSource ,
withValues
} : {
configuration? : Record < string , unknown > ;
withDataSource? : boolean ;
withValues? : boolean ;
} = { }
) {
return createPortfolioTableService ( {
@ -423,6 +525,7 @@ describe('PortfolioTableService', () => {
holdings
} ) . getHoldingsTable ( {
withDataSource ,
withValues ,
languageCode : DEFAULT_LANGUAGE_CODE ,
userId : 'user-id'
} ) ;
@ -516,13 +619,38 @@ describe('PortfolioTableService', () => {
expect ( firstRow ) . toContain ( 'AAPL' ) ;
expect ( secondRow ) . toContain ( 'MSFT' ) ;
} ) ;
it ( 'gives no quantity and no value by default' , async ( ) = > {
const result = await getHoldingsTable ( [ createHolding ( ) ] ) ;
const [ row ] = result . split ( '\n' ) . filter ( ( line ) = > {
return line . startsWith ( '| Name of AAPL' ) ;
} ) ;
expect ( result ) . not . toContain ( 'Quantity' ) ;
expect ( row ) . toMatch ( /\| 75\.000% \|$/ ) ;
} ) ;
it ( 'gives the quantity, the market price and the value with the values' , async ( ) = > {
const result = await getHoldingsTable ( [ createHolding ( ) ] , {
withValues : true
} ) ;
const [ row ] = result . split ( '\n' ) . filter ( ( line ) = > {
return line . startsWith ( '| Name of AAPL' ) ;
} ) ;
expect ( row ) . toMatch ( /\| 75\.000% \| 5 \| 400 \| 2000\.00 \|$/ ) ;
} ) ;
} ) ;
describe ( 'getPerformanceTable' , ( ) = > {
function getPerformanceTable (
parameters : Parameters < typeof createPortfolioTableService > [ 0 ] = { }
parameters : Parameters < typeof createPortfolioTableService > [ 0 ] = { } ,
withValues? : boolean
) {
return createPortfolioTableService ( parameters ) . getPerformanceTable ( {
withValues ,
dateRange : 'ytd' ,
userId : 'user-id'
} ) ;
@ -565,6 +693,36 @@ describe('PortfolioTableService', () => {
}
} ) ;
it ( 'gives the current value and the performance in base currency with the values' , async ( ) = > {
const result = await getPerformanceTable ( { } , true ) ;
const [ row ] = result . split ( '\n' ) . filter ( ( line ) = > {
return line . startsWith ( '| 10.000%' ) ;
} ) ;
expect ( row ) . toBe (
'| 10.000% | 5.000% | 15.000% | 2000.00 | 200.00 | 100.00 | 300.00 |'
) ;
} ) ;
it ( 'gives a currency performance in base currency of zero without a sign' , async ( ) = > {
const result = await getPerformanceTable (
{
performance : createPerformance ( {
netPerformance : 200.000001 ,
netPerformanceWithCurrencyEffect : 200
} )
} ,
true
) ;
const [ row ] = result . split ( '\n' ) . filter ( ( line ) = > {
return line . startsWith ( '| 10.000%' ) ;
} ) ;
expect ( row ) . toMatch ( /\| 200\.00 \| 0\.00 \| 200\.00 \|$/ ) ;
} ) ;
it ( 'tells that no performance is found if the chart is empty' , async ( ) = > {
const result = await getPerformanceTable ( { chart : [ ] } ) ;