Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Estimate fully rented rent #60

Merged
merged 23 commits into from
Aug 18, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions src/components/assetPage/assetPagePropertyTab.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import { FC } from 'react'
import { useTranslation } from 'react-i18next'

import { useCurrencyValue } from 'src/hooks/useCurrencyValue'
import { useFullyRentedAPR } from 'src/hooks/useFullyRentedAPR'
import { UserRealtoken } from 'src/store/features/wallets/walletsSelector'
import { RealTokenRentalType } from 'src/types/RealToken'

Expand Down Expand Up @@ -44,6 +45,8 @@ export const AssetPagePropertyTab: FC<{
const propertyStories = realtoken.propertyStories
? tNumbers('integer', { value: realtoken.propertyStories })
: '-'
const fullyRentedAPR = useFullyRentedAPR(realtoken)
const fullyRentedAPRValue = tNumbers('percent', { value: fullyRentedAPR })

return (
<>
Expand Down Expand Up @@ -106,6 +109,10 @@ export const AssetPagePropertyTab: FC<{
label: t('annualYield'),
value: annualYield,
},
{
label: t('fullyRentedAPR'),
value: fullyRentedAPRValue,
},
]}
/>

Expand Down
3 changes: 2 additions & 1 deletion src/components/assetsView/views/AssetGrid.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import {
useCombobox,
} from '@mantine/core'

import FullyRentedAPRDisclaimer from 'src/components/commons/others/FullyRentedAPRDisclaimer'
import {
RWARealtoken,
UserRealtoken,
Expand All @@ -32,7 +33,6 @@ export const AssetGrid: FC<{ realtokens: (UserRealtoken | RWARealtoken)[] }> = (
}

const paginationOffers: (UserRealtoken | RWARealtoken)[] = useMemo(() => {
console.log({ realtokens: props.realtokens })
if (pageSize === Infinity) return props.realtokens
const start = (page - 1) * pageSize
const end = start + pageSize
Expand Down Expand Up @@ -123,6 +123,7 @@ export const AssetGrid: FC<{ realtokens: (UserRealtoken | RWARealtoken)[] }> = (
<Combobox.Options>{options}</Combobox.Options>
</Combobox.Dropdown>
</Combobox>
<FullyRentedAPRDisclaimer />
</Group>
</>
)
Expand Down
13 changes: 13 additions & 0 deletions src/components/assetsView/views/AssetTable.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import { Anchor, ScrollArea, Table } from '@mantine/core'
import moment from 'moment'

import { useCurrencyValue } from 'src/hooks/useCurrencyValue'
import { useFullyRentedAPR } from 'src/hooks/useFullyRentedAPR'
import { selectTransfersIsLoaded } from 'src/store/features/transfers/transfersSelector'
import {
RWARealtoken,
Expand Down Expand Up @@ -62,6 +63,7 @@ const AssetTableHeader: FC = () => {
) : null}
<Table.Th style={{ textAlign: 'right' }}>{t('ownedTokens')}</Table.Th>
<Table.Th style={{ textAlign: 'right' }}>{t('apr')}</Table.Th>
<Table.Th style={{ textAlign: 'right' }}>{t('fullyRentedAPR')}</Table.Th>
<Table.Th style={{ textAlign: 'right' }}>{t('weeklyRents')}</Table.Th>
<Table.Th style={{ textAlign: 'right' }}>{t('yearlyRents')}</Table.Th>
<Table.Th style={{ textAlign: 'right' }}>{t('rentedUnits')}</Table.Th>
Expand All @@ -85,6 +87,10 @@ const AssetTableRow: FC<{ value: UserRealtoken }> = (props) => {
const weeklyAmount = props.value.amount * props.value.netRentDayPerToken * 7
const yearlyAmount = props.value.amount * props.value.netRentYearPerToken
const totalInvestment = props.value.totalInvestment
const isAProperty = props.value.hasOwnProperty('rentStatus')
const fullyRentedAPR = isAProperty
? useFullyRentedAPR(props.value as UserRealtoken)
: null

return (
<Table.Tr>
Expand Down Expand Up @@ -121,6 +127,13 @@ const AssetTableRow: FC<{ value: UserRealtoken }> = (props) => {
<Table.Td style={{ textAlign: 'right', whiteSpace: 'nowrap' }}>
{t('percent', { value: props.value.annualPercentageYield })}
</Table.Td>
{isAProperty ? (
<Table.Td style={{ textAlign: 'right', whiteSpace: 'nowrap' }}>
{t('percent', { value: fullyRentedAPR })}
</Table.Td>
) : (
<Table.Td style={{ textAlign: 'right', whiteSpace: 'nowrap' }} />
)}
<Table.Td style={{ textAlign: 'right', whiteSpace: 'nowrap' }}>
{useCurrencyValue(weeklyAmount)}
</Table.Td>
Expand Down
12 changes: 12 additions & 0 deletions src/components/cards/AssetCard.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import { Badge, Card, Group } from '@mantine/core'
import moment from 'moment'

import { useCurrencyValue } from 'src/hooks/useCurrencyValue'
import { useFullyRentedAPR } from 'src/hooks/useFullyRentedAPR'
import { selectUserRentCalculation } from 'src/store/features/settings/settingsSelector'
import {
RWARealtoken,
Expand Down Expand Up @@ -57,6 +58,8 @@ const PropertyCardComponent: FC<PropertyCardProps> = (props) => {
const yearlyAmount = props.value.amount * props.value.netRentYearPerToken
const totalInvestment = props.value.totalInvestment

const fullyRentedAPR = useFullyRentedAPR(props.value)

return (
<Card
shadow={'sm'}
Expand Down Expand Up @@ -159,6 +162,15 @@ const PropertyCardComponent: FC<PropertyCardProps> = (props) => {
</div>
</div>

<div className={styles.groupApart}>
<div className={styles.textSm}>{t('fullyRentedEstimation')}*</div>
<div className={styles.textSm}>
{fullyRentedAPR
? tNumbers('percent', { value: fullyRentedAPR })
: '-'}
</div>
</div>

<div style={{ flex: '1 1 auto' }} />
<Divider height={1} my={'xs'} />

Expand Down
9 changes: 9 additions & 0 deletions src/components/cards/main/RentsCard.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,9 @@ import { useSelector } from 'react-redux'

import { Box, Card, Title } from '@mantine/core'

import { useGeneralFullyRentedAPR } from 'src/hooks/useFullyRentedAPR'
import {
selectOwnedRealtokens,
selectOwnedRealtokensAPY,
selectOwnedRealtokensRents,
} from 'src/store/features/wallets/walletsSelector'
Expand All @@ -16,18 +18,25 @@ export const RentsCard: FC = () => {

const rents = useSelector(selectOwnedRealtokensRents)
const apy = useSelector(selectOwnedRealtokensAPY)
const realtokens = useSelector(selectOwnedRealtokens)

// In Dollars
const dailyRents = rents.daily
const weeklyRents = rents.weekly
const monthlyRents = rents.monthly
const yearlyRents = rents.yearly

const fullyRentedAPR = useGeneralFullyRentedAPR(realtokens)

return (
<Card shadow={'sm'} radius={'md'} style={{ height: '100%' }}>
<Title order={4}>{t('title')}</Title>
<Box mx={'sm'}>
<PercentField label={t('apr')} value={apy} />
<PercentField
label={t('fullyRentedAPR')}
value={fullyRentedAPR / 100}
/>
<CurrencyField label={t('daily')} value={dailyRents} />
<CurrencyField label={t('weekly')} value={weeklyRents} />
<CurrencyField label={t('monthly')} value={monthlyRents} />
Expand Down
14 changes: 14 additions & 0 deletions src/components/commons/others/FullyRentedAPRDisclaimer.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
import { useTranslation } from 'react-i18next'

import { Text } from '@mantine/core'

const FullyRentedAPRDisclaimer = () => {
const { t } = useTranslation('common', { keyPrefix: 'disclaimer' })
return (
<Text style={{ fontSize: 'small', color: 'grey' }}>
*{t('fullyRentedAPR')}
</Text>
)
}

export default FullyRentedAPRDisclaimer
98 changes: 98 additions & 0 deletions src/hooks/useFullyRentedAPR.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
import { useMemo } from 'react'
import { useSelector } from 'react-redux'

import moment from 'moment'

import { selectUserRentCalculation } from 'src/store/features/settings/settingsSelector'
import { UserRealtoken } from 'src/store/features/wallets/walletsSelector'
import {
RentCalculation,
RentCalculationState,
} from 'src/types/RentCalculation'

const fullyRentedAPREstimation = (token: UserRealtoken) => {
// Case of fully rented property
if (token.rentedUnits === token.totalUnits) {
return token.annualPercentageYield
}

// Case of property with no rented unit but with APR (e.g. RMM or rent paid by inssurance)
if (token.rentedUnits === 0 && token.annualPercentageYield !== 0) {
return token.annualPercentageYield
}

if (token.history.length > 0) {
let propInfo = token.history[0].values
const history = token.history.map((h) => {
propInfo = { ...propInfo, ...h.values }
return propInfo
})

const previousAPR = history
.map((h) => {
if (
h.rentedUnits &&
h.rentedUnits !== 0 &&
h.netRentYear &&
h.tokenPrice
) {
return (
((h.netRentYear * token.totalUnits) /
(token.totalTokens * h.tokenPrice * h.rentedUnits)) *
100
)
}
return 0
})
.filter((apr) => apr !== undefined)

// Assuming the highest APR is the most accurate
return Math.max(...previousAPR, token.annualPercentageYield)
}

return Math.max(token.annualPercentageYield, 0)
}

export const useFullyRentedAPR = (token: UserRealtoken) => {
const rentCalculation = useSelector(selectUserRentCalculation)

const fullyRentedAPR = useMemo(() => {
const isDisabled = APRDisabled(rentCalculation, token)
if (isDisabled) return 0
return fullyRentedAPREstimation(token)
}, [token, rentCalculation])

return fullyRentedAPR
}

export const useGeneralFullyRentedAPR = (tokens: UserRealtoken[]) => {
const rentCalculation = useSelector(selectUserRentCalculation)
// Fully rented APR average using valuation ponderation
const fullyRentedAPR = useMemo(() => {
const totalValue = tokens.reduce((acc, token) => {
const isDisabled = APRDisabled(rentCalculation, token)
if (isDisabled) return acc
return acc + token.value
}, 0)
const totalAPR = tokens.reduce((acc, token) => {
const isDisabled = APRDisabled(rentCalculation, token)
if (isDisabled) return acc
return acc + token.value * fullyRentedAPREstimation(token)
}, 0)
return totalAPR / totalValue
}, [tokens, rentCalculation])

return fullyRentedAPR
}

const APRDisabled = (
rentCalculation: RentCalculation,
token: UserRealtoken,
) => {
const realtimeDate = moment(new Date(rentCalculation.date))
const rentStartDate = new Date(token.rentStartDate.date)
const isDisabled =
rentCalculation.state === RentCalculationState.Realtime &&
rentStartDate > realtimeDate.toDate()
return isDisabled
}
7 changes: 7 additions & 0 deletions src/i18next/locales/en/common.json
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,7 @@
"rentsCard": {
"title": "Rents",
"apr": "APR",
"fullyRentedAPR": "Fully rented APR *",
"daily": "Daily",
"weekly": "Weekly",
"monthly": "Monthly",
Expand Down Expand Up @@ -191,6 +192,7 @@
"rentedUnits": "Rented units",
"propertyValue": "Property value",
"rentStartDate": "Rent Start",
"fullyRentedEstimation": "Fully rented APR",
"rentNotStarted": "Rent not started yet",
"isRmmAvailable": "RMM",
"rentStatus": {
Expand All @@ -213,6 +215,7 @@
"unitPriceCost": "Unit price cost",
"unrealizedCapitalGain": "Capital gain",
"apr": "Yield",
"fullyRentedAPR":"Fully rented APR *",
"weeklyRents": "Weekly rents",
"yearlyRents": "Yearly rents",
"rentedUnits": "Rented units",
Expand Down Expand Up @@ -261,6 +264,7 @@
"grossRentMonth": "Gross monthly rent",
"netRentMonth": "Net monthly rent",
"annualYield": "Annual yield",
"fullyRentedAPR": "Fully rented APR *",
"constructionYear": "Construction year",
"propertyStories": "Number of stories",
"propertyUnits": "Number of units",
Expand Down Expand Up @@ -379,5 +383,8 @@
"initialTransfersLoader": {
"title": "Initial data loading",
"description": "Retrieving your transactions in progress. This loading can take some time depending on the number of transactions performed (10-20 seconds / 1000 transactions). On your next visits, only new transactions will be retrieved."
},
"disclaimer":{
"fullyRentedAPR": "This is a beta estimation done by RealT community. Please report any issues. Please note that is an indicative value and not a guarantee. RealT community or RealT does not take any responsibility for user actions based on this value."
}
}
7 changes: 7 additions & 0 deletions src/i18next/locales/fr/common.json
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,7 @@
"rentsCard": {
"title": "Loyers",
"apr": "Rendement annuel",
"fullyRentedAPR": "Rendement 100% loué *",
"daily": "Journaliers",
"weekly": "Hebdomadaires",
"monthly": "Mensuels",
Expand Down Expand Up @@ -191,6 +192,7 @@
"rentedUnits": "Logements loués",
"propertyValue": "Valeur de la propriété",
"rentStartDate": "Date du premier loyer",
"fullyRentedEstimation": "Rendement 100% loué",
"rentNotStarted": "Le loyer n'a pas débuté",
"isRmmAvailable": "RMM",
"rentStatus": {
Expand All @@ -213,6 +215,7 @@
"tokenPrice": "Prix du token",
"unitPriceCost": "Prix de revient",
"apr": "Rendement annuel",
"fullyRentedAPR":"Rendement 100% loué *",
"weeklyRents": "Loyer hebdo",
"yearlyRents": "Loyer annuel",
"rentedUnits": "Logements loués",
Expand Down Expand Up @@ -261,6 +264,7 @@
"grossRentMonth": "Loyer brut mensuel",
"netRentMonth": "Loyer net mensuel",
"annualYield": "Rendement annuel",
"fullyRentedAPR": "Rendement 100% loué *",
"constructionYear": "Année de construction",
"propertyStories": "Nombre d'étages",
"propertyUnits": "Nombre de logements",
Expand Down Expand Up @@ -380,5 +384,8 @@
"initialTransfersLoader": {
"title": "Chargement initial des données",
"description": "Récupération de vos transactions en cours. Ce chargement peut prendre un certain temps en fonction du nombre de transactions effecutées (10-20 secondes / 1000 transactions). Lors de vos prochaines visites, seul les nouvelles transactions seront récupérées."
},
"disclaimer":{
"fullyRentedAPR": "Cette estimation est en phase bêta et a été développée par la communauté RealT. Nous vous invitons à signaler tout problème éventuel. Les informations fournies sont à titre indicatif uniquement. La communauté RealT ou RealT ne peut être tenu responsable en cas de décision prise à partir de données inexactites."
}
}
2 changes: 2 additions & 0 deletions src/pages/asset/[assetId].tsx
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import { AssetPageMainTab } from 'src/components/assetPage/assetPageMainTab'
import { AssetPagePropertyTab } from 'src/components/assetPage/assetPagePropertyTab'
import { AssetPageTransfersTab } from 'src/components/assetPage/assetPageTransfersTab'
import { AssetPageYamStatisticsTab } from 'src/components/assetPage/assetPageYamStatisticsTab'
import FullyRentedAPRDisclaimer from 'src/components/commons/others/FullyRentedAPRDisclaimer'
import { selectIsLoading } from 'src/store/features/settings/settingsSelector'
import { selectTransfersIsLoaded } from 'src/store/features/transfers/transfersSelector'
import { selectAllUserRealtokens } from 'src/store/features/wallets/walletsSelector'
Expand Down Expand Up @@ -158,6 +159,7 @@ const AssetPage: NextPage = () => {
</Button>
</div>
</div>
<FullyRentedAPRDisclaimer />
</Flex>
)
}
Expand Down
2 changes: 0 additions & 2 deletions src/pages/yamStatistics.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -87,7 +87,6 @@ const YamStatisticsPage = () => {
}

const yamStatisticsPromise: Promise<YamStatistics[]> = useMemo(async () => {
console.log({ realtokens })
if (!realtokensWithYam.length) return Promise.resolve([])
const statsPromises = realtokensWithYam.map((realtoken) =>
GetYamStatistics({ realtoken }),
Expand All @@ -101,7 +100,6 @@ const YamStatisticsPage = () => {
yamStatisticsPromise.then((data) => {
setYamStatistics(data)
setIsLoading(false)
console.log({ data })
})
}, [yamStatisticsPromise])

Expand Down
Loading