This standalone Python 3 program uses only the standard library and 50-digit Decimal arithmetic. Supply decimal strings, so 1% is "0.01". It returns the raw moments, full and conditional ratios, counts and an explicit zero-denominator status. The assertions test the hand calculations, target-dependent rankings and invalid inputs. It rejects missing values instead of silently changing n.
Run with python3 upr.py. The example prints all six series/target results followed by PASS. No Pine Script or external package is required.python
from decimal import Decimal, InvalidOperation, getcontext
import json
getcontext().prec = 50
D = Decimal
ZERO = D(0)
def finite_decimal(value):
if not isinstance(value, (str, Decimal)):
raise ValueError('Use decimal strings; missing values are not zero.')
try:
result = D(value)
except InvalidOperation as exc:
raise ValueError('Invalid decimal') from exc
if not result.is_finite():
raise ValueError('Returns and target must be finite.')
return result
def upr(values, target='0'):
r = [finite_decimal(v) for v in values]
t = finite_decimal(target)
if not r:
raise ValueError('At least one observation is required.')
excess = [v - t for v in r]
n = len(r)
above = sum(v > 0 for v in excess)
below = sum(v < 0 for v in excess)
positive_sum = sum((max(v, ZERO) for v in excess), ZERO)
negative_square_sum = sum((min(v, ZERO) ** 2 for v in excess), ZERO)
upper = positive_sum / n
lower = negative_square_sum / n
downside = lower.sqrt()
full = upper / downside if downside else None
conditional = (
(positive_sum / above) / (negative_square_sum / below).sqrt()
if above and below else None
)
status = ('finite' if downside else
'no observed shortfall' if upper else 'all equal to target')
return dict(n=n, above=above, below=below, equal=n-above-below,
upper=upper, lower=lower, downside=downside,
full=full, conditional=conditional, status=status,
sortino=(sum(excess, ZERO) / n / downside
if downside else None))
A = ['-0.04', '-0.02', '0', '0.01', '0.03', '0.06']
B = ['-0.01', '-0.01', '0.01', '0.01', '0.01', '0.02']
if __name__ == '__main__':
results = {f'{name}@{t}': upr(series, t)
for name, series in [('A', A), ('B', B)]
for t in ['0', '0.01', '0.03']}
a = upr(A)
assert a['upper'] == D('0.10') / 6
assert a['lower'] == D('0.0020') / 6
assert abs(a['full'] - D(5) / D(30).sqrt()) < D('1e-48')
assert upr(A, '0.01')['lower'] == D('0.0035') / 6
assert upr(A, '0.03')['lower'] == D('0.0087') / 6
assert upr(B)['full'] > a['full']
assert upr(A, '0.01')['full'] > upr(B, '0.01')['full']
assert upr(A[::-1]) == a
assert upr(A * 2)['full'] == a['full']
scaled = upr([str(D(v) * 100) for v in A])
assert abs(scaled['full'] - a['full']) < D('1e-48')
assert upr(['0', '-0.01'])['full'] == 0
assert upr(['0', '-0.01'])['conditional'] is None
assert upr(['0', '0'])['status'] == 'all equal to target'
assert upr(['0', '0.01'])['status'] == 'no observed shortfall'
assert upr(['0.01'])['full'] is None
assert upr(['-0.01'])['full'] == 0
assert upr(A, '-0.04')['full'] is None
assert upr(A, '0.06')['full'] == 0
for bad in [[], ['NaN'], ['Infinity'], [None], [''], [0.01]]:
try:
upr(bad)
except ValueError:
pass
else:
raise AssertionError(f'Expected rejection: {bad}')
print(json.dumps(results, default=str, indent=2))
print('PASS: formulas, target ranking, scale, order, repetition, boundaries')