use nautilus_core::UnixNanos;
use nautilus_model::{
enums::{OrderSide, OrderType},
identifiers::{AccountId, ClientOrderId, InstrumentId, PositionId, TradeId, VenueOrderId},
types::{Price, Quantity},
};
use uuid::Uuid;
use super::types::FillSnapshot;
const FNV_OFFSET_BASIS: u64 = 0xcbf2_9ce4_8422_2325;
const FNV_PRIME: u64 = 0x0100_0000_01b3;
#[must_use]
pub fn create_synthetic_venue_order_id(
fill: &FillSnapshot,
instrument_id: InstrumentId,
) -> VenueOrderId {
let hash_suffix = synthetic_fill_id_suffix("venue_order", fill, Some(instrument_id));
let venue_order_id_value = format!("S-{:x}-{hash_suffix:08x}", fill.ts_event);
VenueOrderId::new(&venue_order_id_value)
}
#[must_use]
pub fn create_synthetic_trade_id(fill: &FillSnapshot) -> TradeId {
let hash_suffix = synthetic_fill_id_suffix("trade", fill, None);
let trade_id_value = format!("S-{:x}-{hash_suffix:08x}", fill.ts_event);
TradeId::new(&trade_id_value)
}
#[must_use]
#[expect(clippy::too_many_arguments)]
pub fn create_inferred_reconciliation_trade_id(
account_id: AccountId,
instrument_id: InstrumentId,
client_order_id: ClientOrderId,
venue_order_id: Option<VenueOrderId>,
order_side: OrderSide,
order_type: OrderType,
filled_qty: Quantity,
last_qty: Quantity,
last_px: Price,
position_id: PositionId,
ts_last: UnixNanos,
) -> TradeId {
let mut seed = String::from("reconciliation-fill");
append_seed_part(&mut seed, account_id.as_str());
append_seed_part(&mut seed, &instrument_id.to_string());
append_seed_part(&mut seed, client_order_id.as_str());
append_seed_part(
&mut seed,
venue_order_id.as_ref().map_or("", |value| value.as_ref()),
);
append_seed_part(&mut seed, order_side.as_ref());
append_seed_part(&mut seed, order_type.as_ref());
append_seed_part(&mut seed, &filled_qty.to_string());
append_seed_part(&mut seed, &last_qty.to_string());
append_seed_part(&mut seed, &last_px.to_string());
append_seed_part(&mut seed, position_id.as_str());
append_seed_part(&mut seed, &ts_last.as_u64().to_string());
TradeId::new(deterministic_uuid_from_seed("reconciliation-fill", &seed))
}
#[must_use]
#[expect(clippy::too_many_arguments)]
pub fn create_position_reconciliation_venue_order_id(
account_id: AccountId,
instrument_id: InstrumentId,
order_side: OrderSide,
order_type: OrderType,
quantity: Quantity,
price: Option<Price>,
venue_position_id: Option<PositionId>,
tag: Option<&str>,
ts_last: UnixNanos,
) -> VenueOrderId {
let mut seed = String::from("position-reconciliation-order");
append_seed_part(&mut seed, account_id.as_str());
append_seed_part(&mut seed, &instrument_id.to_string());
append_seed_part(&mut seed, order_side.as_ref());
append_seed_part(&mut seed, order_type.as_ref());
append_seed_part(&mut seed, &quantity.to_string());
append_seed_part(
&mut seed,
&price.map_or_else(String::new, |value| value.to_string()),
);
append_seed_part(
&mut seed,
&venue_position_id.map_or_else(String::new, |value| value.to_string()),
);
append_seed_part(&mut seed, tag.unwrap_or(""));
append_seed_part(&mut seed, &ts_last.as_u64().to_string());
VenueOrderId::new(deterministic_uuid_from_seed(
"position-reconciliation-order",
&seed,
))
}
fn synthetic_fill_id_suffix(
namespace: &str,
fill: &FillSnapshot,
instrument_id: Option<InstrumentId>,
) -> u32 {
let mut hash: u64 = FNV_OFFSET_BASIS;
update_synthetic_fill_hash(&mut hash, namespace.as_bytes());
if let Some(instrument_id) = instrument_id {
update_synthetic_fill_hash(&mut hash, instrument_id.to_string().as_bytes());
}
update_synthetic_fill_hash(&mut hash, &fill.ts_event.to_le_bytes());
update_synthetic_fill_hash(&mut hash, fill.venue_order_id.as_str().as_bytes());
update_synthetic_fill_hash(&mut hash, order_side_tag(fill.side).as_bytes());
update_synthetic_fill_hash(&mut hash, fill.qty.to_string().as_bytes());
update_synthetic_fill_hash(&mut hash, fill.px.to_string().as_bytes());
hash as u32
}
fn deterministic_uuid_from_seed(namespace: &str, seed: &str) -> String {
let primary = stable_hash64(namespace.as_bytes(), &[seed.as_bytes()]);
let secondary = stable_hash64(b"uuid-alt", &[namespace.as_bytes(), seed.as_bytes()]);
let mut bytes = [0_u8; 16];
bytes[..8].copy_from_slice(&primary.to_be_bytes());
bytes[8..].copy_from_slice(&secondary.to_be_bytes());
bytes[6] = (bytes[6] & 0x0f) | 0x50;
bytes[8] = (bytes[8] & 0x3f) | 0x80;
Uuid::from_bytes(bytes).to_string()
}
fn stable_hash64(namespace: &[u8], parts: &[&[u8]]) -> u64 {
let mut hash: u64 = FNV_OFFSET_BASIS;
update_synthetic_fill_hash(&mut hash, namespace);
for part in parts {
update_synthetic_fill_hash(&mut hash, part);
}
hash
}
fn append_seed_part(seed: &mut String, value: &str) {
seed.push('|');
seed.push_str(value);
}
fn update_synthetic_fill_hash(hash: &mut u64, bytes: &[u8]) {
for &byte in bytes {
*hash ^= byte as u64;
*hash = hash.wrapping_mul(FNV_PRIME);
}
*hash ^= 0xff;
*hash = hash.wrapping_mul(FNV_PRIME);
}
fn order_side_tag(side: OrderSide) -> &'static str {
match side {
OrderSide::Buy => "BUY",
OrderSide::Sell => "SELL",
_ => "UNSPECIFIED",
}
}