vix reversal trigger in python

I wrote a python script to send an email whenever a particular type of VIX reversal indicator triggers. Basically if the following conditions are met, it will send an email:
* If the VIX index has a NEW 15-day high today and closed below the open, then it suggests a buy indicator.
* If the VIX index has a NEW 15-day low today and closed above the open, then it suggests a sell indicator.

I decided to write this in python since I’ve never used it before. It was a fun learning experience. I was a bit taken back by the lack of statement terminators and the indenting rules, but in all I like it better than perl.

This script runs every weekday at 4:15pm (since the yahoo stock quotes are delayed 15 minutes).

Future enhancement ideas are to include the 15-day chart from stockcharts.com as an inline attachment (I have a bash script which already does this so it shouldn’t be that hard to port), as well as proper cleanup & organization of the code once/if I ever learn more python.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
#!/usr/bin/env python
 
import sys
import datetime
import smtplib
import ystockquote
 
from email.mime.text import MIMEText
 
# ticker to look up
ticker = '^VIX'
# number of days of historical data to fetch (not counting today)
days_back = 14
 
email_from = 'redacted'
email_to = 'redacted'
 
# yesterday's date
end = datetime.date.today() + datetime.timedelta(days= -1)
# 30 days ago from today
start = datetime.date.today() + datetime.timedelta(days= -30)
 
# fetch the historical data from yahoo
data = ystockquote.get_historical_prices(ticker, start.strftime("%Y%m%d"), end.strftime("%Y%m%d"))
 
# validate that we have enough historical data to continue
if len(data) < (days_back + 1):
    print 'Not enough historical data available to continue (need at least 15 days of market data)'
    sys.exit(1)
 
current = ystockquote.get_price(ticker)
open = ystockquote.get_open(ticker)
high = ystockquote.get_high(ticker)
low = ystockquote.get_low(ticker)
 
'''
print 'start date is: ' + str(start)
print 'end date is: ' + str(end)
print 'number of days of actual market data retrieved is: ' + str((len(data) - 1))
print 'Current price: ' + str(current)
print 'Open price: ' + str(open)
print 'High price: ' + str(high)
print 'Low price: ' + str(low)
 
# Loop through the previous 14-days of data (the first item in the data array
# is omitted because it is just header information
print 'the previous 14-day of historical data:'
for i in range(1, (days_back + 1)):
    print data[i][0] + ': High:' + data[i][2] + ' Low:' + data[i][3]
'''
 
def isNewHigh(high, data):
    """
    Returns True if the 'high' is higher than any of the highs in the data
    array passed in. Otherwise returns False
    """
    for i in range(1, (days_back + 1)):
        if float(data[i][2]) >= float(high):
            return False
    return True
 
def isNewLow(low, data):
    """
    Returns True if the 'low' is lower than any of the lows in the data
    array passed in. Otherwise returns False
    """
    for i in range(1, (days_back + 1)):
        if float(data[i][3]) < = float(low):
            return False
        return True
 
def isCurrentHigherThanOpen(current, open):
    """
    Simple check to see if the current price is greater than the open price
    """
    return float(current) > float(open)
 
def isCurrentLowerThanOpen(current, open):
    """
    Simple check to see if the current price is lower than the open price
    """
    return float(current) < float(open)
 
buy = False
sell = False
 
# if the current price is higher than the open and today is a new 15-day low, 
# then this is a SELL indicator
if (isCurrentHigherThanOpen(current, open) & isNewLow(low, data)):
    #print 'today is a 15-day low (' + str(low) + ') & the current price (' + str(current) + ') is higher than the open (' + str(open) + ')'
    sell = True
 
# if the current price is lower than the open and today is a new 15-day high, 
# then this is a BUY indicator
if (isCurrentLowerThanOpen(current, open) & isNewHigh(high, data)):
    #print 'today is a 15-day high (' + str(high) + ') & the current price (' + str(current) + ') is lower than the open (' + str(open) + ')'
    buy = True
 
if (buy | sell):
    if buy:
        body = 'Today is a new 15-day high (' + str(high) + ') & the current price (' + str(current) + ') is lower than the open (' + str(open) + ')\n\n====>This is a VIX BUY indicator!'
        subject = 'VIX BUY indicator triggered'
    else:
        body = 'Today is a new 15-day low (' + str(low) + ') & the current price (' + str(current) + ') is higher than the open (' + str(open) + ')\n\n====>This is a VIX SELL indicator!'
        subject = 'VIX SELL indicator triggered'
    msg = MIMEText(body)
    msg['Subject'] = subject
    msg['From'] = email_from
    msg['To'] = email_to
    s = smtplib.SMTP('localhost')
    s.sendmail(email_from, email_to, msg.as_string())
    s.quit()

I took the ystockquote.py from Corey Goldberg and had to extend it slightly to include some additional information:
* day’s high price
* day’s low price
* last trade time
* last ‘real time’ trade + time (this doesn’t appear to be real time after all)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
#!/usr/bin/env python
#
#  Copyright (c) 2007-2008, Corey Goldberg (corey@goldb.org)
#
#  license: GNU LGPL
#
#  This library is free software; you can redistribute it and/or
#  modify it under the terms of the GNU Lesser General Public
#  License as published by the Free Software Foundation; either
#  version 2.1 of the License, or (at your option) any later version.
 
 
import urllib
 
 
"""
This is the "ystockquote" module.
 
This module provides a Python API for retrieving stock data from Yahoo Finance.
 
sample usage:
>>> import ystockquote
>>> print ystockquote.get_price('GOOG')
529.46
"""
 
 
def __request(symbol, stat):
    url = 'http://finance.yahoo.com/d/quotes.csv?s=%s&f=%s' % (symbol, stat)
    return urllib.urlopen(url).read().strip().strip('"')
 
 
def get_all(symbol):
    """
    Get all available quote data for the given ticker symbol.
 
    Returns a dictionary.
    """
    values = __request(symbol, 'l1c1va2xj1b4j4dyekjm3m4rr5p5p6s7oahagat1k1').split(',')
    data = {}
    data['price'] = values[0]
    data['change'] = values[1]
    data['volume'] = values[2]
    data['avg_daily_volume'] = values[3]
    data['stock_exchange'] = values[4]
    data['market_cap'] = values[5]
    data['book_value'] = values[6]
    data['ebitda'] = values[7]
    data['dividend_per_share'] = values[8]
    data['dividend_yield'] = values[9]
    data['earnings_per_share'] = values[10]
    data['52_week_high'] = values[11]
    data['52_week_low'] = values[12]
    data['50day_moving_avg'] = values[13]
    data['200day_moving_avg'] = values[14]
    data['price_earnings_ratio'] = values[15]
    data['price_earnings_growth_ratio'] = values[16]
    data['price_sales_ratio'] = values[17]
    data['price_book_ratio'] = values[18]
    data['short_ratio'] = values[19]
    data['open'] = values[20]
    data['days_high'] = values[21]
    data['days_low'] = values[22]
    data['last_trade_time'] = values[23]
    data['last_trade_real_time'] = values[24]
    return data
 
 
def get_price(symbol): 
    return __request(symbol, 'l1')
 
 
def get_change(symbol):
    return __request(symbol, 'c1')
 
 
def get_volume(symbol): 
    return __request(symbol, 'v')
 
 
def get_avg_daily_volume(symbol): 
    return __request(symbol, 'a2')
 
 
def get_stock_exchange(symbol): 
    return __request(symbol, 'x')
 
 
def get_market_cap(symbol):
    return __request(symbol, 'j1')
 
 
def get_book_value(symbol):
    return __request(symbol, 'b4')
 
 
def get_ebitda(symbol): 
    return __request(symbol, 'j4')
 
 
def get_dividend_per_share(symbol):
    return __request(symbol, 'd')
 
 
def get_dividend_yield(symbol): 
    return __request(symbol, 'y')
 
 
def get_earnings_per_share(symbol): 
    return __request(symbol, 'e')
 
 
def get_52_week_high(symbol): 
    return __request(symbol, 'k')
 
 
def get_52_week_low(symbol): 
    return __request(symbol, 'j')
 
 
def get_50day_moving_avg(symbol): 
    return __request(symbol, 'm3')
 
 
def get_200day_moving_avg(symbol): 
    return __request(symbol, 'm4')
 
 
def get_price_earnings_ratio(symbol): 
    return __request(symbol, 'r')
 
 
def get_price_earnings_growth_ratio(symbol): 
    return __request(symbol, 'r5')
 
 
def get_price_sales_ratio(symbol): 
    return __request(symbol, 'p5')
 
 
def get_price_book_ratio(symbol): 
    return __request(symbol, 'p6')
 
 
def get_short_ratio(symbol): 
    return __request(symbol, 's7')
 
 
def get_open(symbol): 
    return __request(symbol, 'o')
 
 
def get_high(symbol): 
    return __request(symbol, 'h')
 
 
def get_low(symbol): 
    return __request(symbol, 'g')
 
 
def get_last_trade_time(symbol): 
    return __request(symbol, 't1')
 
 
def get_last_real_time_trade(symbol): 
    return __request(symbol, 'k1')
 
 
def get_historical_prices(symbol, start_date, end_date):
    """
    Get historical prices for the given ticker symbol.
    Date format is 'YYYYMMDD'
 
    Returns a nested list.
    """
    url = 'http://ichart.yahoo.com/table.csv?s=%s&' % symbol + \
          'd=%s&' % str(int(end_date[4:6]) - 1) + \
          'e=%s&' % str(int(end_date[6:8])) + \
          'f=%s&' % str(int(end_date[0:4])) + \
          'g=d&' + \
          'a=%s&' % str(int(start_date[4:6]) - 1) + \
          'b=%s&' % str(int(start_date[6:8])) + \
          'c=%s&' % str(int(start_date[0:4])) + \
          'ignore=.csv'
    days = urllib.urlopen(url).readlines()
    data = [day[:-2].split(',') for day in days]
    return data

daily vix chart

A bash script to email an inline image of the previous 15-day ^VIX index chart:
daily_stocks.sh

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
#!/bin/bash
# requires: basename,date,sed,sendmail,uuencode,wget
# portions of this taken from http://www.zedwood.com/article/103/bash-send-mail-with-an-attachment
 
function fappend {
    echo "$2">>$1;
}
DATE=`date +%F`
 
# CHANGE THESE
SYMBOL="\$VIX"
DAYS="15"
URL="http://stockcharts.com/c-sc/sc?s=${SYMBOL}&p=D&yr=0&mn=0&dy=${DAYS}&i=p16348210875&r=4872"
TOEMAIL=redacted
FREMAIL=redacted
SUBJECT="Daily ${SYMBOL} for `date +%c`";
ATTACHMENT="/tmp/${SYMBOL}-${DATE}.png"
 
# DON'T CHANGE ANYTHING BELOW
TMP="/tmp/tmpfil_123"$RANDOM;
FILENAME=`basename $ATTACHMENT`
 
# Fetch the daily chart and put it into ${TMP}.png
/opt/local/bin/wget -q -O ${TMP}.png "${URL}"
 
rm -rf $TMP;
cat ${TMP}.png | uuencode -m $FILENAME>$TMP;
sed -i -e '1,1d' $TMP;#removes first & last lines from $TMP
DATA=`cat $TMP`
 
rm -rf ${TMP}.png
rm -rf $TMP;
fappend $TMP "From: $FREMAIL";
fappend $TMP "To: $TOEMAIL";
fappend $TMP "Reply-To: $FREMAIL";
fappend $TMP "Subject: $SUBJECT";
fappend $TMP "Content-Type: image/png; name=\"$FILENAME\"";
fappend $TMP "Content-Transfer-Encoding: base64";
fappend $TMP "Content-Disposition: inline; filename=\"$FILENAME\";";
fappend $TMP "";
fappend $TMP "$DATA";
fappend $TMP "";
fappend $TMP "";
fappend $TMP "";
fappend $TMP "";
#cat $TMP>out.txt
cat $TMP|/usr/sbin/sendmail -t;
rm $TMP;
 
# sleep for 10 seconds to prevent launchd cleanup
sleep 10

Now, the launchd plist (think cron for OSX) to invoke the script every weekday at 4pm:
com.billimek.stocks.plist

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
< ?xml version="1.0" encoding="UTF-8"?>
< !DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
        <key>AbandonProcessGroup</key>
        <true />
        <key>Disabled</key>
        <false />
        <key>Label</key>
        <string>com.billimek.stocks</string>
        <key>LowPriorityIO</key>
        <true />
        <key>Nice</key>
        <integer>1</integer>
        <key>ProgramArguments</key>
        <array>
                <string>/usr/local/bin/daily_stocks.sh</string>
        </array>
    <key>StartCalendarInterval</key>
    <array> 
           <dict>
            <key>Hour</key>
            <integer>16</integer>
            <key>Minute</key>
            <integer>00</integer>
            <key>Weekday</key>
            <integer>1</integer>
        </dict>
        <dict>
            <key>Hour</key>
            <integer>16</integer>
            <key>Minute</key>
            <integer>00</integer>
            <key>Weekday</key>
            <integer>2</integer>
        </dict>
        <dict>
            <key>Hour</key>
            <integer>16</integer>
            <key>Minute</key>
            <integer>00</integer>
            <key>Weekday</key>
            <integer>3</integer>
        </dict>
        <dict>
            <key>Hour</key>
            <integer>16</integer>
            <key>Minute</key>
            <integer>00</integer>
            <key>Weekday</key>
            <integer>4</integer>
        </dict>
        <dict>
            <key>Hour</key>
            <integer>16</integer>
            <key>Minute</key>
            <integer>00</integer>
            <key>Weekday</key>
            <integer>5</integer>
        </dict>
    </array>
        <key>UserName</key>
        <string>jeff</string>
</dict>
</plist>

This is what it looks like as an email on my iphone:
daily vix example email

Jeff’s photography metadata statistics

Some fun statistics from my photograph library (lightroom) that contains photographs from 2002 through present-day:

48,711 photos total
5,839 with 3 stars (good enough to publish)

—Top 6 camera models used:

  • 20,486: Canon EOS 5D
  • 10,698: Canon EOS 10D
  • 10,429: Canon EOS 20D
  • 2,837: Minolta Dimage 7i
  • 2,593: Canon PowerShot D=S410
  • 512: Canon EOS 1Ds Mark III

—Top 7 Lenses used:

  • 8,712: EF17-40mm f/4L USM 8,712
  • 7,317: EF50mm f/1.8
  • 7,038: EF70-200mm f/2.8L IF USM
  • 6,949: EF28-135mm IF USM
  • 5,153: 15mm Fisheye
  • 3,592: EF100mm f/2.8L Macro USM
  • 1,239: EF24-70mm USM

—Top 7 names tagged:

  • 6,318: Ansley Billimek
  • 5,067: Jennifer Billimek
  • 3,191: Jeff Billimek
  • 1,674: Larry Billimek
  • 1,405: Tucker Billimek
  • 1,173: Brinley Billimek
  • 1,140: Lily Billimek

nom nom nom

So Jen and I went through the starbucks drive-thru and they gave us a free sample of their cinnamon roll.  Jen didn’t want any so I gladly ate it.  It was good and I made cookie-monster sounds as I ate, “Nom Nom Nom”.  Jen gave me that look again.  I was all, “well at least I’m not making pig noises as I eat”.  And then I had a funny thought which I shared with Jen: “Why don’t we go to a fancy restaurant and make squealing noises like pigs while we eat?”  As I imagined this in my minds-eye it seemed really funny and I started laughing hysterically.  Jen didn’t find it funny though.  Jen was all, “I’m glad you can make yourself laugh.”

working too many hours

So after I went back to sleep this morning I got a call a couple of hours later from my director.  Aparantly there were some problems with the sever and he needed my help.  I worked on this until about 6pm.  I worked from home all day today.

Jen had taken my car to work today and brought it home washed.  It looked great and I was so excited that it was nice and clean!  She also had gone shopping and bought some new tennis shirts for me and some new tennis skirts for herself.  I made tofu rubens for dinner tonight.  Jennifer thought that I was a little grumpy but I explained that I was just tired.

I’m a terrible cook and managed to make the tofu chewy.  This is something that Jennifer has never seen done before.  After dinner we watched an episode of ‘24′.

production support

When I got home from work today I took a nap as I was very tired.  I went to bed at 10pm and woke up at 1:40am to do more work on the server migration.  There were issues and it took longer than expected.  I didn’t finish until about 5am.  I went to bed but then was paged an hour later.  There were more problems.

I worked on this some more while Jennifer woke up, showered, dressed, and left.  I continued to work until about 7am at which point I went to bed again.

memorial day

Because it is Memorial Day, Jennifer had off from work.  I didn’t.  She was still in bed when I left this morning at 8:30am.  It was very quiet at work today.  This evening I spent a lot of time working on a unix server patch failover.  I ended up working from 11pm until 3am.  Kumar helped too.




Powered By Site5.com