Created
January 16, 2017 05:02
-
-
Save kpmiller/605486ea8e461732056e1ece044e8cbe to your computer and use it in GitHub Desktop.
scanner for iron condor trades matching some criteria
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/usr/bin/python | |
import sys,os,glob,re,csv | |
try: | |
loglevel = int(os.environ["LOGLEVEL"]) | |
except: | |
loglevel = 1 | |
def Log(s, level=1): | |
if level <= loglevel: | |
print s | |
def QuotesFromFile(filename = None): | |
########################## | |
# Parse the file | |
########################## | |
try: | |
loglevel = int(os.environ["LOGLEVEL"]) | |
except: | |
loglevel = 1 | |
optcsv = [] | |
f = open(filename, "r") | |
for line in csv.reader(f): | |
optcsv.append(line) | |
f.close() | |
optd = { "underlying" : "", "symbol" : "", "series" : {} } | |
key = "" | |
opts = {} | |
state=0 | |
for line in optcsv: | |
#state 0: looking for first line for quote symbol | |
#state 10: looking for "UNDERLYING" | |
#state 11: eating underlying header | |
#state 12: taking underlying | |
#state 1: looking for a date line with a () days to exp | |
#state 2: store header, go to 3 | |
#state 3: eating lines until empty line, then end date, reset key and opts | |
Log(str(line), 900) | |
Log(str(state), 900) | |
if state == 0: | |
m = re.match(".*option quote for (\w*) on ", line[0]) | |
if m != None: | |
optd["symbol"] = m.group(1) | |
Log("\nScanning " + optd["symbol"], level = 1) | |
state = 10 | |
elif state == 10: | |
if len(line)>0 and line[0].rfind ("UNDERLYING") != -1: | |
state = 11 | |
elif state == 11: | |
state = 12 | |
elif state == 12: | |
optd["underlying"] = float(line[0]) | |
Log("Quote " + str(optd["underlying"]), level = 1) | |
state = 1 | |
elif state == 1: | |
if len(line)>0: | |
m = re.match("\d* ... .. \((\d*)\)", line[0]) | |
if m != None: | |
key = line[0] | |
state = 2 | |
elif state == 2: | |
if len(line) == 0: | |
state = 1 | |
else: | |
header = line | |
state = 3 | |
elif state == 3: | |
if len(line) == 0: | |
optd["series"][key] = opts | |
opts = {} | |
key = "" | |
state = 1 | |
else: | |
if line[2] != "<empty>": | |
puts = {} | |
calls = {} | |
strike = "" | |
exp = "" | |
isCalls = True | |
#Make a dictionary based on the header instead of | |
#by csv position. Things that come before "Strike" will get | |
#added to calls, after get added to puts | |
for i in range(0,len(header)): | |
if len(header[i]) == 0: | |
pass | |
elif header[i] == "Exp": | |
exp = line[i] | |
elif header[i] == "Strike": | |
strike = line[i] | |
isCalls = False | |
elif isCalls: | |
calls[header[i]] = line[i] | |
else: | |
puts[header[i]] = line[i] | |
# ['', '', 'Mark', 'Volume', 'Open.Int', 'Prob.ITM', 'Delta', 'BID', 'BX', 'ASK', 'AX', 'Exp', 'Strike', 'BID', 'BX', 'ASK', 'AX', 'Mark', 'Volume', 'Open.Int', 'Prob.ITM', 'Delta', '', ''] | |
# 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 | |
try: | |
st = float(strike) | |
cm = float(calls["Mark"]) | |
cp = float(calls["Prob.ITM"].replace("%","")) | |
co = int(calls["Open.Int"].replace(",", "")) | |
cv = int(calls["Volume"].replace(",", "")) | |
if (co > cv): cv = co | |
pm = float(puts["Mark"]) | |
pp = float(puts["Prob.ITM"].replace("%","")) | |
po = int(puts["Open.Int"].replace(",", "")) | |
pv = int(puts["Volume"].replace(",", "")) | |
if (po > pv): pv = po | |
opts[st] = { | |
"strike" : st, | |
"callmark" : cm, | |
"probcall" : cp, | |
"callvol" : cv, | |
"putmark" : pm, | |
"probput" : pp, | |
"putvol" : pv | |
} | |
except: | |
Log("conversion error",1) | |
return optd | |
def BestMatchBuyPut(series, putstrike, width): | |
bestmatch = 1000000000.0 | |
beststrike = None | |
for key in series.keys(): | |
if key < putstrike: | |
if abs(putstrike - (key + width)) < bestmatch: | |
bestmatch = abs(putstrike - (key + width)) | |
if bestmatch == 0.0: | |
return key | |
beststrike = key | |
if bestmatch < width * 2.0: | |
return beststrike | |
else: | |
return None | |
def BestMatchBuyCall(series, callstrike, width): | |
bestmatch = 1000000000.0 | |
beststrike = None | |
for key in series.keys(): | |
if key > callstrike: | |
if abs(callstrike - (key - width)) < bestmatch: | |
bestmatch = abs(callstrike - (key - width)) | |
if bestmatch == 0.0: | |
return key | |
beststrike = key | |
if bestmatch < width * 2.0: | |
return beststrike | |
else: | |
return None | |
def BestCallMatch(series, putprob, totalprob=30.0): | |
strikes = series.keys() | |
strikes.sort() | |
bestdiff = 1000000.0 | |
callmatch = None | |
for key in strikes: | |
diff = totalprob - (putprob + series[key]["probcall"]) | |
if abs(diff) < bestdiff: | |
bestdiff = abs(diff) | |
callmatch = key | |
return callmatch | |
def LogMatch(putb=0.0, puts=0.0, callb=0.0, calls=0.0, width=0.0, prob=0.0, credit=0.0, pricediff=0.0, underlying=None, header=False): | |
if header: | |
print "%8s %8s %8s %8s %8s %8s %8s %8s %-11s" % ("+put", "-put", "-call", "+call", "$wide", "ITM prob", "credit", "$-risk", "tilt") | |
else: | |
ulstr = "" | |
if underlying != None: | |
diff = int(9.0 * (calls - underlying) / (calls - puts)) | |
ulstr = "[" + "-"*(8-diff) + "|" + "-"*diff + "]" | |
print "%8.2f %8.2f %8.2f %8.2f %8.2f %7.2f%% %8.2f %8.2f %11s" % (putb, puts, calls, callb, width, prob, credit, pricediff, ulstr) | |
def FindIronCondors(series, underlying=None): | |
strikes = series.keys() | |
strikes.sort() | |
widths = [1.0, 2.0, 3.0, 4.0] | |
LogMatch(header=True) | |
for width in widths: | |
for key in strikes: | |
putprob = series[key]['probput'] | |
if putprob > 5.0 and putprob < 30: | |
try: | |
putsell = series[key] | |
callsell = series[BestCallMatch(series, putprob)] | |
putbuy = series[BestMatchBuyPut(series, key, width)] | |
callbuy = series[BestMatchBuyCall(series, callsell["strike"], width)] | |
totalprob = putprob + callsell["probcall"] | |
credit = putsell["putmark"] + callsell["callmark"] - (putbuy["putmark"] + callbuy["callmark"]) | |
riskdiff = credit - ((totalprob/100.0) * width) | |
LogMatch(putbuy["strike"], putsell["strike"], callbuy["strike"], callsell["strike"], width, totalprob, credit, riskdiff, underlying) | |
except: | |
pass | |
def ProcessData(info): | |
for key in info["series"].keys(): | |
m = re.match("\d* ... .. \((\d*)\)", key) | |
if int(m.group(1)) > 75: #skip far away options | |
continue | |
print "Processing %s (%.02f) - %s" % (info["symbol"], info["underlying"], key) | |
FindIronCondors(info["series"][key],info["underlying"]) | |
if len(sys.argv) == 1: | |
scandir = os.path.abspath(os.path.expanduser("~/Desktop")) | |
else: | |
scandir = os.path.abspath(os.path.expanduser(sys.argv[1])) | |
files = glob.glob(scandir+"/*.csv") | |
for f in files: | |
info = QuotesFromFile(f) | |
ProcessData(info) |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment