mirror of
https://github.com/OpenByteInc/QuantDinger.git
synced 2026-09-28 23:32:55 +08:00
81 lines
2.6 KiB
Python
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",
|
|
]
|