use super::{PreTradeContext, Reject, RejectCode, RejectScope, Rejects};
use crate::Mutations;
pub trait PreTradePolicy<O, R> {
fn name(&self) -> &str;
fn perform_pre_trade_check(
&self,
ctx: &PreTradeContext,
order: &O,
mutations: &mut Mutations,
) -> Result<(), Rejects>;
fn apply_execution_report(&self, report: &R) -> bool;
}
pub(crate) fn request_field_access_pre_trade_reject(
policy_name: &str,
err: &crate::RequestFieldAccessError,
) -> Reject {
Reject::new(
policy_name,
RejectScope::Order,
RejectCode::MissingRequiredField,
"failed to access required field",
err.to_string(),
)
}
#[cfg(test)]
mod tests {
use crate::core::{
ExecutionReportOperation, FinancialImpact, OrderOperation, WithExecutionReportOperation,
WithFinancialImpact,
};
use crate::param::{AccountId, Asset, Fee, Pnl, Quantity, Side, TradeAmount};
use crate::pretrade::{RejectCode, RejectScope, Rejects};
use crate::{Mutations, RequestFieldAccessError};
use super::{request_field_access_pre_trade_reject, PreTradeContext, PreTradePolicy};
type TestOrder = OrderOperation;
type TestReport = WithExecutionReportOperation<WithFinancialImpact<()>>;
struct MainPolicyNoop;
impl PreTradePolicy<TestOrder, TestReport> for MainPolicyNoop {
fn name(&self) -> &str {
"MainPolicyNoop"
}
fn perform_pre_trade_check(
&self,
_ctx: &PreTradeContext,
_order: &TestOrder,
_mutations: &mut Mutations,
) -> Result<(), Rejects> {
Ok(())
}
fn apply_execution_report(&self, _report: &TestReport) -> bool {
false
}
}
#[test]
fn apply_execution_report_hook_returns_false_for_noop_main_policy() {
let report = WithExecutionReportOperation {
inner: WithFinancialImpact {
inner: (),
financial_impact: FinancialImpact {
pnl: Pnl::from_str("0").expect("pnl must be valid"),
fee: Fee::ZERO,
},
},
operation: ExecutionReportOperation {
instrument: crate::Instrument::new(
Asset::new("AAPL").expect("asset code must be valid"),
Asset::new("USD").expect("asset code must be valid"),
),
account_id: AccountId::from_u64(99224416),
side: Side::Buy,
},
};
assert!(!MainPolicyNoop.apply_execution_report(&report));
}
#[test]
fn required_trait_methods_can_be_invoked_without_side_effects() {
let order = OrderOperation {
instrument: crate::Instrument::new(
Asset::new("AAPL").expect("asset code must be valid"),
Asset::new("USD").expect("asset code must be valid"),
),
account_id: AccountId::from_u64(99224416),
side: Side::Buy,
trade_amount: TradeAmount::Quantity(
Quantity::from_str("1").expect("quantity must be valid"),
),
price: None,
};
let mut mutations = Mutations::new();
assert_eq!(MainPolicyNoop.name(), "MainPolicyNoop");
let result =
MainPolicyNoop.perform_pre_trade_check(&PreTradeContext::new(), &order, &mut mutations);
assert!(mutations.is_empty());
assert!(result.is_ok());
}
#[test]
fn request_field_access_error_is_mapped_to_reject_payload() {
let err = RequestFieldAccessError::new("instrument");
let reject = request_field_access_pre_trade_reject("TestPolicy", &err);
assert_eq!(reject.policy, "TestPolicy");
assert_eq!(reject.scope, RejectScope::Order);
assert_eq!(reject.code, RejectCode::MissingRequiredField);
assert_eq!(reject.reason, "failed to access required field");
assert_eq!(reject.details, "failed to access field 'instrument'");
}
}