ЧИСТВНДОХ на М – а почему бы и нет?

#АнатомияФункций – CustomFunctions



Всем привет!

В чат закинули задачку – можно ли посчитать экселевскую ЧИСТВНДОХ через PQ?

Ну было бы нельзя, не было бы поста )))



На вход нам подают список дат и список потоков, вычислить нужно внутреннюю ставку доходности, что в переводе на русский означает численно решить уравнение.

Получился такой код:

ЧИСТВНДОХ =(даты as list,потоки as list) as number =>

[ sum=(x)=>List.Sum(List.Transform(lst,(y)=>y{1}/Number.Power(1+x,y{0}))),

min = List.Min(даты),

zip= List.Zip({даты,потоки}),

lst = List.Buffer(List.Transform(zip,(x)=>{Duration.TotalDays(x{0}-min)/365,x{1}})),

base = Number.Sign(sum(0)),

gen=List.Generate( ()=>[i=0,a=0,b=100,p=(a+b)/2,s=sum(p),t=Number.Sign(s), f=t=base],

(x)=>x[i]<100 and x[b]-x[a]>0.0000001,

(x)=>[ i=x[i]+1,

a=if x[f] then x[p] else x[a],

b=if x[f] then x[b] else x[p],

p=(a+b)/2,

s=sum(p),

t=Number.Sign(s),

f=t=base],

(x)=>x[p]),

to = List.Last(gen)][to]


Где

sum – функция, вычисляющая сумму, которую мы должны обнулить

min – стартовая дата

zip, lst – получаем список для sum – по каждой дате получаем период в годах и сам поток

base – знак исходной суммы (нам это знание нужно, чтобы ловить переход через ноль)

gen – ну и запускаем генератор – обычный метод половинного деления (краевые значения и точность задал вручную, кому интересно может сделать параметрами или вообще вычислить)

to – результат последней итерации и есть искомое.



Как-то так – тут мало М, в основном численные методы.

Кому код покажется громоздким – можно переписать на рекурсию:

ЧИСТВНДОХ =(даты as list,потоки as list) as number =>

[ sum=(x)=>List.Sum(List.Transform(lst,(y)=>y{1}/Number.Power(1+x,y{0}))),

min = List.Min(даты),

zip= List.Zip({даты,потоки}),

lst = List.Buffer(List.Transform(zip,(x)=>{Duration.TotalDays(x{0}-min)/365,x{1}})),

base = Number.Sign(sum(0)),

func=(i,a,b)=> [ p=(a+b)/2, s=sum(p), t=Number.Sign(s), f=t=base,

out =if i<100 and (b-a)>0.0000001

then if f

then @func(i+1,p,b)

else @func(i+1,a,p) else p ][out],

to = func(0,0,100)][to]


Так немножко лаконичнее, суть та же, как в общем и скорость.

Ну и пример использования:

let

from = #table({"Даты","Потоки"},{{#date(2019,1,1),-1090},{#date(2019,12,31),123},{#date(2020,12,31),123},{#date(2021,12,31),123},{#date(2022,12,31),123},{#date(2023,12,31),1353}}),

to = ЧИСТВНДОХ(from[Даты],from[Потоки])

in

to //0.13257


Вроде работает и даже не вешает комп, пользуйтесь!



Надеюсь, было полезно.

Всех благ!

@buchlotnik