Skip to main content

Position

Struct Position 

Source
pub struct Position {
    pub option: Options,
    pub premium: Positive,
    pub date: DateTime<Utc>,
    pub open_fee: Positive,
    pub close_fee: Positive,
    pub epic: Option<String>,
    pub extra_fields: Option<Value>,
}
Expand description

The Position struct represents a financial position in an options market.

This structure encapsulates all the necessary information to track an options position, including the underlying option details, costs associated with the position, and the date when the position was opened. It provides methods for analyzing profitability, time metrics, and position characteristics.

§Contract size

The option’s quantity counts contracts of contract_size units of the underlying (see Options::position_size):

  • premium is quoted per unit of the underlying, so one contract pays or receives premium × contract_size and the position premium × contract_size × quantity;
  • open_fee and close_fee are per contract and scale with quantity only;
  • payoff, P&L and Greeks are per position, scaled by quantity × contract_size;
  • TradeAble::trade records the per-unit premium and copies the contract size into the Trade, whose cost, income and net apply the same rules.

§Examples

use optionstratlib_core::model::{Options, Side, OptionStyle};
use positive::pos_or_panic;
use chrono::Utc;
use tracing::info;
use optionstratlib_core::model::Position;
use optionstratlib_core::model::utils::create_sample_option_simplest;

let option = create_sample_option_simplest(OptionStyle::Call, Side::Long);
let position = Position::new(
    option,
    pos_or_panic!(5.25),           // premium per unit of the underlying
    Utc::now(),           // position open date
    pos_or_panic!(0.65),           // opening fee per contract
    pos_or_panic!(0.65),           // closing fee per contract
    None,
    None,
);

let total_cost = position.total_cost()?;
info!("Total position cost: {}", total_cost);

Fields§

§option: Options

The detailed options contract information, including the type, strike price, expiration, underlying asset details, and other option-specific parameters.

§premium: Positive

The premium paid or received, quoted per unit of the underlying (the option price). For long positions, this represents the cost; for short positions, this is the credit received. One contract pays or receives premium × option.contract_size; with the default contract size of 1 that is the premium itself.

§date: DateTime<Utc>

The date and time when the position was opened, used for calculating time-based metrics like days held and days to expiration.

§open_fee: Positive

The fee paid to open the position per contract. This typically includes broker commissions and exchange fees. It scales by option.quantity only, never by option.contract_size.

§close_fee: Positive

The fee that will be paid to close the position per contract. This is used in profit/loss calculations to account for all transaction costs. It scales by option.quantity only, never by option.contract_size.

§epic: Option<String>

Identifier for the position in an external system or platform

§extra_fields: Option<Value>

Additional custom data fields for the position stored as JSON

Implementations§

Source§

impl Position

Source

pub fn new( option: Options, premium: Positive, date: DateTime<Utc>, open_fee: Positive, close_fee: Positive, epic: Option<String>, extra_fields: Option<Value>, ) -> Self

Creates a new options position.

This constructor initializes a new Position instance representing an options trade, capturing all essential information for position tracking and analysis.

§Parameters
  • option - The options contract details including type (call/put), strike price, expiration date, underlying asset information, and other option parameters.

  • premium - The premium paid (for long positions) or received (for short positions) per contract, represented as a positive value.

  • date - The timestamp when the position was opened, used for calculating time-based metrics like days to expiration and position duration.

  • open_fee - The transaction costs paid to open the position per contract, including broker commissions and exchange fees.

  • close_fee - The anticipated transaction costs to close the position per contract, used for accurate profit/loss calculations.

§Returns

Returns a new Position instance containing the provided information.

§Examples
use optionstratlib_core::model::{Options, Side, OptionStyle};
use positive::pos_or_panic;
use chrono::Utc;
use optionstratlib_core::model::Position;
use optionstratlib_core::model::utils::create_sample_option_simplest;

let option = create_sample_option_simplest(OptionStyle::Call, Side::Long);
let position = Position::new(
    option,
    pos_or_panic!(5.25),           // premium per contract
    Utc::now(),           // position open date
    pos_or_panic!(0.65),           // opening fee per contract
    pos_or_panic!(0.65),           // closing fee per contract
   None,                  // epic (optional)
  None,                  // extra fields (optional)
);
Source

pub fn total_cost(&self) -> Result<Positive, PositionError>

Calculates the total cost of the position based on the option’s side and fees.

Depending on whether the position is long or short, different components contribute to the total cost calculation:

  • For a long position, the total cost includes the premium per contract (premium × contract_size), open fee, and close fee multiplied by the option’s quantity.
  • For a short position, the total cost includes only the open fee and close fee multiplied by the option’s quantity; fees are per contract and do not scale by the contract size.
§Returns

A Positive representing the total cost of the position. THE VALUE IS ALWAYS POSITIVE

§Errors

Returns PositionError::PositiveError when accumulating the premium and the two fees, scaling the premium by the contract size, or scaling the accumulated per-contract cost by the contract quantity, overflows the Positive range. The raw Positive operators abort on overflow, so every step is taken through its checked counterpart.

Source

pub fn premium_received(&self) -> Result<Positive, PositionError>

Calculates the premium received from an options position.

This method determines the premium amount received based on the position’s side:

  • For long positions, it returns zero as the trader pays premium (doesn’t receive any)
  • For short positions, it returns the total premium received (premium × contract_size × quantity)

The result is always returned as a Positive value, ensuring non-negative amounts.

§Returns
  • Result<Positive, PositionError> - A result containing the premium received as a Positive value if successful, or a PositionError if any calculation errors occur.
§Examples
use optionstratlib_core::model::{Side, OptionStyle};
use positive::pos_or_panic;
use optionstratlib_core::model::Position;
use optionstratlib_core::model::utils::create_sample_option_simplest;
use chrono::Utc;
use tracing::info;

// Create a short position
let option = create_sample_option_simplest(OptionStyle::Call, Side::Short);
let position = Position::new(
    option,
    pos_or_panic!(5.25),  // premium per contract
    Utc::now(),  // position open date
    pos_or_panic!(0.65),  // opening fee
    pos_or_panic!(0.65),  // closing fee
   None,        // epic (optional)
  None,        // extra fields (optional)
);

// Calculate premium received
let received = position.premium_received()?;
info!("Premium received: {}", received);
§Errors

Returns PositionError::PositiveError when the short-side premium × contract_size × quantity product overflows the Positive range. Long positions receive no premium and are infallible.

Source

pub fn net_premium_received(&self) -> Result<Positive, PositionError>

Calculates the net premium received for the position.

This method determines the premium amount received after accounting for costs, which is relevant primarily for short positions. For long positions, this always returns zero as premium is paid rather than received.

For short positions, the method calculates the difference between the premium received and the total costs incurred. If this value is positive (meaning the premium exceeds the costs), it represents the maximum potential profit for the position. If negative, the position is considered invalid as it would represent a guaranteed loss.

§Returns
  • Ok(Positive) - The net premium received as a non-negative value
  • Err(PositionError) - If the position is invalid because the premium received is less than the costs, resulting in a guaranteed loss
§Errors

Propagates any PositionError raised by total_cost(), and returns PositionError::PositiveError when scaling the premium by the contract size and quantity overflows the Positive range.

When the short-side net amount premium − total_cost is negative the function returns Ok(Positive::ZERO) (clamped) rather than an error, so a guaranteed-loss short position is reported as zero net received rather than as a failure.

Source

pub fn pnl_at_expiration( &self, price: &Option<&Positive>, ) -> Result<Decimal, PositionError>

Calculates the profit and loss (PnL) at the option’s expiration.

This function determines the total profit or loss that would be realized when the option position expires, taking into account the intrinsic value at expiration, the cost to establish the position, and any premiums received.

§Arguments
  • price - An optional reference to a positive decimal value representing the underlying asset price at expiration. If None is provided, the calculation will use the current underlying price stored in the option.
§Returns
  • Result<Decimal, PositionError> - The calculated profit or loss as a Decimal value, or an error if the calculation fails.
§Examples
// Assuming position is a properly initialized Position
use chrono::Utc;
use optionstratlib_core::model::utils::create_sample_option_simplest;
use optionstratlib_core::model::{OptionStyle, Side};
use positive::pos_or_panic;
use optionstratlib_core::model::Position;

let option = create_sample_option_simplest(OptionStyle::Call, Side::Short);
let position = Position::new(
    option,
    pos_or_panic!(5.25),  // premium per contract
    Utc::now(),  // position open date
    pos_or_panic!(0.65),  // opening fee
    pos_or_panic!(0.65),  // closing fee
   None,        // epic (optional)
  None,        // extra fields (optional)
);
let current_price = pos_or_panic!(105.0);

// Calculate PnL at expiration with specified price
let pnl_specific = position.pnl_at_expiration(&Some(&current_price))?;

// Calculate PnL at expiration using the option's current underlying price
let pnl_current = position.pnl_at_expiration(&None)?;
§Errors

Propagates any OptionsError returned by the underlying payoff evaluation (Options::intrinsic_value or Options::payoff), wrapped as PositionError::Options.

Source

pub fn unrealized_pnl(&self, price: Positive) -> Result<Decimal, PositionError>

Calculates the unrealized profit and loss (PnL) for an options position at a given price.

This method computes the current theoretical profit or loss of the position if it were to be closed at the specified price, taking into account the premium paid/received and all transaction fees (both opening and closing fees).

The calculation differs based on the position side:

  • For long positions: ((current_price - premium) * contract_size - open_fee - close_fee) * quantity
  • For short positions: ((premium - current_price) * contract_size - open_fee - close_fee) * quantity

Prices are quoted per unit of the underlying and fees per contract, so only the price difference scales by the contract size.

§Parameters
  • price - A Positive value representing the current price of the option, quoted per unit of the underlying like premium
§Returns
  • Result<Decimal, PositionError> - The calculated unrealized PnL as a Decimal if successful, or a PositionError if the calculation fails
§Example
use chrono::Utc;
use tracing::info;
use optionstratlib_core::model::Position;
use optionstratlib_core::model::utils::create_sample_option_simplest;
use optionstratlib_core::model::{OptionStyle, Side};
use positive::pos_or_panic;
let current_price = pos_or_panic!(6.50);
let option = create_sample_option_simplest(OptionStyle::Call, Side::Short);
let position = Position::new(
    option,
    pos_or_panic!(5.25),  // premium per contract
    Utc::now(),  // position open date
    pos_or_panic!(0.65),  // opening fee
    pos_or_panic!(0.65),  // closing fee
   None,        // epic (optional)
  None,        // extra fields (optional)
);
let unrealized_pnl = position.unrealized_pnl(current_price)?;
info!("Current unrealized PnL: {}", unrealized_pnl);
§Errors

Returns PositionError::DecimalError when the premium and fee deductions, or the scaling by the contract size and quantity, leave the representable Decimal range. Nothing is repriced here: the result is arithmetic over the stored premium, fees and quantity.

Source

pub fn days_held(&self) -> Result<Positive, PositionError>

Calculates the number of days the position has been held.

This method computes the difference between the current UTC date and the position’s opening date, returning the result as a Positive value.

The calculation uses Chrono’s num_days method to determine the precise number of whole days between the position’s date and current time.

§Returns
  • Ok(Positive) - The number of days the position has been held as a positive value
  • Err(PositionError) - If there’s an error during the calculation or validation
§Errors

Returns PositionError wrapping a PositionValidationErrorKind::InvalidPositionSize if the elapsed day-count is negative (future-dated open date) or cannot be represented as a Positive.

Source

pub fn days_to_expiration(&self) -> Result<Positive, PositionError>

Calculates the number of days remaining until the option expires.

This function determines the time to expiration in days based on the option’s expiration date format. It handles both explicit day counts and datetime-based expiration dates.

§Returns
  • Ok(Positive) - The number of days to expiration as a positive value
  • Err(PositionError) - If the calculation fails due to issues with the position data

For datetime-based expirations, the function calculates the difference between the expiration date and the current date, converting the result to days.

§Errors

Returns PositionError wrapping the underlying expiration_date::error::ExpirationDateError when the expiration cannot be converted (e.g. a past datetime that would produce a negative day count).

Source

pub fn is_long(&self) -> bool

Determines if the position is a long position.

This method checks the side attribute of the option to determine the directionality of the position. Long positions profit when the underlying asset’s price increases.

§Returns
  • true if the position is long
  • false if the position is short
Source

pub fn is_short(&self) -> bool

Determines if the position is a short position.

This method checks the side attribute of the option to determine the directionality of the position. Short positions profit when the underlying asset’s price decreases.

§Returns
  • true if the position is short
  • false if the position is long
Source

pub fn net_cost(&self) -> Result<Decimal, PositionError>

Calculates the net cost of the position based on the option’s side and fees.

This method calculates the net cost of a position by determining whether the position is long or short and then computing the respective costs:

  • For a long position, the net cost is equivalent to the total_cost() of the position.
  • For a short position, the net cost is calculated by subtracting the premium from the sum of the open and close fees, and then multiplying the result by the option’s quantity.
§Returns

A Decimal representing the net cost of the position. The value should be positive but if the fee is higher than the premium it will be negative in short positions

§Errors

Propagates the PositionError::PositiveError raised by total_cost, fees and premium_received when the fee or premium accumulation, or its product with the contract quantity, overflows the Positive range.

Source

pub fn break_even(&self) -> Option<Positive>

Calculates the break-even price for an options position.

This method determines the price of the underlying asset at which the position will neither make a profit nor a loss. The calculation varies based on both the side of the position (Long/Short) and the option style (Call/Put).

The break-even price is an important reference point for options traders as it represents the threshold price that the underlying asset must cross for the position to become profitable, accounting for all costs associated with the position.

§Formula by position type:
  • Long Call: Strike Price + Total Cost per Unit
  • Short Call: Strike Price + Premium - Total Cost per Unit
  • Long Put: Strike Price - Total Cost per Unit
  • Short Put: Strike Price - Premium + Total Cost per Unit

The total cost per unit is the position total cost divided by the position size (quantity × contract_size), so per-contract fees are spread over the units one contract covers.

§Returns
  • Some(Positive) containing the break-even price if the position has non-zero quantity
  • None if the position has zero quantity (no contracts), if the position total cost cannot be calculated, or if the break-even price is not representable as a Positive — a cost per contract above the strike puts it below zero, and a cost per contract near Positive::MAX overflows the sum
Source

pub fn fees(&self) -> Result<Positive, PositionError>

Calculates the total transaction fees for the position.

This method computes the sum of opening and closing fees for the position, scaled by the quantity of options contracts. These fees typically include broker commissions, exchange fees, and other transaction costs.

§Returns
  • Ok(Positive) - The total fees as a positive value
  • Err(PositionError) - If there’s an issue calculating the fees
§Errors

Returns PositionError::PositiveError when adding the two fees, or scaling their sum by the contract quantity, overflows the Positive range. The raw Positive operators abort on overflow, so both steps are taken through their checked counterparts.

Source

pub fn validate(&self) -> bool

Validates the position to ensure it meets all necessary conditions for trading.

This method performs a series of checks to determine if the position is valid:

  1. For short positions, verifies that:
    • Premium is greater than zero
    • Premium exceeds the sum of opening and closing fees
  2. Validates the underlying option parameters
§Returns
  • true if the position is valid and meets all conditions
  • false otherwise, with specific failure reasons logged via debug messages
§Examples
use optionstratlib_core::model::{Position, Options};
use optionstratlib_core::model::{Side, OptionStyle};
use positive::pos_or_panic;
use optionstratlib_core::model::utils::create_sample_option_simplest;
use chrono::Utc;

// Create a valid position
let option = create_sample_option_simplest(OptionStyle::Call, Side::Long);
let position = Position::new(
    option,
    pos_or_panic!(5.25),
    Utc::now(),
    pos_or_panic!(0.65),
    pos_or_panic!(0.65),
    None, // epic (optional)
   None, // extra fields (optional)
);

assert!(position.validate());

Trait Implementations§

Source§

impl Clone for Position

Source§

fn clone(&self) -> Self

Returns a duplicate of the value. Read more
1.0.0 (const: unstable) · Source§

fn clone_from(&mut self, source: &Self)

Performs copy-assignment from source. Read more
Source§

impl ComposeSchema for Position

Source§

impl Debug for Position

Source§

fn fmt(&self, f: &mut Formatter<'_>) -> Result

Formats the value using the given formatter. Read more
Source§

impl Default for Position

Source§

fn default() -> Self

Returns the “default value” for a type. Read more
Source§

impl<'de> Deserialize<'de> for Position

Source§

fn deserialize<__D>(__deserializer: __D) -> Result<Self, __D::Error>
where __D: Deserializer<'de>,

Deserialize this value from the given Serde deserializer. Read more
Source§

impl Display for Position

Source§

fn fmt(&self, f: &mut Formatter<'_>) -> Result

Formats the value using the given formatter. Read more
Source§

impl From<Position> for Leg

Source§

fn from(position: Position) -> Self

Converts to this type from the input type.
Source§

impl PartialEq for Position

Source§

fn eq(&self, other: &Self) -> bool

Equality operator ==. Read more
1.0.0 (const: unstable) · Source§

fn ne(&self, other: &Rhs) -> bool

Inequality operator !=. Read more
Source§

impl Serialize for Position

Source§

fn serialize<__S>(&self, __serializer: __S) -> Result<__S::Ok, __S::Error>
where __S: Serializer,

Serialize this value into the given Serde serializer. Read more
Source§

impl StructuralPartialEq for Position

Source§

impl ToSchema for Position

Source§

fn name() -> Cow<'static, str>

Return name of the schema. Read more
Source§

fn schemas(schemas: &mut Vec<(String, RefOr<Schema>)>)

Implement reference utoipa::openapi::schema::Schemas for this type. Read more
Source§

impl TradeAble for Position

Source§

fn trade(&self) -> Result<Trade, TradeError>

Retrieves a reference to a Trade instance associated with the current object. Read more
Source§

fn trade_ref(&self) -> Result<&Trade, TradeError>

Returns a reference to the Trade associated with the current instance. Read more
Source§

fn trade_mut(&mut self) -> Result<&mut Trade, TradeError>

Provides a mutable reference to the Trade instance contained within the current structure. Read more
Source§

impl TradeStatusAble for Position

Source§

fn open(&self) -> Result<Trade, TradeError>

open: Return a Trade instance representing the trade in its open status or a TradeError if the transition is invalid. Read more
Source§

fn close(&self) -> Result<Trade, TradeError>

closed: Return a Trade instance representing the trade in its closed status or a TradeError if the transition is invalid. Read more
Source§

fn expired(&self) -> Result<Trade, TradeError>

expired: Return a Trade instance representing the trade in its expired status or a TradeError if the transition is invalid. Read more
Source§

fn exercised(&self) -> Result<Trade, TradeError>

exercised: Return a Trade instance representing the trade in its exercised status or a TradeError if the transition is invalid. Read more
Source§

fn assigned(&self) -> Result<Trade, TradeError>

assigned: Return a Trade instance representing the trade in its assigned status or a TradeError if the transition is invalid. Read more
Source§

fn status_other(&self) -> Result<Trade, TradeError>

status_other: Return a Trade instance representing undeclared status or a TradeError if the transition is invalid. Read more

Auto Trait Implementations§

Blanket Implementations§

Source§

impl<T> Any for T
where T: 'static + ?Sized,

Source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
Source§

impl<T> Borrow<T> for T
where T: ?Sized,

Source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
Source§

impl<T> BorrowMut<T> for T
where T: ?Sized,

Source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
Source§

impl<T> CloneToUninit for T
where T: Clone,

Source§

unsafe fn clone_to_uninit(&self, dest: *mut u8)

🔬This is a nightly-only experimental API. (clone_to_uninit)
Performs copy-assignment from self to dest. Read more
Source§

impl<T> DeserializeOwned for T
where T: for<'de> Deserialize<'de>,

Source§

impl<T> From<T> for T

Source§

fn from(t: T) -> T

Returns the argument unchanged.

Source§

impl<T> Instrument for T

Source§

fn instrument(self, span: Span) -> Instrumented<Self> ⓘ

Instruments this type with the provided Span, returning an Instrumented wrapper. Read more
Source§

fn in_current_span(self) -> Instrumented<Self> ⓘ

Instruments this type with the current Span, returning an Instrumented wrapper. Read more
Source§

impl<T, U> Into<U> for T
where U: From<T>,

Source§

fn into(self) -> U

Calls U::from(self).

That is, this conversion is whatever the implementation of From<T> for U chooses to do.

Source§

impl<T> PartialSchema for T
where T: ComposeSchema + ?Sized,

Source§

fn schema() -> RefOr<Schema>

Return ref or schema of implementing type that can then be used to construct combined schemas.
Source§

impl<T> ToOwned for T
where T: Clone,

Source§

type Owned = T

The resulting type after obtaining ownership.
Source§

fn to_owned(&self) -> T

Creates owned data from borrowed data, usually by cloning. Read more
Source§

fn clone_into(&self, target: &mut T)

Uses borrowed data to replace owned data, usually by cloning. Read more
Source§

impl<T> ToString for T
where T: Display + ?Sized,

Source§

fn to_string(&self) -> String

Converts the given value to a String. Read more
Source§

impl<T, U> TryFrom<U> for T
where U: Into<T>,

Source§

type Error = !

The type returned in the event of a conversion error.
Source§

fn try_from(value: U) -> Result<T, !>

Performs the conversion.
Source§

impl<T, U> TryInto<U> for T
where U: TryFrom<T>,

Source§

type Error = <U as TryFrom<T>>::Error

The type returned in the event of a conversion error.
Source§

fn try_into(self) -> Result<U, <U as TryFrom<T>>::Error>

Performs the conversion.
Source§

impl<T> WithSubscriber for T

Source§

fn with_subscriber<S>(self, subscriber: S) -> WithDispatch<Self> ⓘ
where S: Into<Dispatch>,

Attaches the provided Subscriber to this type, returning a WithDispatch wrapper. Read more
Source§

fn with_current_subscriber(self) -> WithDispatch<Self> ⓘ

Attaches the current default Subscriber to this type, returning a WithDispatch wrapper. Read more