28
28
29
29
# How many days out to look for rollouts
30
30
MAX_ROLLOUT_DAYS = 65
31
+ MIN_ROLLOUT_DAYS = 1
31
32
32
33
# This is the highest strike price to consider
33
34
MAX_STRIKE_FACTOR = 1.05
34
35
35
- def main (config_file , symbol , existing_expiration , strike , verbose ):
36
+ def main (config_file , symbol , existing_expiration , strike , min_days , max_days , verbose ):
36
37
37
38
quote = get_quote (config_file , symbol )
38
39
price = quote .get_price ()
39
40
exdate = quote .get_exdate ()
40
41
dividend = quote .get_dividend ()
41
42
43
+ intrinsic_value = price - strike
44
+ if intrinsic_value < 0 :
45
+ intrinsic_value = 0
46
+
42
47
if price <= float (strike * (100 - PRICE_PROXIMITY )/ 100 ):
43
48
print (f"{ symbol } : price ${ price :.2f} is more than { PRICE_PROXIMITY } % below strike { strike } " )
44
49
return
@@ -50,16 +55,25 @@ def main(config_file, symbol, existing_expiration, strike, verbose):
50
55
51
56
print (f"{ symbol } : ${ price :.2f} " )
52
57
print (f"{ call_option .get_display_symbol ()} " )
58
+
59
+ ask = call_option .get_ask ()
60
+ time_value = ask - intrinsic_value
61
+ tv_prct = 100 * (time_value / strike )
62
+
63
+ itm_prct = 100 * (intrinsic_value / strike )
64
+
65
+ print (f"Ask: { ask } (time value remaining=${ time_value :.2f} / { tv_prct :.1f} %)" )
66
+ print (f"Current intrinsic value: ${ intrinsic_value :.2f} ({ itm_prct :.1f} %)" )
53
67
print (f"-----" )
54
- ask = call_option .get_bid ()
55
68
56
69
if ask == 0 :
57
70
print ("ERROR: ask is 0.00, maybe the market will open soon?" )
58
71
return
59
72
60
- option_chain_list = get_matching_option_chains (config_file , symbol , existing_expiration , MAX_ROLLOUT_DAYS )
73
+ option_chain_list = get_matching_option_chains (config_file , symbol , existing_expiration , min_days , max_days )
61
74
for option_chain in option_chain_list :
62
- roll_out_days = option_chain .get_expiration () - existing_expiration
75
+ #roll_out_days = option_chain.get_expiration() - existing_expiration
76
+ roll_out_days = option_chain .get_expiration () - datetime .datetime .now ()
63
77
risky_exdate = False
64
78
div_ex_date_from_expiration = option_chain .get_expiration () - exdate
65
79
if 0 < div_ex_date_from_expiration .days < EXDATE_THRESHOLD :
@@ -74,34 +88,76 @@ def main(config_file, symbol, existing_expiration, strike, verbose):
74
88
if bid < ask :
75
89
# Skip this, it would end with a debit
76
90
continue
91
+
77
92
credit = bid - ask
78
93
buy_up = strike_price - strike
79
94
80
95
total_gain = credit + buy_up
81
- prct = 100 * (total_gain / strike )
82
- bpd = 100 * (prct / roll_out_days .days )
96
+ total_prct = 100 * (total_gain / strike )
97
+ bpd = 100 * (total_prct / roll_out_days .days )
83
98
84
99
buy_up_prct = 100 * (buy_up / strike )
85
- buy_up_score = (100 * BUY_UP_SCORE_FACTOR * buy_up_prct ) / (roll_out_days .days ** 2 )
100
+ credit_prct = 100 * (credit / strike )
101
+
102
+ #buy_up_score = (100 * BUY_UP_SCORE_FACTOR * buy_up_prct) / (roll_out_days.days ** 2)
103
+ #credit_score = (100 * CREDIT_FACTOR * credit ) / (roll_out_days.days ** 2)
86
104
87
- credit_score = ( 100 * CREDIT_FACTOR * credit ) / ( roll_out_days .days ** 2 )
105
+ ( total_score ) = get_scores ( buy_up_prct , credit_prct , roll_out_days .days , risky_exdate )
88
106
89
- total_score = credit_score + buy_up_score + bpd
90
- if risky_exdate :
91
- total_score = total_score * RISKY_EXDATE_FACTOR
107
+ print (f"{ call .get_display_symbol ():28} : credit=${ credit :5.2f} ({ credit_prct :4.2f} %) buy_up=${ buy_up :5.2f} ({ buy_up_prct :4.2f} %) total={ total_prct :5.2f} % days={ roll_out_days .days :2d} bpd={ bpd :5.2f} exdate_risk={ risky_exdate } score={ total_score :5.2f} " )
92
108
93
- print (f"{ call .get_display_symbol ()} : credit=${ credit :5.2f} buy_up=${ buy_up :5.2f} gain={ prct :5.2f} % days={ roll_out_days .days :2d} bpd={ bpd :5.2f} c_score={ credit_score :5.2f} b_score={ buy_up_score :5.2f} exdate_risk={ risky_exdate } score={ total_score :5.2f} " )
109
+ def get_scores (buy_up_prct , credit_prct , num_days , risky_exdate ):
110
+ # What makes a good score?
111
+ # The best score has a good credit, and a roll up, is soon not later than the next monthly expiration
112
+ # Points:
113
+ # buy up score: multiply by 100 (on the order of 0.5 to 4.0)
114
+ # credit score: multiply by 100 (on the order of 0.5 to 4.0)
115
+ # credit score > buy up score: if buy up == 0, add 1 else divide credit by buy up (0.5 to 2.0)
116
+ # Risky ex-date: subtract 2
117
+ # Date: (10 - (days / 7) )/ 5 (0.00 to 1.8)
94
118
119
+ # What makes a bad score?
120
+ # Too much buy up, not enough credit
121
+ # Has a risky exdate
122
+
123
+ # Add credit and buy up
124
+ #print(f"credit prct = {credit_prct:.2f}")
125
+ #print(f"buy_up = {buy_up_prct:.2f}")
126
+
127
+ score = (credit_prct + buy_up_prct )
128
+
129
+ # Add the credit preference
130
+ credit_preference = 0
131
+ if buy_up_prct > 0 :
132
+
133
+ if buy_up_prct > credit_prct :
134
+ credit_preference = credit_prct
135
+ else :
136
+ credit_preference = (credit_prct ) + (buy_up_prct )
137
+
138
+ # print(f"credit prefernece = {credit_preference:.2f}")
139
+ score += credit_preference
140
+
141
+ duration_factor = (10 - ((num_days / 7 )* 3 ))
142
+ score += duration_factor
143
+
144
+ exdate_factor = 0
145
+ if risky_exdate :
146
+ exdate_factor = - 2
147
+
148
+ score += exdate_factor
149
+
150
+ return score
95
151
96
- def get_matching_option_chains (config_file , symbol , existing_expiration , MAX_ROLLOUT_DAYS ):
152
+ def get_matching_option_chains (config_file , symbol , existing_expiration , min_days , max_days ):
97
153
option_chain_list = list ()
98
154
dates = get_expiration_dates (config_file , symbol )
99
155
100
156
for (expiration_date , expiration_type ) in dates :
101
157
if expiration_date > existing_expiration :
102
158
elapsed = expiration_date - existing_expiration
103
159
days = elapsed .days
104
- if days < MAX_ROLLOUT_DAYS :
160
+ if min_days < days < max_days :
105
161
option_chain = get_option_chain (config_file , symbol , expiration_date )
106
162
if option_chain :
107
163
option_chain_list .append (option_chain )
@@ -131,11 +187,13 @@ def get_expiration_dates(config_file, symbol):
131
187
parser .add_argument ('-s' ,'--symbol' , dest = 'symbol' , required = True ,help = "Symbol of the call" )
132
188
parser .add_argument ('-e' ,'--expiration' , dest = 'expiration' , required = True ,help = "Symbol to search" )
133
189
parser .add_argument ('-p' ,'--strike-price' , dest = 'strike' , required = True ,help = "Strike price" )
190
+ parser .add_argument ('--min-days' , dest = 'min_days' , required = False , default = MIN_ROLLOUT_DAYS ,help = "Minimum number of days until expiration" )
191
+ parser .add_argument ('--max-days' , dest = 'max_days' , required = False , default = MAX_ROLLOUT_DAYS ,help = "Maximum number of days until expiration" )
134
192
parser .add_argument ('-v' ,'--verbose' , dest = 'verbose' , required = False ,default = False ,action = 'store_true' ,help = "Increase verbosity" )
135
193
args = parser .parse_args ()
136
194
137
195
if args .expiration is not None :
138
196
(y ,m ,d ) = args .expiration .split ("-" )
139
197
expiration = datetime .datetime (year = int (y ),month = int (m ), day = int (d ))
140
198
141
- main (args .config_file , args .symbol , expiration , float (args .strike ), args .verbose )
199
+ main (args .config_file , args .symbol , expiration , float (args .strike ), int ( args . min_days ), int ( args . max_days ), args .verbose )
0 commit comments