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 ):
@@ -29,18 +28,9 @@ def price(self):
2928 10
3029
3130 The method will return the latest price by timestamp, so even if
32- updates are out of order, it will return the latest one
33-
34- >>> stock = Stock("GOOG")
35- >>> stock.update(datetime(2011, 10, 3), 10)
36-
37- Now, let us do an update with a date that is earlier than the previous
38- one
31+ updates are out of order, it will return the latest one.
3932
4033 >>> stock.update(datetime(2011, 10, 2), 5)
41-
42- And the method still returns the latest price
43-
4434 >>> stock.price
4535 10
4636
@@ -69,6 +59,7 @@ def update(self, timestamp, price):
6959 ...
7060 ValueError: price should not be negative
7161 """
62+
7263 if price < 0 :
7364 raise ValueError ("price should not be negative" )
7465 self .history .update (timestamp , price )
@@ -82,26 +73,59 @@ def is_increasing_trend(self):
8273 >>> stock.is_increasing_trend()
8374 False
8475 """
76+
8577 try :
86- 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
8780 except IndexError :
8881 return False
8982
90- def _is_crossover_below_to_above (self , on_date , ma , reference_ma ):
91- prev_date = on_date - timedelta ( 1 )
92- return ( ma . value_on ( prev_date ) < reference_ma . value_on ( prev_date )
93- 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
9487
9588 def get_crossover_signal (self , on_date ):
96- long_term_ma = MovingAverage (self .history , self .LONG_TERM_TIMESPAN )
97- short_term_ma = MovingAverage (self .history , self .SHORT_TERM_TIMESPAN )
98- try :
99- 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 ):
100118 return StockSignal .buy
101119
102- 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 ):
103124 return StockSignal .sell
104- except NotEnoughDataException :
105- return StockSignal .neutral
106125
107126 return StockSignal .neutral
127+
128+
129+ if __name__ == "__main__" :
130+ import doctest
131+ doctest .testmod ()
0 commit comments