@@ -36,19 +36,17 @@ class Option(Dispatcher):
36
36
"""The expiration date"""
37
37
option_type : OptionType = field (compare = True )
38
38
"""Put or Call"""
39
-
40
- # mutable fields, but must be updated with update method instead of directly
41
- quote_datetime : Optional [datetime .datetime ] = field (default = None , compare = False )
39
+ quote_datetime : datetime .datetime = field (default = None , compare = False )
42
40
"""The date of the current price information: spot_price, bid, ask, and price"""
43
- spot_price : Optional [ int | float ] = field (default = None , compare = False )
41
+ spot_price : int | float = field (default = None , compare = False )
44
42
"""The current price of the underlying asset"""
45
- bid : Optional [ float | int ] = field (default = None , compare = False )
43
+ bid : float | int = field (default = None , compare = False )
46
44
"""The current bid price of the option"""
47
- ask : Optional [ float | int ] = field (default = None , compare = False )
45
+ ask : float | int = field (default = None , compare = False )
48
46
"""The current ask price of the option"""
49
- price : Optional [ float | int ] = field (default = None , compare = False )
47
+ price : float | int = field (default = None , compare = False )
50
48
"""The price is the mid-point between the bid and ask"""
51
- status : OptionStatus = field (init = False , default = None , compare = False )
49
+ status : OptionStatus = field (init = False , default = OptionStatus . INITIALIZED , compare = False )
52
50
"""
53
51
The option status tracks the life cycle of an option. This is a flag enum, so there can be
54
52
more than one status. To find out about a status, such as if the option is currently
@@ -106,25 +104,21 @@ def __post_init__(self):
106
104
raise ValueError ("expiration cannot be None" )
107
105
if self .option_type is None :
108
106
raise ValueError ("option_type cannot be None" )
109
-
110
- self .status = OptionStatus .CREATED
111
-
112
- if (any ([self .quote_datetime is not None , self .spot_price is not None , self .bid is not None ,
113
- self .ask is not None ,
114
- self .price is not None ])
115
- and not all ([self .quote_datetime is not None , self .spot_price is not None ,
116
- self .bid is not None , self .ask is not None , self .price is not None ])):
117
- raise ValueError (
118
- "All the required option quote values must be passed to set any quote values: "
119
- + "quote date, spot price, bid, ask, price." )
107
+ if self .quote_datetime is None :
108
+ raise ValueError ("quote_datetime cannot be None" )
109
+ if self .spot_price is None :
110
+ raise ValueError ("spot_price cannot be None" )
111
+ if self .bid is None :
112
+ raise ValueError ("bid cannot be None" )
113
+ if self .ask is None :
114
+ raise ValueError ("ask cannot be None" )
115
+ if self .price is None :
116
+ raise ValueError ("price cannot be None" )
120
117
121
118
# make sure the quote date is not past the expiration date
122
- if self .quote_datetime is not None and self . quote_datetime .date () > self .expiration :
119
+ if self .quote_datetime .date () > self .expiration :
123
120
raise ValueError ("Cannot create an option with a quote date past its expiration date" )
124
121
125
- if self .quote_datetime :
126
- self .status = OptionStatus .INITIALIZED
127
-
128
122
def __repr__ (self ) -> str :
129
123
return f'<{ self .option_type .name } ({ self .option_id } ) { self .symbol } { self .strike } ' \
130
124
+ f'{ datetime .datetime .strftime (self .expiration , "%Y-%m-%d" )} >'
@@ -151,7 +145,7 @@ def _check_expired(self):
151
145
Assumes the option is PM settled. Add the status OptionStatus.EXPIRED flag if the
152
146
option is expired.
153
147
"""
154
- if OptionStatus .EXPIRED in self .status or OptionStatus . INITIALIZED not in self . status :
148
+ if OptionStatus .EXPIRED in self .status :
155
149
return
156
150
quote_date , expiration_date = self .quote_datetime .date (), self .expiration
157
151
quote_time , exp_time = self .quote_datetime .time (), datetime .time (16 , 15 )
@@ -161,6 +155,8 @@ def _check_expired(self):
161
155
self .emit ("option_expired" , self .option_id )
162
156
163
157
def next_update (self , quote_datetime : datetime .datetime ):
158
+ if self .expiration > quote_datetime .date ():
159
+ return
164
160
update_values = self .update_cache .loc [quote_datetime ]
165
161
self .quote_datetime = quote_datetime
166
162
update_fields = [f for f in update_values .index if f not in ['option_id' , 'symbol' , 'strike' , 'expiration' , 'option_type' ]]
@@ -183,8 +179,7 @@ def next_update(self, quote_datetime: datetime.datetime):
183
179
self .open_interest = update_values ['open_interest' ]
184
180
if 'implied_volatility' in update_fields :
185
181
self .implied_volatility = update_values ['implied_volatility' ]
186
- self .status &= ~ OptionStatus .CREATED
187
- self .status |= OptionStatus .INITIALIZED
182
+
188
183
self ._check_expired ()
189
184
190
185
@@ -267,8 +262,6 @@ def open_trade(self, *, quantity: int, **kwargs: dict) -> TradeOpenInfo:
267
262
268
263
additional keyword arguments are added to the user_defined list of values
269
264
"""
270
- if OptionStatus .CREATED in self .status :
271
- raise ValueError ("Cannot open a position that does not have price data" )
272
265
if OptionStatus .TRADE_IS_OPEN in self .status :
273
266
raise ValueError ("Cannot open position. A position is already open." )
274
267
if (quantity is None ) or not (isinstance (quantity , int )) or (quantity == 0 ):
@@ -351,7 +344,6 @@ def close_trade(self, *, quantity: int, **kwargs: dict) -> TradeCloseInfo:
351
344
if self .quantity == 0 :
352
345
self .status &= ~ OptionStatus .TRADE_IS_OPEN
353
346
self .status |= OptionStatus .TRADE_IS_CLOSED
354
- self .update_cache = None
355
347
else :
356
348
self .status |= OptionStatus .TRADE_PARTIALLY_CLOSED
357
349
@@ -457,25 +449,22 @@ def _calculate_trade_close_info(self) -> None:
457
449
458
450
self .trade_close_info = trade_close
459
451
460
- def dte (self ) -> int | None :
452
+ def dte (self ) -> int :
461
453
"""
462
454
DTE is "days to expiration"
463
455
:return: The number of days to the expiration for the current quote
464
456
:rtype: int
465
457
"""
466
- if OptionStatus .INITIALIZED not in self .status :
467
- return None
458
+
468
459
dt_date = self .quote_datetime .date ()
469
460
time_delta = self .expiration - dt_date
470
461
return time_delta .days
471
462
472
463
@property
473
464
def current_value (self ) -> float :
474
- if self .quantity == 0 :
475
- return 0.0
476
465
current_price = decimalize_2 (self .price )
477
- open_quantity = decimalize_0 (self .quantity )
478
- current_value = current_price * 100 * open_quantity
466
+ quantity = decimalize_0 (self .quantity )
467
+ current_value = current_price * 100 * quantity
479
468
return float (current_value )
480
469
481
470
def get_unrealized_profit_loss (self ) -> float :
@@ -568,7 +557,7 @@ def get_days_in_trade(self) -> int:
568
557
time_delta = dt_date - self .trade_open_info .date .date ()
569
558
return time_delta .days
570
559
571
- def itm (self ) -> bool | None :
560
+ def itm (self ) -> bool :
572
561
"""
573
562
In the Money
574
563
A call option is in the money when the current price is higher than or equal to the strike price
@@ -577,25 +566,20 @@ def itm(self) -> bool | None:
577
566
:return: Returns a boolean value indicating whether the option is currently in the money.
578
567
:rtype: bool
579
568
"""
580
- if OptionStatus .INITIALIZED not in self .status :
581
- return None
582
569
583
570
if self .option_type == OptionType .CALL :
584
571
return True if self .spot_price >= self .strike else False
585
572
elif self .option_type == OptionType .PUT :
586
573
return True if self .spot_price <= self .strike else False
587
574
588
- def otm (self ) -> bool | None :
575
+ def otm (self ) -> bool :
589
576
"""
590
577
Out of the Money
591
578
A call option is out of the money when the current price is lower than the spot price.
592
579
A put option is out of the money when the current price is greater than the spot price.
593
580
:return: Returns a boolean value indicating whether the option is currently out of the money.
594
581
:rtype: bool
595
582
"""
596
- if OptionStatus .INITIALIZED not in self .status :
597
- return None
598
-
599
583
if self .option_type == OptionType .CALL :
600
584
return True if self .spot_price < self .strike else False
601
585
elif self .option_type == OptionType .PUT :
0 commit comments