Files

81 lines
2.6 KiB
Python

"""Shared decimal helpers for exchange constraints and generated strategy source."""
from __future__ import annotations
from decimal import Decimal, ROUND_DOWN, ROUND_HALF_UP
from typing import Any
_STEP_EDGE_RELATIVE_TOLERANCE = Decimal("1e-12")
def to_decimal(value: Any) -> Decimal:
"""Convert through ``str`` so binary float noise is not expanded further."""
try:
return Decimal(str(value))
except Exception:
return Decimal("0")
def floor_decimal_to_step(value: Any, step: Any) -> Decimal:
"""Floor a positive value to a step while absorbing float-only edge noise.
Order quantities commonly cross float-based ledgers before reaching an
exchange adapter. A mathematically exact step such as ``0.0001`` can then
arrive as ``0.00009999999999999994``. Blind ``ROUND_DOWN`` turns that into
the previous step (often zero). Values within one part per trillion of an
integer step are therefore snapped to that integer before flooring; real
below-step quantities remain below the boundary.
"""
number = to_decimal(value)
increment = to_decimal(step)
if number <= 0:
return Decimal("0")
if increment <= 0:
return number
try:
units = number / increment
nearest = units.to_integral_value(rounding=ROUND_HALF_UP)
tolerance = max(
_STEP_EDGE_RELATIVE_TOLERANCE,
abs(units) * _STEP_EDGE_RELATIVE_TOLERANCE,
)
if nearest > 0 and abs(units - nearest) <= tolerance:
units = nearest
else:
units = units.to_integral_value(rounding=ROUND_DOWN)
return units * increment
except Exception:
return Decimal("0")
def clean_generated_number(value: Any, *, decimal_places: int = 12) -> float:
"""Return a stable, bounded-precision float for generated Python source."""
places = min(18, max(0, int(decimal_places)))
number = to_decimal(value)
quantum = Decimal("1").scaleb(-places)
try:
cleaned = number.quantize(quantum, rounding=ROUND_HALF_UP)
except Exception:
cleaned = number
if cleaned == 0:
return 0.0
return float(cleaned)
def format_decimal(value: Any, *, decimal_places: int = 12) -> str:
"""Format a user-facing number without scientific notation or zero padding."""
cleaned = clean_generated_number(value, decimal_places=decimal_places)
text = format(cleaned, f".{min(18, max(0, int(decimal_places)))}f")
if "." in text:
text = text.rstrip("0").rstrip(".")
return text or "0"
__all__ = [
"clean_generated_number",
"floor_decimal_to_step",
"format_decimal",
"to_decimal",
]