galaxis-po/backend/tests/unit/test_tax_simulation.py
머니페니 12d235a1f1 feat: add 9 new modules - notification alerts, trading journal, position sizing, pension allocation, drawdown monitoring, benchmark dashboard, tax simulation, correlation analysis, parameter optimizer
Phase 1:
- Real-time signal alerts (Discord/Telegram webhook)
- Trading journal with entry/exit tracking
- Position sizing calculator (Fixed/Kelly/ATR)

Phase 2:
- Pension asset allocation (DC/IRP 70% risk limit)
- Drawdown monitoring with SVG gauge
- Benchmark dashboard (portfolio vs KOSPI vs deposit)

Phase 3:
- Tax benefit simulation (Korean pension tax rules)
- Correlation matrix heatmap
- Parameter optimizer with grid search + overfit detection
2026-03-29 10:03:08 +09:00

246 lines
8.3 KiB
Python

"""
Unit tests for tax simulation service.
"""
import pytest
from app.services.tax_simulation import (
calculate_tax_deduction,
calculate_pension_tax,
simulate_accumulation,
)
class TestCalculateTaxDeduction:
def test_low_income_deduction_rate(self):
"""총급여 5,500만원 이하 → 공제율 16.5%"""
result = calculate_tax_deduction(
annual_income=40_000_000,
contribution=9_000_000,
account_type="irp",
)
assert result["deduction_rate"] == 16.5
assert result["deductible_contribution"] == 9_000_000
assert result["tax_deduction"] == 9_000_000 * 0.165
def test_high_income_deduction_rate(self):
"""총급여 5,500만원 초과 → 공제율 13.2%"""
result = calculate_tax_deduction(
annual_income=80_000_000,
contribution=9_000_000,
account_type="irp",
)
assert result["deduction_rate"] == 13.2
assert result["tax_deduction"] == 9_000_000 * 0.132
def test_boundary_income_55m(self):
"""정확히 5,500만원은 16.5% 적용"""
result = calculate_tax_deduction(
annual_income=55_000_000,
contribution=5_000_000,
account_type="irp",
)
assert result["deduction_rate"] == 16.5
def test_contribution_exceeds_limit(self):
"""납입액이 900만원 한도 초과 시 900만원까지만 공제"""
result = calculate_tax_deduction(
annual_income=40_000_000,
contribution=12_000_000,
account_type="irp",
)
assert result["deductible_contribution"] == 9_000_000
assert result["tax_deduction"] == 9_000_000 * 0.165
def test_contribution_below_limit(self):
"""납입액이 한도 미만이면 실제 납입액 기준 공제"""
result = calculate_tax_deduction(
annual_income=40_000_000,
contribution=3_000_000,
account_type="irp",
)
assert result["deductible_contribution"] == 3_000_000
assert result["tax_deduction"] == 3_000_000 * 0.165
def test_dc_account_type(self):
"""DC 계좌도 동일 한도 적용 (DC+IRP 합산 900만원)"""
result = calculate_tax_deduction(
annual_income=60_000_000,
contribution=9_000_000,
account_type="dc",
)
assert result["deduction_rate"] == 13.2
assert result["deductible_contribution"] == 9_000_000
def test_zero_contribution(self):
result = calculate_tax_deduction(
annual_income=50_000_000,
contribution=0,
account_type="irp",
)
assert result["tax_deduction"] == 0
def test_result_structure(self):
result = calculate_tax_deduction(
annual_income=50_000_000,
contribution=5_000_000,
account_type="irp",
)
assert "annual_income" in result
assert "contribution" in result
assert "account_type" in result
assert "deduction_rate" in result
assert "deductible_contribution" in result
assert "tax_deduction" in result
assert "irp_limit" in result
class TestCalculatePensionTax:
def test_pension_tax_under_70(self):
"""70세 미만 연금소득세 5.5%"""
result = calculate_pension_tax(
withdrawal_amount=10_000_000,
withdrawal_type="pension",
age=65,
)
assert result["pension_tax_rate"] == 5.5
assert result["pension_tax"] == 10_000_000 * 0.055
def test_pension_tax_70_to_79(self):
"""70~79세 연금소득세 4.4%"""
result = calculate_pension_tax(
withdrawal_amount=10_000_000,
withdrawal_type="pension",
age=75,
)
assert result["pension_tax_rate"] == 4.4
assert result["pension_tax"] == pytest.approx(10_000_000 * 0.044)
def test_pension_tax_80_and_over(self):
"""80세 이상 연금소득세 3.3%"""
result = calculate_pension_tax(
withdrawal_amount=10_000_000,
withdrawal_type="pension",
age=85,
)
assert result["pension_tax_rate"] == 3.3
assert result["pension_tax"] == pytest.approx(10_000_000 * 0.033)
def test_pension_tax_boundary_70(self):
"""정확히 70세는 4.4% 적용"""
result = calculate_pension_tax(
withdrawal_amount=10_000_000,
withdrawal_type="pension",
age=70,
)
assert result["pension_tax_rate"] == 4.4
def test_pension_tax_boundary_80(self):
"""정확히 80세는 3.3% 적용"""
result = calculate_pension_tax(
withdrawal_amount=10_000_000,
withdrawal_type="pension",
age=80,
)
assert result["pension_tax_rate"] == 3.3
def test_lump_sum_tax(self):
"""일시금 수령 시 기타소득세 16.5%"""
result = calculate_pension_tax(
withdrawal_amount=10_000_000,
withdrawal_type="lump_sum",
age=65,
)
assert result["lump_sum_tax_rate"] == 16.5
assert result["lump_sum_tax"] == 10_000_000 * 0.165
def test_comparison_shows_savings(self):
"""연금 수령이 일시금보다 세금이 적음"""
result = calculate_pension_tax(
withdrawal_amount=10_000_000,
withdrawal_type="pension",
age=65,
)
assert result["tax_saving"] > 0
assert result["tax_saving"] == result["lump_sum_tax"] - result["pension_tax"]
def test_result_structure(self):
result = calculate_pension_tax(
withdrawal_amount=10_000_000,
withdrawal_type="pension",
age=65,
)
assert "withdrawal_amount" in result
assert "pension_tax_rate" in result
assert "pension_tax" in result
assert "lump_sum_tax_rate" in result
assert "lump_sum_tax" in result
assert "tax_saving" in result
class TestSimulateAccumulation:
def test_basic_accumulation(self):
"""기본 적립 시뮬레이션"""
result = simulate_accumulation(
monthly_contribution=500_000,
years=20,
annual_return=7.0,
tax_deduction_rate=16.5,
)
assert len(result["yearly_data"]) == 20
assert result["total_contribution"] == 500_000 * 12 * 20
assert result["final_value"] > result["total_contribution"]
def test_yearly_data_structure(self):
result = simulate_accumulation(
monthly_contribution=300_000,
years=5,
annual_return=5.0,
tax_deduction_rate=13.2,
)
first_year = result["yearly_data"][0]
assert "year" in first_year
assert "contribution" in first_year
assert "cumulative_contribution" in first_year
assert "investment_value" in first_year
assert "tax_deduction" in first_year
assert "cumulative_tax_deduction" in first_year
def test_tax_deduction_accumulates(self):
result = simulate_accumulation(
monthly_contribution=500_000,
years=3,
annual_return=5.0,
tax_deduction_rate=16.5,
)
yearly = result["yearly_data"]
annual_contribution = 500_000 * 12
deductible = min(annual_contribution, 9_000_000)
expected_deduction = deductible * 0.165
assert yearly[0]["tax_deduction"] == expected_deduction
assert yearly[2]["cumulative_tax_deduction"] == pytest.approx(
expected_deduction * 3, rel=1e-6
)
def test_zero_return(self):
result = simulate_accumulation(
monthly_contribution=100_000,
years=10,
annual_return=0.0,
tax_deduction_rate=16.5,
)
assert result["final_value"] == result["total_contribution"]
assert result["total_return"] == 0
def test_result_summary(self):
result = simulate_accumulation(
monthly_contribution=500_000,
years=20,
annual_return=7.0,
tax_deduction_rate=16.5,
)
assert "total_contribution" in result
assert "final_value" in result
assert "total_return" in result
assert "total_tax_deduction" in result
assert "yearly_data" in result