fix corporate action replay and trade filtering
This commit is contained in:
@@ -1332,6 +1332,8 @@ def create_corporate_action(portfolio_id: str, payload: CorporateActionRequest,
|
|||||||
return CorporateActionResponse(**saved.__dict__)
|
return CorporateActionResponse(**saved.__dict__)
|
||||||
except ValueError as exc:
|
except ValueError as exc:
|
||||||
code = str(exc)
|
code = str(exc)
|
||||||
|
if code == "portfolio not found":
|
||||||
|
raise HTTPException(status_code=404, detail="Portfolio not found") from exc
|
||||||
raise HTTPException(status_code=409 if code.startswith(("IDEMPOTENCY", "CORPORATE_ACTION_")) else 400, detail=code) from exc
|
raise HTTPException(status_code=409 if code.startswith(("IDEMPOTENCY", "CORPORATE_ACTION_")) else 400, detail=code) from exc
|
||||||
finally:
|
finally:
|
||||||
_close_repo(repo)
|
_close_repo(repo)
|
||||||
|
|||||||
@@ -290,6 +290,7 @@ def corporate_action_portfolio(
|
|||||||
"""Record an auditable stock consolidation without a trade."""
|
"""Record an auditable stock consolidation without a trade."""
|
||||||
from datetime import date
|
from datetime import date
|
||||||
from decimal import Decimal
|
from decimal import Decimal
|
||||||
|
import uuid
|
||||||
from app.domain.corporate_actions import calculate_stock_consolidation, canonical_payload_hash
|
from app.domain.corporate_actions import calculate_stock_consolidation, canonical_payload_hash
|
||||||
from app.domain.models import CorporateAction
|
from app.domain.models import CorporateAction
|
||||||
if action_type != "stock_consolidation":
|
if action_type != "stock_consolidation":
|
||||||
@@ -312,7 +313,7 @@ def corporate_action_portfolio(
|
|||||||
payload = {"action_type": action_type, "effective_date": day.isoformat(), "quantity_before": str(quantity_before), "quantity_after": str(quantity_after), "evidence_ref": evidence_ref, "confirm_daemon_managed": False}
|
payload = {"action_type": action_type, "effective_date": day.isoformat(), "quantity_before": str(quantity_before), "quantity_after": str(quantity_after), "evidence_ref": evidence_ref, "confirm_daemon_managed": False}
|
||||||
digest = canonical_payload_hash(payload)
|
digest = canonical_payload_hash(payload)
|
||||||
calc = calculate_stock_consolidation(Decimal(str(quantity_before)), Decimal(str(quantity_after)), Decimal("0"))
|
calc = calculate_stock_consolidation(Decimal(str(quantity_before)), Decimal(str(quantity_after)), Decimal("0"))
|
||||||
action = CorporateAction("", portfolio_id, portfolio.asset_id, action_type, day, calc.quantity_before, calc.quantity_after, calc.average_price_before, calc.average_price_after, calc.total_cost_basis, Decimal(str(portfolio.cash_balance)), Decimal(str(portfolio.profit_buffer)), evidence_ref, idempotency_key, digest, 0, user)
|
action = CorporateAction(str(uuid.uuid4()), portfolio_id, portfolio.asset_id, action_type, day, calc.quantity_before, calc.quantity_after, calc.average_price_before, calc.average_price_after, calc.total_cost_basis, Decimal(str(portfolio.cash_balance)), Decimal(str(portfolio.profit_buffer)), evidence_ref, idempotency_key, digest, 0, user)
|
||||||
return repository.record_corporate_action(portfolio_id, action=action, idempotency_key=idempotency_key, payload_hash=digest)
|
return repository.record_corporate_action(portfolio_id, action=action, idempotency_key=idempotency_key, payload_hash=digest)
|
||||||
detail = _with_service_and_repo(ctx, _callback)
|
detail = _with_service_and_repo(ctx, _callback)
|
||||||
render_output(ctx.obj.console, ctx.obj.output_format, detail)
|
render_output(ctx.obj.console, ctx.obj.output_format, detail)
|
||||||
|
|||||||
@@ -1637,6 +1637,21 @@ class PostgresPortfolioRepository:
|
|||||||
row = cur.fetchone()
|
row = cur.fetchone()
|
||||||
if row is None:
|
if row is None:
|
||||||
raise ValueError("portfolio not found")
|
raise ValueError("portfolio not found")
|
||||||
|
cur.execute("SELECT corporate_action_id, portfolio_id, asset_id, action_type, effective_date, actor_user_id, evidence_ref, idempotency_key, payload_hash, quantity_before, quantity_after, average_price_before, average_price_after, total_cost_basis, cash_balance, profit_buffer, ledger_entry_id, recorded_at FROM portfolio_corporate_actions WHERE portfolio_id=%s AND idempotency_key=%s", (portfolio_id, idempotency_key))
|
||||||
|
prior = cur.fetchone()
|
||||||
|
if prior:
|
||||||
|
if prior[8] != payload_hash:
|
||||||
|
raise ValueError("IDEMPOTENCY_KEY_REUSE")
|
||||||
|
(action.corporate_action_id, action.portfolio_id, action.asset_id, action.action_type,
|
||||||
|
action.effective_date, action.actor_user_id, action.evidence_ref,
|
||||||
|
action.idempotency_key, action.payload_hash, action.quantity_before,
|
||||||
|
action.quantity_after, action.average_price_before, action.average_price_after,
|
||||||
|
action.total_cost_basis, action.cash_balance, action.profit_buffer,
|
||||||
|
ledger_id, action.recorded_at) = prior
|
||||||
|
action.corporate_action_id = str(action.corporate_action_id)
|
||||||
|
action.ledger_entry_id = int(ledger_id)
|
||||||
|
action.idempotent_replay = True
|
||||||
|
return action
|
||||||
if row[4] != "enabled":
|
if row[4] != "enabled":
|
||||||
raise ValueError("CORPORATE_ACTION_PORTFOLIO_DISABLED")
|
raise ValueError("CORPORATE_ACTION_PORTFOLIO_DISABLED")
|
||||||
if row[3] != action.asset_id:
|
if row[3] != action.asset_id:
|
||||||
@@ -1720,7 +1735,7 @@ class PostgresPortfolioRepository:
|
|||||||
boundaries (start of day / start of next day).
|
boundaries (start of day / start of next day).
|
||||||
"""
|
"""
|
||||||
self.ensure_schema()
|
self.ensure_schema()
|
||||||
clauses = ["portfolio_id = %s"]
|
clauses = ["portfolio_id = %s", "side IN ('buy','sell')"]
|
||||||
params: list[object] = [portfolio_id]
|
params: list[object] = [portfolio_id]
|
||||||
|
|
||||||
if date is not None:
|
if date is not None:
|
||||||
|
|||||||
Reference in New Issue
Block a user