def start(self) -> None: """ Schedules health checks to run automatically at night. These scheduled runs are in addition to runs initiated by the user manually using the webpanel. """ # Stop the already-running thread, if active self.stop() # Create a time interval representing [6PM, 6AM] update_time_period = TimeInterval(self.logfeed_process, time(hour=18), time(hour=6)) # Run health checks during the time interval, in a separate thread self.health_updates_thread = threading.Thread(target=self._updates_logic, args=[update_time_period]) self.health_updates_thread.start()
def get_ordered_candles(cls, candles: List[Candle], interval: TimeInterval) -> List[Candle]: """ :param candles: a list of candles potentially containing extraneous, unsorted candles :return: a list of candles containing only candles contained in this time interval, sorted ascending by date """ # Filter out extraneous candles contained_candles = [] for candle in candles: if interval.contains_time(candle.moment.time()): contained_candles.append(candle) # Sort by date contained_candles.sort( key=lambda candle_to_sort: candle_to_sort.moment) return contained_candles
def times_active(cls) -> TimeInterval: return TimeInterval(logfeed=None, start1=time(hour=10, minute=30), end1=time(hour=15, minute=0))
def find_mins_maxs(trendline_candles: List[Candle]) -> Tuple[List[Candle], List[Candle]]: """ Returns two lists: the first containing local minima, and the second local maxima. """ # Sanitize input. assert len(trendline_candles) > 9, 'Cannot find mins/maxs without at least 10 candles' # Get sliding window length. trend_length = ContinuousTimeInterval(start_time=trendline_candles[0].moment.time(), end_time=trendline_candles[-1].moment.time()).length() window_length = max(5, int(trend_length * 0.12)) # Ensure sliding window length is an odd number of seconds. window_length = window_length if window_length % 2 == 0 else window_length + 1 # Get slide interval. slide_interval = max(1, window_length * 0.02) # Slide the window along the trendline period. mins, maxs = [], [] window = ContinuousTimeInterval(trendline_candles[0].moment.time(), (trendline_candles[0].moment + timedelta(seconds=window_length)).time()) while datetime.combine(trendline_candles[0].moment.date(), window.end_time) <= trendline_candles[-1].moment: # Get candles in the window. window_candles = SymbolDay.get_ordered_candles(candles=trendline_candles, interval=TimeInterval(None, window.start_time, window.end_time)) # Get midpoint candle. midpoint_candle = midpoint_candle_in_period(period=window, candles=trendline_candles, day_date=trendline_candles[0].moment.date()) # Get candles before and after the midpoint. first_half_candles = [candle for candle in window_candles if candle.moment < midpoint_candle.moment] second_half_candles = [candle for candle in window_candles if candle.moment > midpoint_candle.moment] # Ensure there are candles before/after the midpoint. if midpoint_candle is None or len(window_candles) == 0 or len(first_half_candles) == 0 \ or len(second_half_candles) == 0: # Slide the window forward if not enough candles. window_start = datetime.combine(datetime.today(), window.start_time) + timedelta(seconds=slide_interval) window_end = datetime.combine(datetime.today(), window.end_time) + timedelta(seconds=slide_interval) window = ContinuousTimeInterval(window_start.time(), window_end.time()) continue # Find out what percentage of prices before/after midpoint are less than the midpoint price. pct_prices_below = (len([candle for candle in first_half_candles if candle.low < midpoint_candle.low]) + len([candle for candle in second_half_candles if candle.low < midpoint_candle.low])) \ / len(window_candles) # Find out what percentage of prices before/after midpoint are greater than the midpoint price. pct_prices_above = (len([candle for candle in first_half_candles if candle.high > midpoint_candle.high]) + len([candle for candle in second_half_candles if candle.high > midpoint_candle.high])) \ / len(window_candles) # Record a local minimum if 97% of the window's prices are higher than the midpoint price. if pct_prices_above >= 0.97: mins.append(midpoint_candle) # Record a local maximum if 97% of the window's prices are lower than the midpoint price. if pct_prices_below >= 0.97: maxs.append(midpoint_candle) # Slide the window forward. window_start = datetime.combine(datetime.today(), window.start_time) + timedelta(seconds=slide_interval) window_end = datetime.combine(datetime.today(), window.end_time) + timedelta(seconds=slide_interval) window = ContinuousTimeInterval(window_start.time(), window_end.time()) # Get candles at the beginning and end of the trendline period. start_candles = SymbolDay.get_ordered_candles( candles=trendline_candles, interval=TimeInterval(None, trendline_candles[0].moment.time(), (trendline_candles[0].moment + timedelta(seconds=window_length)).time())) end_candles = SymbolDay.get_ordered_candles( candles=trendline_candles, interval=TimeInterval(None, (trendline_candles[-1].moment - timedelta(seconds=window_length)).time(), trendline_candles[-1].moment.time())) # Check for a global minimum in prices at the start and end of the trendline period. start_min = sorted(start_candles, key=lambda candle: candle.low)[0] end_min = sorted(end_candles, key=lambda candle: candle.low)[0] if len(mins) < 2 or start_min.low < min([local_min_candle.low for local_min_candle in mins]): mins.insert(0, start_min) if len(mins) < 2 or end_min.low < min([local_min_candle.low for local_min_candle in mins]): mins.append(end_min) # Check for a global maximum in prices at the start and end of the trendline period. start_max = sorted(start_candles, key=lambda candle: candle.high)[-1] end_max = sorted(end_candles, key=lambda candle: candle.high)[-1] if len(maxs) < 2 or start_max.high > max([local_max_candle.high for local_max_candle in maxs]): maxs.insert(0, start_max) if len(maxs) < 2 or end_max.high > max([local_max_candle.high for local_max_candle in maxs]): maxs.append(end_max) # Ensure minima are spread apart by at least 3% of the trendline's period. reqd_dist = max(3, trend_length * 0.03) i = 0 while i < len(mins) - 1 and len(mins) >= 3: if (mins[i + 1].moment - mins[i].moment).total_seconds() < reqd_dist: # Remove the higher of the two local minima mins.pop(i if mins[i].low > mins[i + 1].low else i + 1) else: i += 1 # Ensure maxima are spread apart by at least 3% of the trendline's period. i = 0 while i < len(maxs) - 1 and len(maxs) >= 3: if (maxs[i + 1].moment - maxs[i].moment).total_seconds() < reqd_dist: # Remove the lower of the two local maxima. maxs.pop(i if maxs[i].high < maxs[i + 1].high else i + 1) else: i += 1 return mins, maxs
def _updates_logic(self, time_to_run: TimeInterval) -> None: """ Starts an infinite loop that runs health checks during time_to_run. """ self.info_process('Starting health checks thread') checks_completed = [] while getattr(threading.current_thread(), "do_run", True): # Wait for 6PM to run health checks if not time_to_run.contains_time(self.live_time_env.now()): checks_completed = [] pytime.sleep(5) continue # TODO Ensure we have a valid auth token for the API expected_update_time = self.live_time_env.now() # Check MongoDB health if 'mongo' not in checks_completed: self.info_process('Running scheduled mongo model_type') # TODO Call API '/api/health_checks/perform?check_type=MONGO' # TODO Wait 1 second between /get calls, until last_updated > expected_update_time checks_completed.append('mongo') continue # Check output of dip45 analysis model if 'dip45' not in checks_completed: self.info_process('Running scheduled dip45 model_type') # TODO Call API '/api/health_checks/perform?check_type=DIP45' # TODO Wait 1 second between /get calls, until last_updated > expected_update_time checks_completed.append('dip45') continue # Check speed of simulations if 'sim-timings' not in checks_completed: self.info_process('Running scheduled simulations model_type') # TODO Call API '/api/health_checks/perform?check_type=SIMULATION_TIMINGS' # TODO Wait 1 second between /get calls, until last_updated > expected_update_time checks_completed.append('sim-timings') continue # Check health of daily data collection and model feeding if 'model-feeding' not in checks_completed: self.info_process('Running scheduled model feeding check') # TODO Call API '/api/health_checks/perform?check_type=MODEL_FEEDING' # TODO Wait 1 second between /get calls, until last_updated > expected_update_time checks_completed.append('model-feeding') continue # Check depth and accuracy of symbol data for symbol in self.symbols: if f'data-{symbol}' not in checks_completed: self.info_process('Running scheduled symbol ({0}) model_type'.format(symbol)) # TODO Call API '/api/health_checks/perform?check_type=DATA?symbol={}' # TODO Wait 1 second between /get calls, until last_updated > expected_update_time checks_completed.append('data-' + symbol) break # Allow health checks to run again once all have been run checks_completed = []
"""Times""" OPEN_TIME = time(hour=9, minute=30) CLOSE_TIME = time(hour=16, minute=0) """Time durations in seconds""" FOR_1_MIN = 60 FOR_15_MINS = FOR_1_MIN * 15 FOR_1_HR = FOR_1_MIN * 60 FOR_24_HRS = FOR_1_HR * 24 # How long the markets are open for on a regular market day, in seconds OPEN_DURATION = FOR_1_HR * 6.5 # How long the markets are closed for on a regular market day, in seconds CLOSED_DURATION = FOR_24_HRS - OPEN_DURATION # How many minutes to wait after markets close before the program trains its analysis models MODEL_FEED_DELAY = FOR_15_MINS """Time intervals""" ENTIRE_DAY = TimeInterval(None, OPEN_TIME, CLOSE_TIME) FIRST_45_MINS = TimeInterval(None, OPEN_TIME, (datetime.combine(datetime.today(), OPEN_TIME) + timedelta(minutes=45)).time()) def calculate_holidays() -> List[date]: """Calculates market holidays from 2015 - 2030""" holidays = [] for year in range(2015, 2030): holidays.extend(_get_holidays_in_year(year)) return holidays def _get_holidays_in_year(year: int) -> List[date]: """Calculates all the market holidays for the given year."""