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):
premiumis quoted per unit of the underlying, so one contract pays or receivespremium × contract_sizeand the positionpremium × contract_size × quantity;open_feeandclose_feeare per contract and scale withquantityonly;- payoff, P&L and Greeks are per position, scaled by
quantity × contract_size; TradeAble::traderecords the per-unit premium and copies the contract size into theTrade, whosecost,incomeandnetapply 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: OptionsThe detailed options contract information, including the type, strike price, expiration, underlying asset details, and other option-specific parameters.
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: PositiveThe 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: PositiveThe 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
impl Position
Sourcepub fn new(
option: Options,
premium: Positive,
date: DateTime<Utc>,
open_fee: Positive,
close_fee: Positive,
epic: Option<String>,
extra_fields: Option<Value>,
) -> Self
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)
);Sourcepub fn total_cost(&self) -> Result<Positive, PositionError>
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.
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 aPositivevalue if successful, or aPositionErrorif 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.
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 valueErr(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.
Sourcepub fn pnl_at_expiration(
&self,
price: &Option<&Positive>,
) -> Result<Decimal, PositionError>
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(¤t_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.
Sourcepub fn unrealized_pnl(&self, price: Positive) -> Result<Decimal, PositionError>
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- APositivevalue representing the current price of the option, quoted per unit of the underlying likepremium
§Returns
Result<Decimal, PositionError>- The calculated unrealized PnL as aDecimalif successful, or aPositionErrorif 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.
Sourcepub fn days_held(&self) -> Result<Positive, PositionError>
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 valueErr(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.
Sourcepub fn days_to_expiration(&self) -> Result<Positive, PositionError>
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 valueErr(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).
Sourcepub fn is_long(&self) -> bool
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
trueif the position is longfalseif the position is short
Sourcepub fn is_short(&self) -> bool
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
trueif the position is shortfalseif the position is long
Sourcepub fn net_cost(&self) -> Result<Decimal, PositionError>
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.
Sourcepub fn break_even(&self) -> Option<Positive>
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 quantityNoneif 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 aPositive— a cost per contract above the strike puts it below zero, and a cost per contract nearPositive::MAXoverflows the sum
Sourcepub fn fees(&self) -> Result<Positive, PositionError>
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 valueErr(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.
Sourcepub fn validate(&self) -> bool
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:
- For short positions, verifies that:
- Premium is greater than zero
- Premium exceeds the sum of opening and closing fees
- Validates the underlying option parameters
§Returns
trueif the position is valid and meets all conditionsfalseotherwise, 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<'de> Deserialize<'de> for Position
impl<'de> Deserialize<'de> for Position
Source§fn deserialize<__D>(__deserializer: __D) -> Result<Self, __D::Error>where
__D: Deserializer<'de>,
fn deserialize<__D>(__deserializer: __D) -> Result<Self, __D::Error>where
__D: Deserializer<'de>,
impl StructuralPartialEq for Position
Source§impl TradeAble for Position
impl TradeAble for Position
Source§impl TradeStatusAble for Position
impl TradeStatusAble for Position
Source§fn open(&self) -> Result<Trade, TradeError>
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 moreSource§fn close(&self) -> Result<Trade, TradeError>
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 moreSource§fn expired(&self) -> Result<Trade, TradeError>
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 moreSource§fn exercised(&self) -> Result<Trade, TradeError>
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 moreSource§fn assigned(&self) -> Result<Trade, TradeError>
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 moreSource§fn status_other(&self) -> Result<Trade, TradeError>
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