ข้ามไปยังเนื้อหา

ขอบเขตการชำระราคาและสูตรการคิดเงิน (Settlement Boundary & Monetary Recipes)

ข้อผิดพลาดคลาสสิกในวิศวกรรมการเงินคือความไม่สอดคล้องกันระหว่าง การประเมินมูลค่าแบบต่อเนื่อง (โมเดลคำนวณราคาด้วย f64) และ การชำระราคาแบบไม่ต่อเนื่อง (กระแสเงินสดที่ต้องลงบัญชีแยกประเภทในหน่วยเงินจริง)

SabiQuant นำเสนอสถาปัตยกรรม Settlement Boundary ที่ชัดเจนเพื่อขจัดปัญหาเหล่านี้

ปัญหาการปัดเศษ: รายส่วน vs. ยอดรวม (Component vs. Total Dilemma)

พิจารณาพอร์ตโฟลิโอที่มีการจ่ายคูปอง NN รายการ:

  1. การปัดเศษรายส่วน (Component Rounding): แต่ละกระแสเงินสดจะถูกปัดเศษเป็นหน่วยเงินย่อย (เช่น ทศนิยม 2 ตำแหน่งสำหรับ USD/THB) ก่อนนำมารวมกัน: Totalcomp=∑i=1nRound(ci)\text{Total}_{\text{comp}} = \sum_{i=1}^n \text{Round}(c_i)

  2. การปัดเศษยอดรวม (Total Rounding): นำยอดกระแสเงินสดดิบที่ยังไม่ปัดเศษมารวมกันจนเสร็จสิ้น แล้วจึงปัดเศษเฉพาะยอดรวมสุดท้าย: Totaltotal=Round(∑i=1nci)\text{Total}_{\text{total}} = \text{Round}\left(\sum_{i=1}^n c_i\right)

เมื่อทำธุรกรรมนับหมื่นรายการ จะพบว่า Totalcomp≠Totaltotal\text{Total}_{\text{comp}} \ne \text{Total}_{\text{total}} ส่งผลให้เกิดข้อผิดพลาดในการตรวจสอบบัญชี


รูปแบบสูตรการคิดเงินเชิงป้องกัน (Defensive Monetary Recipes)

SabiQuant มอบ MonetaryRecipe และนโยบาย SettlementPolicy เพื่อบังคับใช้อนุสัญญาการปัดเศษที่ตกลงกันในสัญญา:

use sabiquant::exact::{DecimalAmount, RoundingMode, SettlementPolicy};
fn main() -> Result<(), Box<dyn std::error::Error>> {
let items = vec![
DecimalAmount::from_str("10.004")?,
DecimalAmount::from_str("10.004")?,
DecimalAmount::from_str("10.004")?,
];
// นโยบาย A: Component Rounding (ปัดเศษทีละรายการก่อนรวม)
let sum_component: DecimalAmount = items.iter()
.map(|x| x.round(2, RoundingMode::HalfEven).unwrap())
.sum();
// นโยบาย B: Total Rounding (รวมยอดดิบก่อนแล้วปัดเศษปลายทาง)
let raw_sum: DecimalAmount = items.iter().cloned().sum();
let sum_total = raw_sum.round(2, RoundingMode::HalfEven)?;
println!("Sum of Component Rounding: {}", sum_component); // 30.00
println!("Rounded Total: {}", sum_total); // 30.01
// บังคับใช้นโยบายการชำระราคาที่ระบุไว้อย่างชัดเจน
let policy = SettlementPolicy::EnforceComponentRounding;
let finalized = policy.settle(&items, 2, RoundingMode::HalfEven)?;
println!("Finalized Settlement Amount: {}", finalized);
Ok(())
}