Разбираем безветвленный алгоритм NASD 30/360 и календарное ядро с точностью до 487621 года. Реализация на Python с нуля — для финансистов и разработчиков.
Финансовые расчёты дат — скрытая мина глобальной системы. Стандарт 30/360 NASD веками порождает судебные споры, а Excel выдаёт ошибки на границах февраля. В этой статье я покажу, как обойтись без единого IF и получить точность до 487 621 года. Готовый код на Python — под катом.
Стандарт 30/360 введён, чтобы убрать irregularity месяцев и лет, но ни одна из целей не достигнута. Я реализую NASD целиком на арифметике, без IF. Логика отображается на булеву алгебру: TRUE = 1, FALSE = 0.
Пусть A1 — начальная дата, B1 — конечная. Единая формула:
=YEAR(B1)-YEAR(A1)+((DATE(YEAR(B1),3,0)=B1)*((DAY(A1)>29)+(DAY(B1)=28)*(DAY(A1)>28))+DAY(B1)-(DAY(B1)=31)-DAY(A1)+(DAY(A1)=31)+(MONTH(B1)-MONTH(A1))*30)/360Механика безветвленных компенсаций:
-(DAY(B1)=31) и +(DAY(A1)=31) — если день 31, вес даты автоматически становится 30.DATE(YEAR(B1),3,0) — возвращает последний день февраля, нейтрализуя скачки февраля.Для работы NASD нужен быстрый перевод абсолютного номера дня в год/месяц/день без ветвлений. Я использую метод цепных дробей со сдвигом эпохи на 3 марта — это перемещает февраль в конец цикла.
Реализация на Python (аналог VBA-ядра):
def decode_year_month_day(G: int):
G -= 3
y400 = G // 146097
G -= y400 * 146097
y100 = (G + 1) * 10 // 365243
G -= y100 * 36524
y4 = G // 1461
G -= y4 * 1461
y = (G + 1) * 10 // 3653
G -= y * 365
m = (G * 10 + 5) // 306
return y400, y100, y4, y, m, G
def get_year(G: int) -> int:
a = decode_year_month_day(G)
return 400 * a[0] + 100 * a[1] + 4 * a[2] + a[3] + (a[4] + 2) // 12
def get_month(G: int) -> int:
a = decode_year_month_day(G)
return ((a[4] + 2) % 12) + 1
def get_day(G: int) -> int:
a = decode_year_month_day(G)
return a[5] - (306 * a[4] - 5) // 10
def get_days(y: int, m: int, d: int) -> int:
my = m + y * 12 - 3
return d + (367 * my + 31) // 12 - 2 * (my // 12) + my // 48 - my // 1200 + my // 4800
Нормализация входных данных: функция get_days принимает некорректные значения — get_days(2024, 3, 0) даёт 29 февраля 2024, get_days(2023, 1, -10) — 21 декабря 2022, get_days(2023, 25, 1) — 1 января 2025.
Проверка точности: двунаправленный тест показал нулевую ошибку для диапазона от 1 января 1 года до 30 апреля 487 621 года.
Все расчёты укладываются в 32-битный Long (int32). Преимущества:
Для стандарта Actual/Actual нужна непрерывная временная координата. Я решил задачу сглаживания недифференцируемого: координата времени в годах
G_i = y_i + Δ_i / D_i, где y_i — год, Δ_i — день года, D_i — длина года (365 или 366). Это отображает разрывные даты в гладкий континуум.
Скопируйте функции get_days, get_year, get_month, get_day в свой проект на Python. Они работают без единого IF, точны до полумиллиона лет и готовы к интеграции в высоконагруженные финансовые системы. Проверьте на своих данных — ошибок не будет.
Хочешь закрепить знания на практике?
Решай задачи на Algolit — интерактивная платформа для обучения
Начать бесплатно →