1- from datetime import timedelta
21from enum import Enum
32
3+ from .timeseries import TimeSeries
44from .event import Event
5- from .timeseries import TimeSeries , MovingAverage , NotEnoughDataException
65
76
87class StockSignal (Enum ):
@@ -20,10 +19,6 @@ def __init__(self, symbol):
2019 self .history = TimeSeries ()
2120 self .updated = Event ()
2221
23- def __str__ (self ):
24- class_name = type (self ).__name__
25- return '{}("{}")' .format (class_name , self .symbol )
26-
2722 @property
2823 def price (self ):
2924 """Returns the current price of the Stock
@@ -33,18 +28,9 @@ def price(self):
3328 10
3429
3530 The method will return the latest price by timestamp, so even if
36- updates are out of order, it will return the latest one
37-
38- >>> stock = Stock("GOOG")
39- >>> stock.update(datetime(2011, 10, 3), 10)
40-
41- Now, let us do an update with a date that is earlier than the previous
42- one
31+ updates are out of order, it will return the latest one.
4332
4433 >>> stock.update(datetime(2011, 10, 2), 5)
45-
46- And the method still returns the latest price
47-
4834 >>> stock.price
4935 10
5036
@@ -73,6 +59,7 @@ def update(self, timestamp, price):
7359 ...
7460 ValueError: price should not be negative
7561 """
62+
7663 if price < 0 :
7764 raise ValueError ("price should not be negative" )
7865 self .history .update (timestamp , price )
@@ -86,26 +73,59 @@ def is_increasing_trend(self):
8673 >>> stock.is_increasing_trend()
8774 False
8875 """
76+
8977 try :
90- return self .history [- 3 ].value < self .history [- 2 ].value < self .history [- 1 ].value
78+ return self .history [- 3 ].value < \
79+ self .history [- 2 ].value < self .history [- 1 ].value
9180 except IndexError :
9281 return False
9382
94- def _is_crossover_below_to_above (self , on_date , ma , reference_ma ):
95- prev_date = on_date - timedelta ( 1 )
96- return ( ma . value_on ( prev_date ) < reference_ma . value_on ( prev_date )
97- and ma . value_on ( on_date ) > reference_ma . value_on ( on_date ))
83+ def _is_crossover_below_to_above (self , prev_ma , prev_reference_ma ,
84+ current_ma , current_reference_ma ):
85+ return prev_ma < prev_reference_ma \
86+ and current_ma > current_reference_ma
9887
9988 def get_crossover_signal (self , on_date ):
100- long_term_ma = MovingAverage (self .history , self .LONG_TERM_TIMESPAN )
101- short_term_ma = MovingAverage (self .history , self .SHORT_TERM_TIMESPAN )
102- try :
103- if self ._is_crossover_below_to_above (on_date , short_term_ma , long_term_ma ):
89+ NUM_DAYS = self .LONG_TERM_TIMESPAN + 1
90+ closing_price_list = self .history .get_closing_price_list (on_date ,
91+ NUM_DAYS )
92+
93+ if len (closing_price_list ) < NUM_DAYS :
94+ return StockSignal .neutral
95+
96+ long_term_series = closing_price_list [- self .LONG_TERM_TIMESPAN :]
97+ prev_long_term_series = closing_price_list [- self .LONG_TERM_TIMESPAN - 1 :- 1 ]
98+ short_term_series = closing_price_list [- self .SHORT_TERM_TIMESPAN :]
99+ prev_short_term_series = closing_price_list [- self .SHORT_TERM_TIMESPAN - 1 :- 1 ]
100+
101+ long_term_ma = 1.0 * sum ([update .value
102+ for update in long_term_series ])\
103+ / self .LONG_TERM_TIMESPAN
104+ prev_long_term_ma = 1.0 * sum ([update .value
105+ for update in prev_long_term_series ])\
106+ / self .LONG_TERM_TIMESPAN
107+ short_term_ma = 1.0 * sum ([update .value
108+ for update in short_term_series ])\
109+ / self .SHORT_TERM_TIMESPAN
110+ prev_short_term_ma = 1.0 * sum ([update .value
111+ for update in prev_short_term_series ])\
112+ / self .SHORT_TERM_TIMESPAN
113+
114+ if self ._is_crossover_below_to_above (prev_short_term_ma ,
115+ prev_long_term_ma ,
116+ short_term_ma ,
117+ long_term_ma ):
104118 return StockSignal .buy
105119
106- if self ._is_crossover_below_to_above (on_date , long_term_ma , short_term_ma ):
120+ if self ._is_crossover_below_to_above (prev_long_term_ma ,
121+ prev_short_term_ma ,
122+ long_term_ma ,
123+ short_term_ma ):
107124 return StockSignal .sell
108- except NotEnoughDataException :
109- return StockSignal .neutral
110125
111126 return StockSignal .neutral
127+
128+
129+ if __name__ == "__main__" :
130+ import doctest
131+ doctest .testmod ()
0 commit comments