Ticket #3621: sage-3621-combined.patch

File sage-3621-combined.patch, 22.0 KB (added by cswiercz, 13 years ago)

Replacement patch for all patches listed above. Combines all changes.

  • new file finance/AAPL-minutely.csv

    # HG changeset patch
    # User Brett Nakashima <brettnak@gmail.com>
    # Date 1218149661 25200
    # Node ID d3fe688b1cf71619f33d3c25562d3343caf6a1c0
    # Parent  5e2426d277ebfd4f7010fb71fee7d5ab3f3574b2
    Combined all patches in #3621 and updated added cid.
    * * *
    Split .historical() in to three parts, added .load_file and ._load_from_csv
    * * *
    added a close command to the .load_file()
    * * *
    Fixed but in .load_file()
    * * *
    Various bug fixes. Added documentation.
    * * *
    Added finance.Stock.load_from_file() example. CSV-formatted data file with AAPL OHLC stock information from the first few minutes of the month of June.
    * * *
    All corrections from referee report and new doctests.
    * * *
    Fixed spelling errors in doctests
    * * *
    Changed a try/excpet block, added a doctest to demonstrate
    * * *
    Fixed quote error in doc string and cache problem in open and close
    * * *
    Fixed example doctests for Stock.load_from_file(). Uses example in SAGE_ROOT/examples.
    
    diff -r 5e2426d277eb -r d3fe688b1cf7 finance/AAPL-minutely.csv
    - +  
     1Date,Open,High,Low,Close,Volume
     21212405780,187.80,187.80,187.80,187.80,100
     31212407640,187.75,188.00,187.75,188.00,2000
     41212407700,188.00,188.00,188.00,188.00,1000
     51212408000,188.00,188.11,188.00,188.00,2877
     61212408060,188.00,188.00,188.00,188.00,687
     7 No newline at end of file
  • sage/finance/stock.py

    diff -r 5e2426d277eb -r d3fe688b1cf7 sage/finance/stock.py
    a b Stock market price series 
    33
    44AUTHORS:
    55    -- William Stein, 2008
     6    -- Brett Nakayama, 2008
     7    -- Chris Swierczewski, 2008
    68
    79TESTS:
    8     sage: day = sage.finance.stock.Day('18-Aug-04', 100.01, 104.06, 95.96, 100.34, 22353092)
    9     sage: loads(dumps(day)) == day
     10    sage: ohlc = sage.finance.stock.OHLC('18-Aug-04', 100.01, 104.06, 95.96, 100.34, 22353092)
     11    sage: loads(dumps(ohlc)) == ohlc
    1012    True
    1113"""
    1214
    1315import urllib
    1416from sage.structure.all import Sequence
     17from datetime import date
    1518
    16 class Day:
    17     def __init__(self, date, open, high, low, close, volume):
     19class OHLC:
     20    def __init__(self, timestamp, open, high, low, close, volume):
    1821        """
    19         Summary stats for a day that a stock traded.
     22        Open, high, low, and close information for a stock. Also stores
     23        a timestamp for that data along with the volume.
    2024
    2125        INPUT:
    22             date -- string
     26            timestamp -- string
    2327            open, high, low, close -- float
    2428            volume -- int
    2529
    2630        EXAMPLES:
    27             sage: sage.finance.stock.Day('18-Aug-04', 100.01, 104.06, 95.96, 100.34, 22353092)
     31            sage: sage.finance.stock.OHLC('18-Aug-04', 100.01, 104.06, 95.96, 100.34, 22353092)
    2832             18-Aug-04 100.01 104.06 95.96 100.34   22353092
    2933        """
    30         self.date = date
     34        self.timestamp = timestamp
    3135        self.open=float(open); self.high=float(high); self.low=float(low); self.close=float(close)
    3236        self.volume=int(volume)
    3337       
    3438    def __repr__(self):
    3539        """
    36         Return string representation of stock day.
     40        Return string representation of stock OHLC data.
    3741
    3842        EXAMPLES:
    39             sage: sage.finance.stock.Day('18-Aug-04', 100.01, 104.06, 95.96, 100.34, 22353092).__repr__()
     43            sage: sage.finance.stock.OHLC('18-Aug-04', 100.01, 104.06, 95.96, 100.34, 22353092).__repr__()
    4044            ' 18-Aug-04 100.01 104.06 95.96 100.34   22353092'
    4145        """
    42         return '%10s %4.2f %4.2f %4.2f %4.2f %10d'%(self.date, self.open, self.high,
     46        return '%10s %4.2f %4.2f %4.2f %4.2f %10d'%(self.timestamp, self.open, self.high,
    4347                   self.low, self.close, self.volume)
    4448
    4549    def __cmp__(self, other):
    class Day: 
    4751        Compare self and other.
    4852
    4953        EXAMPLES:
    50             sage: day = sage.finance.stock.Day('18-Aug-04', 100.01, 104.06, 95.96, 100.34, 22353092)
    51             sage: day2 = sage.finance.stock.Day('18-Aug-04', 101.01, 104.06, 95.96, 100.34, 22353092)
    52             sage: cmp(day, day2)
     54            sage: ohlc = sage.finance.stock.OHLC('18-Aug-04', 100.01, 104.06, 95.96, 100.34, 22353092)
     55            sage: ohlc2 = sage.finance.stock.OHLC('18-Aug-04', 101.01, 104.06, 95.96, 100.34, 22353092)
     56            sage: cmp(ohlc, ohlc2)
    5357            -1
    5458        """
    55         if not isinstance(other, Day):
     59        if not isinstance(other, OHLC):
    5660            return cmp(type(self), type(other))
    57         return cmp((self.date, self.open, self.high, self.low, self.close, self.volume),
    58                    (other.date, other.open, other.high, other.low, other.close, other.volume))
     61        return cmp((self.timestamp, self.open, self.high, self.low, self.close, self.volume),
     62                   (other.timestamp, other.open, other.high, other.low, other.close, other.volume))
    5963
    6064class Stock:
    61     def __init__(self, symbol):
     65    """
     66    Class for retrieval of stock market information.
     67    """
     68    def __init__(self, symbol, cid=''):
    6269        """
    63         Create a Stock object.
     70        Create a Stock object. Optional initialization by cid: an identifier
     71        for each equity used by Google Finance.
    6472
    6573        INPUT:
    66             symbol -- string, a ticker symbol
     74            symbol -- string, a ticker symbol (with or without market)
     75                      format: "MARKET:SYMBOL" or "SYMBOL", if you don't
     76                              supply the market, it is assumed to be
     77                              NYSE or NASDAQ.
     78                      eg. "goog" or "OTC:NTDOY"
     79            cid    -- Integer, a Google contract ID (optional)
     80
     81        LIMITATIONS:
     82            Currently, the symbol and cid do not have to match.  When using
     83            google(), the cid will take precedence.
    6784
    6885        EXAMPLES:
    6986            sage: S = finance.Stock('ibm')
    class Stock: 
    7188            IBM (127.48)           
    7289        """
    7390        self.symbol = symbol.upper()
     91        self.cid = cid
    7492
    7593    def __repr__(self):
    7694        """
    class Stock: 
    131149        data['short_ratio'] = values[19]
    132150        return data
    133151
    134     def historical(self,startdate='Jan+1,+1990'):
     152    def google(self,startdate='Jan+1,+1900',enddate=date.today().strftime("%b+%d,+%Y"), histperiod='daily'):
    135153        """
    136154        Return an immutable sequence of historical price data
    137         for this stock, obtained from Google.
     155        for this stock, obtained from Google. OHLC data is stored
     156        internally as well. By default, returns the past year's daily
     157        OHLC data.
     158
     159        Dates startdate and enddate should be formatted 'Mon+d,+yyyy' where
     160        'Mon' is a three character abbreviation of the month's name.
     161
     162        NOTE:
     163            Google Finance returns the past year's financial data by default
     164            when startdate is set too low from the equity's date of going
     165            public.  By default, this function only looks at the NASDAQ and
     166            NYSE markets.  However, if you sepcified the market during
     167            initialization of the stock (i.e. "finance.Stock("OTC:NTDOY")"),
     168            Stock.google() will give correct results.
    138169
    139170        INPUT:
    140             startdate -- string, (default: 'Jan+1,+1990')
     171            startdate -- string, (default: 'Jan+1,+1900')
     172            enddate   -- string, (default: current date )
     173            histperiod -- string, ('daily' or 'weekly')
    141174
    142175        OUTPUT:
    143176            Sequence
    144177
    145178        EXAMPLES:
    146179        We get the first five days of VMware's stock history:
    147             sage: finance.Stock('vmw').historical()[:5]   # optional -- requires internet
     180            sage: finance.Stock('vmw').google()[:5]   # optional -- requires internet
    148181            [
    149182             15-Aug-07 52.11 59.87 51.50 57.71   10678500,
    150183             16-Aug-07 60.99 61.49 52.71 56.99    6919500,
    class Stock: 
    152185             20-Aug-07 56.05 57.50 55.61 57.33    2140900,
    153186             21-Aug-07 57.25 66.59 56.50 65.99    7369700
    154187            ]
     188           
     189            sage: finance.Stock('F').google('Jan+3,+1978', 'Jul+7,+2008')[:5] # optional -- requires internet
     190            [
     191              3-Jan-78 2.90 2.90 2.84 2.85    1074495,
     192              4-Jan-78 2.84 2.84 2.81 2.83    1648713,
     193              5-Jan-78 2.83 2.84 2.77 2.77    1988524,
     194              6-Jan-78 2.77 2.77 2.74 2.76    2019988,
     195              9-Jan-78 2.73 2.73 2.69 2.73    2600499
     196            ]
     197           
     198        Note that when startdate is too far prior to a stock's actual start
     199        date, Google Finance defaults to a year's worth of stock history
     200        leading up to the specified enddate.  For example, Apple's (AAPL) stock
     201        history only dates back to September 7, 1984
     202           
     203            sage: finance.Stock('AAPL').google('Sep+1,+1900', 'Jan+1,+2000')[0:5] # optional -- requires internet
     204            [
     205              4-Jan-99 10.53 10.56 10.00 10.31   34031600,
     206              5-Jan-99 10.48 10.98 10.38 10.83   50361200,
     207              6-Jan-99 11.03 11.03 10.25 10.44   48163200,
     208              7-Jan-99 10.56 11.27 10.53 11.25   51036400,
     209              8-Jan-99 11.64 11.72 11.00 11.25   24244000
     210            ]
     211           
     212        Here is an example where we create and get the history of a stock
     213        that is not in NASDAQ or NYSE
     214               
     215            sage: finance.Stock("OTC:NTDOY").google(startdate="Jan+1,+2007", enddate="Jan+1,+2008")[:5]
     216            [
     217              3-Jan-07 32.44 32.75 32.30 32.44     156300,
     218              4-Jan-07 31.70 32.40 31.20 31.70     222700,
     219              5-Jan-07 30.15 30.50 30.15 30.15      65700,
     220              8-Jan-07 30.10 30.50 30.00 30.10     130800,
     221              9-Jan-07 29.90 30.05 29.60 29.90     103400
     222            ]
     223
     224       
     225        Here, we create a stock by cid, and get historical data.
     226        Note that when using historical, if a cid is specified,
     227        it will take precedence over the stock's symbol.  So, if
     228        the symbol and cid do not match, the history based on the
     229        contract id will be returned.
     230       
     231            sage: sage.finance.stock.Stock("AAPL", 22144).google(startdate='Jan+1,+1990')[:5] #optional -- requires internet
     232            [
     233              2-Jan-90 8.81 9.38 8.75 9.31    6542800,
     234              3-Jan-90 9.50 9.50 9.38 9.38    7428400,
     235              4-Jan-90 9.56 9.69 9.31 9.41    7911200,
     236              5-Jan-90 9.44 9.56 9.25 9.44    4404000,
     237              8-Jan-90 9.38 9.50 9.25 9.50    3627600
     238            ]
     239
     240        """
     241        cid = self.cid
     242        symbol = self.symbol
     243       
     244        if self.cid=='':
     245            if ':' in symbol:
     246                R = self._get_data('', startdate, enddate, histperiod)
     247            else:
     248                R = self._get_data('NASDAQ:', startdate, enddate, histperiod)
     249                if "Bad Request" in R:
     250                     R = self._get_data("NYSE:", startdate, enddate, histperiod)
     251        else:
     252            R = self._get_data('', startdate, enddate, histperiod)
     253        if "Bad Request" in R:
     254            raise RuntimeError
     255        self.__historical = []
     256        self.__historical = self._load_from_csv(R)
     257        return self.__historical
     258
     259    def open(self, *args, **kwds):
     260        r"""
     261        Return a TimeSeries containing historical opening prices for this
     262        stock. If no arguments are given, will return last acquired historical
     263        data. Otherwise, data will be gotten from Google Finance.
     264
     265        INPUT:
     266            startdate  -- string, (default: 'Jan+1,+1900')
     267            enddate    -- string, (default: current date)
     268            histperiod -- string, ('daily' or 'weekly')
     269
     270        OUTPUT:
     271            TimeSeries -- Close price data
     272
     273        EXAMPLES:
     274        You can directly obtain Open data as so:
     275            sage: finance.Stock('vmw').open(startdate='Jan+1,+2008', enddate='Feb+1,+2008')                 # optional -- requires internet
     276            [83.0500, 85.4900, 84.9000, 82.0000, 81.2500 ... 82.0000, 58.0500, 54.4900, 55.6000, 56.9800]
     277
     278        Or, you can initialize stock data first and then extract the Open
     279        data:
     280            sage: c = finance.Stock('vmw')
     281            sage: c.google(startdate='Feb+1,+2008', enddate='Mar+1,+2008')[:5]    # optional -- requires internet
     282            [
     283             31-Jan-08 55.60 57.35 55.52 56.67    2607800,
     284              1-Feb-08 56.98 58.14 55.06 57.85    2489400,
     285              4-Feb-08 58.00 60.47 56.91 58.05    1840300,
     286              5-Feb-08 57.60 59.30 57.17 59.30    1711700,
     287              6-Feb-08 60.32 62.00 59.50 61.52    2209700
     288            ]
     289            sage: c.open()    # optional -- requires internet
     290            [55.6000, 56.9800, 58.0000, 57.6000, 60.3200 ... 56.5500, 59.3000, 60.0000, 59.7900, 59.2600]
     291
     292        Otherwise, \code{self.google()} will be called with the default
     293        arguements returning a year's worth of data:
     294            sage: finance.Stock('vmw').open()   # optional and random -- requires internet and depends on day
     295            [52.1100, 60.9900, 59.0000, 56.0500, 57.2500 ... 83.0500, 85.4900, 84.9000, 82.0000, 81.2500]
     296
     297        """
     298       
     299        from time_series import TimeSeries
     300       
     301        if len(args) != 0:
     302            return TimeSeries([x.open for x in self.google(*args, **kwds)])
     303           
     304        try:
     305            return TimeSeries([x.open for x in self.__historical])
     306        except AttributeError:
     307            pass
     308       
     309        return TimeSeries([x.open for x in self.google(*args, **kwds)])
     310   
     311    def close(self, *args, **kwds):
     312        r"""
     313        Return the time series of all historical closing prices for this stock.
     314        If no arguments are given, will return last aquired historical data.
     315        Otherwise, data will be gotten from Google Finance.
     316
     317        INPUT:
     318            startdate  -- string, (default: 'Jan+1,+1900')
     319            enddate    -- string, (default: current date)
     320            histperiod -- string, ('daily' or 'weekly')
     321
     322        OUTPUT:
     323            TimeSeries -- Close price data
     324
     325        EXAMPLES:
     326        You can directly obtain close data as so:
     327            sage: finance.Stock('vmw').close(startdate='Jan+1,+2008', enddate='Feb+1,+2008')                 # optional -- requires internet
     328            [84.9900, 84.6000, 83.9500, 80.4900, 72.9900 ... 83.0000, 54.8700, 56.4200, 56.6700, 57.8500]
     329
     330        Or, you can initialize stock data first and then extract the Close
     331        data:
     332            sage: c = finance.Stock('vmw')
     333            sage: c.google(startdate='Feb+1,+2008', enddate='Mar+1,+2008')[:5]    # optional -- requires internet
     334            [
     335             31-Jan-08 55.60 57.35 55.52 56.67    2607800,
     336              1-Feb-08 56.98 58.14 55.06 57.85    2489400,
     337              4-Feb-08 58.00 60.47 56.91 58.05    1840300,
     338              5-Feb-08 57.60 59.30 57.17 59.30    1711700,
     339              6-Feb-08 60.32 62.00 59.50 61.52    2209700
     340            ]
     341            sage: c.close()    # optional -- requires internet
     342            [56.6700, 57.8500, 58.0500, 59.3000, 61.5200 ... 58.2900, 60.1800, 59.8600, 59.9500, 58.6700]
     343
     344
     345
     346        Otherwise, \code{self.google()} will be called with the default
     347        arguements returning a year's worh of data:
     348            sage: finance.Stock('vmw').close()   # optional and random -- requires internet and depends on day
     349            [57.7100, 56.9900, 55.5500, 57.3300, 65.9900 ... 84.9900, 84.6000, 83.9500, 80.4900, 72.9900]
     350        """
     351               
     352        from time_series import TimeSeries
     353       
     354        if len(args) != 0:
     355            return TimeSeries([x.close for x in self.google(*args, **kwds)])
     356           
     357        try:
     358            return TimeSeries([x.close for x in self.__historical])
     359        except AttributeError:
     360            pass
     361       
     362        return TimeSeries([x.close for x in self.google(*args, **kwds)])
     363       
     364    def load_from_file(self, file):
     365        """
     366        Load historical data from a local csv formatted data file. Note
     367        that no symbol data is included in Google Finance's csv data.
     368        The csv file must be formatted in the following way, just as
     369        on Google Finance:
     370
     371        Timestamp,Open,High,Low,Close,Volume
     372
     373        INPUT:
     374            file -- local file with Google Finance formatted OHLC data
     375
     376        OUTPUTS:
     377            Sequence -- OHLC data
     378
     379        EXAMPLES:
     380        Suppose you have a file in your home directoy containing Apple stock
     381        OHLC data, such as that from Google Finance, called AAPL-minutely.csv.
     382        One can load this information into a Stock object like so. Note that
     383        the path must be explicit:
     384            sage: finance.Stock('aapl').load_from_file(SAGE_ROOT + '/examples/finance/AAPL-minutely.csv')[:5]
     385            [
     386            1212408060 188.00 188.00 188.00 188.00        687,
     387            1212408000 188.00 188.11 188.00 188.00       2877,
     388            1212407700 188.00 188.00 188.00 188.00       1000,
     389            1212407640 187.75 188.00 187.75 188.00       2000,
     390            1212405780 187.80 187.80 187.80 187.80        100
     391            ]
     392
     393
     394        Note that since the source file doesn't contain information on which
     395        equity the information comes from, the symbol designated at
     396        initialization of Stock need not match the source of the data. For
     397        example, we can initialize a Stock object with the symbol 'goog',
     398        but load data from 'aapl' stock prices:
     399            sage: finance.Stock('goog').load_from_file(SAGE_ROOT + '/examples/finance/AAPL-minutely.csv')[:5]
     400            [
     401            1212408060 188.00 188.00 188.00 188.00        687,
     402            1212408000 188.00 188.11 188.00 188.00       2877,
     403            1212407700 188.00 188.00 188.00 188.00       1000,
     404            1212407640 187.75 188.00 187.75 188.00       2000,
     405            1212405780 187.80 187.80 187.80 187.80        100
     406            ]
     407
     408        This tests a file that doesn't exist:
     409            sage: finance.Stock("AAPL").load_from_file("I am not a file")
     410            Bad path or file name
     411
    155412        """
    156413        try:
     414            file_obj = open(file, 'r')
     415            R = file_obj.read();
     416            self.__historical = self._load_from_csv(R)
     417            file_obj.close()
    157418            return self.__historical
    158         except AttributeError:
    159             pass
    160         symbol = self.symbol
    161         def get_data(exchange=''):
    162             """
    163             This function is used internally.
     419        except IOError, msg:
     420            print "Bad path or file name"
     421       
     422    def _load_from_csv(self, R):
     423        """
     424        EXAMPLES:
     425        This indirectly tests _load_from_csv():
     426            sage: finance.Stock('aapl').load_from_file(SAGE_ROOT + "/examples/finance/AAPL-minutely.csv")
     427            [
     428            1212408060 188.00 188.00 188.00 188.00        687,
     429            1212408000 188.00 188.11 188.00 188.00       2877,
     430            1212407700 188.00 188.00 188.00 188.00       1000,
     431            1212407640 187.75 188.00 187.75 188.00       2000,
     432            1212405780 187.80 187.80 187.80 187.80        100
     433            ]
    164434
    165             EXAMPLES:
    166             This indirectly tests the use of get_data.
    167                 sage: finance.Stock('aapl').historical()[:2]    # optional -- requires internet
    168                 [
    169                   2-Jan-90 8.81 9.38 8.75 9.31    6542800,
    170                   3-Jan-90 9.50 9.50 9.38 9.38    7428400
    171                 ]
    172             """
    173             url = 'http://finance.google.com/finance/historical?q=%s%s&output=csv&startdate=%s'%(
    174                  exchange, symbol.upper(),startdate)
    175             return urllib.urlopen(url).read()
    176         if ':' in symbol:
    177             R = get_data()
    178         else:
    179             R = get_data('NASDAQ:')
    180             if "Bad Request" in R:
    181                  R = get_data("NYSE:")
    182         if "Bad Request" in R:
    183             raise RuntimeError
     435       
     436        """
    184437        R = R.splitlines()
    185438        headings = R[0].split(',')
    186         self.__historical = []
    187         try:
    188             for x in reversed(R[1:]):
    189                 date, opn, high, low, close, volume = x.split(',')
    190                 self.__historical.append(Day(date, opn,high,low,close,volume))
    191         except ValueError:
    192              pass
    193         self.__historical = Sequence(self.__historical,cr=True,universe=lambda x:x, immutable=True)
    194         return self.__historical
    195 
    196     def close(self, *args, **kwds):
    197         """
    198         Return the time series of all historical closing prices for this stock.
     439        hist_data = []
     440        for x in reversed(R[1:]):
     441            try:
     442                timestamp, opn, high, low, close, volume = x.split(',')
     443                ohlc = OHLC(timestamp, opn,high,low,close,volume)
     444                hist_data.append(ohlc)
     445            except ValueError:
     446                pass
     447        hist_data = Sequence(hist_data,cr=True,universe=lambda x:x, immutable=True)
     448        return hist_data
     449       
     450    def _get_data(self, exchange='', startdate='Jan+1,+1900', enddate=date.today().strftime("%b+%d,+%Y"), histperiod='daily'):
     451        """       
     452        This function is used internally.
    199453
    200454        EXAMPLES:
    201             sage: finance.Stock('vmw').close()                 # optional -- requires internet
    202             [57.7100, 56.9900, 55.5500, 57.3300, 65.9900 ...
     455        This indirectly tests the use of get_data.
     456            sage: finance.Stock('aapl').google(startdate='Jan+1,+1990')[:2]    # optional -- requires internet
     457            [
     458              2-Jan-90 8.81 9.38 8.75 9.31    6542800,
     459              3-Jan-90 9.50 9.50 9.38 9.38    7428400
     460            ]
    203461        """
    204         from time_series import TimeSeries
    205         return TimeSeries([x.close for x in self.historical(*args, **kwds)])
     462        symbol = self.symbol
     463        cid = self.cid
     464        if cid == '':
     465            url = 'http://finance.google.com/finance/historical?q=%s%s&startdate=%s&enddate=%s&histperiod=%s&output=csv'%(exchange, symbol.upper(), startdate, enddate, histperiod)
     466        else:
     467            url = 'http://finance.google.com/finance/historical?cid=%s&startdate=%s&enddate=%s&histperiod=%s&output=csv'%(cid, startdate, enddate, histperiod)
     468        return urllib.urlopen(url).read()