openpit 0.8.1

Embeddable pre-trade risk SDK
Documentation
// Copyright The Pit Project Owners. All rights reserved.
// SPDX-License-Identifier: Apache-2.0
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//
// Please see https://openpit.dev and the OWNERS file for details.

#[cfg(test)]
use crate::core::{AccountBlockHandle, AccountCurrencies, BlockedAccounts};
use crate::core::{AccountGroups, AccountGroupsHandle, Accounts, GroupLookup};
use crate::param::{AccountGroupId, AccountId, Asset};
use crate::storage::{self, StorageBuilder};

/// Context of the current post-trade (execution-report) operation.
///
/// Exposes lazy account-group accessors and account-currency access for the
/// report's account. Unlike
/// [`PreTradeContext`](crate::pretrade::PreTradeContext) and
/// [`AccountAdjustmentContext`](crate::AccountAdjustmentContext) it carries no
/// `account_control`: post-trade processing reports account blocks through the
/// [`PostTradeResult`](crate::pretrade::PostTradeResult) return value instead.
///
/// The bound account is the report's account; it is `None` when the report
/// carries no recognizable account identifier, in which case
/// [`account_group`](Self::account_group) returns `None`.
pub struct PostTradeContext<StorageFactory>
where
    StorageFactory: storage::LockingPolicyFactory + storage::CreateStorageFor<AccountId> + 'static,
{
    accounts: Option<Accounts<StorageFactory>>,
    account: Option<AccountId>,
    group_lookup: GroupLookup<StorageFactory>,
}

impl<StorageFactory> PostTradeContext<StorageFactory>
where
    StorageFactory: storage::LockingPolicyFactory + storage::CreateStorageFor<AccountId> + 'static,
{
    pub(crate) fn with_groups(
        account_groups: AccountGroupsHandle<StorageFactory>,
        account: Option<AccountId>,
    ) -> Self {
        Self {
            accounts: None,
            account,
            group_lookup: GroupLookup::new(account_groups, account),
        }
    }

    pub(crate) fn with_accounts(
        accounts: Accounts<StorageFactory>,
        account_groups: AccountGroupsHandle<StorageFactory>,
        account: Option<AccountId>,
    ) -> Self {
        Self {
            accounts: Some(accounts),
            account,
            group_lookup: GroupLookup::new(account_groups, account),
        }
    }

    /// Creates a standalone context for testing a [`PreTradePolicy`]'s
    /// post-trade hook outside an engine.
    ///
    /// The context is backed by an empty, private account-group registry and no
    /// bound account, so [`account_group`](Self::account_group) returns `None`.
    /// Inside the engine the registry is the engine's shared one; this
    /// constructor exists so policy authors can drive a policy's hook directly
    /// in unit tests.
    ///
    /// [`PreTradePolicy`]: crate::pretrade::PreTradePolicy
    pub fn new() -> Self
    where
        StorageFactory: Default,
    {
        let builder = StorageBuilder::new(StorageFactory::default());
        let handle = AccountGroupsHandle::from_inner(StorageFactory::new_shared(
            AccountGroups::new(&builder),
        ));
        Self::with_groups(handle, None)
    }

    #[cfg(test)]
    pub(crate) fn with_account_currency(account: AccountId, currency: Asset) -> Self
    where
        StorageFactory: Default + storage::CreateStorageFor<AccountGroupId>,
    {
        let builder = StorageBuilder::new(StorageFactory::default());
        let account_groups = AccountGroupsHandle::from_inner(StorageFactory::new_shared(
            AccountGroups::new(&builder),
        ));
        let block_handle = AccountBlockHandle::from_inner(StorageFactory::new_shared(
            BlockedAccounts::new(&builder),
        ));
        let currencies = StorageFactory::new_shared(AccountCurrencies::new(&builder));
        let config_registry = StorageFactory::new_shared(crate::core::ConfigRegistry::empty());
        let accounts = Accounts::new(
            account_groups.clone(),
            block_handle,
            currencies,
            config_registry,
        );
        accounts.set_currency(account, currency);
        Self::with_accounts(accounts, account_groups, Some(account))
    }

    /// Returns the group of the report's account, or `None` when the account is
    /// absent or unregistered.
    ///
    /// The lookup is performed once and cached for the lifetime of this
    /// context, so repeated calls during one evaluation return the same group.
    pub fn account_group(&self) -> Option<AccountGroupId> {
        self.group_lookup.group()
    }

    pub(crate) fn state_account_group(&self) -> Option<AccountGroupId> {
        match (self.accounts.as_ref(), self.account) {
            (Some(accounts), Some(account)) => accounts.group_of(account),
            _ => self.group_lookup.group(),
        }
    }

    /// Returns the effective currency for the report's account.
    ///
    /// The caller supplies `account_group`; this method resolves only the
    /// account -> group -> default currency cascade. Standalone test contexts
    /// have no account registry, so they return `None`.
    pub(crate) fn account_currency(&self, account_group: Option<AccountGroupId>) -> Option<Asset> {
        self.account.and_then(|account| {
            self.accounts
                .as_ref()?
                .currency_of_in_group(account, account_group)
        })
    }

    pub(crate) fn with_state_writer<R>(&self, operation: impl FnOnce() -> R) -> R {
        match (self.accounts.as_ref(), self.account) {
            (Some(accounts), Some(_)) => accounts.with_state_writer(operation),
            _ => operation(),
        }
    }
}

impl<StorageFactory> Default for PostTradeContext<StorageFactory>
where
    StorageFactory:
        storage::LockingPolicyFactory + storage::CreateStorageFor<AccountId> + Default + 'static,
{
    fn default() -> Self {
        Self::new()
    }
}

impl<StorageFactory> crate::marketdata::AccountInfo for PostTradeContext<StorageFactory>
where
    StorageFactory: storage::LockingPolicyFactory + storage::CreateStorageFor<AccountId> + 'static,
{
    fn group(&self) -> Option<AccountGroupId> {
        self.account_group()
    }
}