fix: restore manual confirm_duplicate compatibility and precision safeguards

Co-authored-by: genichin <4282227+genichin@users.noreply.github.com>
This commit is contained in:
copilot-swe-agent[bot]
2026-09-09 02:53:15 +00:00
committed by GitHub
co-authored by genichin
parent a71c91f6ef
commit aa40c7fdff
4 changed files with 113 additions and 15 deletions
+34 -11
View File
@@ -21,6 +21,7 @@ logger = logging.getLogger(__name__)
_LEDGER_NUMERIC_SCALE = 8
_LEDGER_MAX_INTEGER_DIGITS = 12
_LEDGER_QUANT = Decimal("0.00000001")
_MAX_SAFE_DOUBLE_FOR_8DP = Decimal("45035996")
_SELECT_USER = """
SELECT
@@ -2039,6 +2040,21 @@ class PostgresPortfolioRepository:
raise ValueError(f"{field_name} must be a decimal") from None
return cls._validate_ledger_decimal(decimal_value, field_name=field_name, allow_zero=allow_zero)
@staticmethod
def _coerce_portfolio_snapshot_decimal(value: Any, *, field_name: str) -> Decimal:
try:
decimal_value = Decimal(str(value))
except (InvalidOperation, ValueError, TypeError):
raise ValueError(f"{field_name} must be a decimal") from None
if decimal_value.is_nan() or decimal_value.is_infinite():
raise ValueError(f"{field_name} must be a finite decimal")
return decimal_value
@staticmethod
def _ensure_safe_double_snapshot_precision(value: Decimal, *, field_name: str) -> None:
if value.copy_abs() > _MAX_SAFE_DOUBLE_FOR_8DP:
raise ValueError(f"{field_name} exceeds double snapshot precision at 8 decimal places")
def record_manual_ledger(
self,
portfolio_id: str,
@@ -2068,15 +2084,20 @@ class PostgresPortfolioRepository:
if row is None:
raise ValueError("포트폴리오를 찾을 수 없습니다")
portfolio = self._row_to_portfolio(row)
cash = self._coerce_ledger_decimal(portfolio.cash_balance, field_name="cash_balance", allow_zero=True)
qty = self._coerce_ledger_decimal(portfolio.asset_quantity, field_name="asset_quantity", allow_zero=True)
buffer = self._coerce_ledger_decimal(portfolio.profit_buffer, field_name="profit_buffer", allow_zero=True)
asset_price = self._coerce_ledger_decimal(portfolio.price, field_name="asset_price", allow_zero=True)
cash = self._coerce_portfolio_snapshot_decimal(portfolio.cash_balance, field_name="cash_balance")
qty = self._coerce_portfolio_snapshot_decimal(portfolio.asset_quantity, field_name="asset_quantity")
buffer = self._coerce_portfolio_snapshot_decimal(portfolio.profit_buffer, field_name="profit_buffer")
asset_price = self._coerce_portfolio_snapshot_decimal(portfolio.price, field_name="asset_price")
ledger_price = self._coerce_ledger_decimal(price or 0, field_name="price", allow_zero=True)
ledger_quantity = self._coerce_ledger_decimal(quantity or 0, field_name="quantity", allow_zero=True)
fee_f = self._coerce_ledger_decimal(fee or 0, field_name="fee", allow_zero=True)
tax_f = self._coerce_ledger_decimal(tax or 0, field_name="tax", allow_zero=True)
amount_f = self._coerce_ledger_decimal(amount or 0, field_name="amount", allow_zero=True)
self._ensure_safe_double_snapshot_precision(amount_f, field_name="amount")
self._ensure_safe_double_snapshot_precision(ledger_price, field_name="price")
self._ensure_safe_double_snapshot_precision(ledger_quantity, field_name="quantity")
self._ensure_safe_double_snapshot_precision(fee_f, field_name="fee")
self._ensure_safe_double_snapshot_precision(tax_f, field_name="tax")
ledger_executed_at = executed_at or datetime.now(timezone.utc)
if ledger_executed_at.tzinfo is None:
ledger_executed_at = ledger_executed_at.replace(tzinfo=timezone.utc)
@@ -2163,19 +2184,21 @@ class PostgresPortfolioRepository:
""",
(portfolio_id, target_ledger_entry_id),
)
reverse_target = self._row_to_ledger_entry(cur.fetchone())
reverse_target_row = cur.fetchone()
if reverse_target_row is None:
raise ValueError("LEDGER_ENTRY_NOT_FOUND")
cur.execute(
"SELECT 1 FROM portfolio_ledger WHERE portfolio_id = %s AND side = 'reversal' AND target_ledger_entry_id = %s LIMIT 1",
(portfolio_id, target_ledger_entry_id),
)
if cur.fetchone() is not None:
raise ValueError("LEDGER_ENTRY_ALREADY_REVERSED")
target_action = (reverse_target.action or "").upper()
amount_f = self._coerce_ledger_decimal(reverse_target.price, field_name="amount", allow_zero=True)
ledger_price = self._coerce_ledger_decimal(reverse_target.price, field_name="price", allow_zero=True)
ledger_quantity = self._coerce_ledger_decimal(reverse_target.quantity, field_name="quantity", allow_zero=True)
fee_f = self._coerce_ledger_decimal(reverse_target.fee_amount, field_name="fee", allow_zero=True)
tax_f = self._coerce_ledger_decimal(reverse_target.tax_amount, field_name="tax", allow_zero=True)
target_action = str(reverse_target_row[4] or "").upper()
amount_f = self._coerce_ledger_decimal(reverse_target_row[6], field_name="amount", allow_zero=True)
ledger_price = self._coerce_ledger_decimal(reverse_target_row[6], field_name="price", allow_zero=True)
ledger_quantity = self._coerce_ledger_decimal(reverse_target_row[5], field_name="quantity", allow_zero=True)
fee_f = self._coerce_ledger_decimal(reverse_target_row[7], field_name="fee", allow_zero=True)
tax_f = self._coerce_ledger_decimal(reverse_target_row[8], field_name="tax", allow_zero=True)
if target_action == "BUY":
cash += (ledger_quantity * ledger_price + fee_f + tax_f).quantize(_LEDGER_QUANT, rounding=ROUND_HALF_UP)
qty -= ledger_quantity