def check_max_reward(self, max_reward: float) -> ModelStep: """ Returns whether the profit upper bound is acceptably high. :param max_reward: highest profit to sell for: res_line - sup_line at 0.5t """ return ModelStep(passed=max_reward / self.period_low > 0.15 / 100, value=f'{100 * max_reward / self.period_low:.2f}%', step_id=Breakout1ModelSteps.MAX_REWARD)
def check_range_change_2(self, range_change_2: float) -> ModelStep: """ Returns whether the range decreased from [0.31t, 0.65t] to [0.66t, 0.85t]. :param range_change_2: change in price range between [0.66, 0.85t] and [0.66t, 0.85t] """ return ModelStep( passed=range_change_2 / self.period_low < 0.1, value=f'{100 * range_change_2 / self.period_low:0.2f}%', step_id=Breakout1ModelSteps.RANGE_CHANGE_2)
def check_dist_from_res(self, dist_from_res: float) -> ModelStep: """ Returns whether the price exceeded the resistance line by at least 10% of the period's range. :param dist_from_res: difference between the resistance line and the symbol's recent high """ return ModelStep( passed=dist_from_res >= 0.1 * self.period_range, value=f'{100 * dist_from_res / self.period_range:.2f}%', step_id=Breakout1ModelSteps.BREAKS_RESISTANCE)
def check_dist_to_sup(self, dist_to_sup: float) -> ModelStep: """ Returns whether the price dipped to slightly above or just barely below the support. :param dist_to_sup: difference between the symbol's recent low and the support line """ return ModelStep(passed=self.period_range * -15 / 100 <= dist_to_sup <= self.period_range * 20 / 100, value=f'{100 * dist_to_sup / self.period_range:.2f}%', step_id=Breakout1ModelSteps.DIPS_TO_SUPPORT)
def check_high_volume_drop_ratio( self, high_volume_drop_ratio: float) -> ModelStep: """ Returns whether the price drops too often when volume increases. :param high_volume_drop_ratio: when a minute's volume is higher than usual, the pct of occurrences that see price drop """ self.set_val('high_volume_drop_ratio', high_volume_drop_ratio) return ModelStep(passed=high_volume_drop_ratio > 1.5, value=f'{high_volume_drop_ratio}:1', step_id=Breakout1ModelSteps.HIGH_VOL_DROP_RATIO)
def check_strongest_high_volume_dip( self, strongest_high_volume_dip: float) -> ModelStep: """ Returns whether any single minute with atypically high volume drops in price too steeply. :param strongest_high_volume_dip: strongest minute-resolution price drop that is accompanied by higher-than-usual volume """ return ModelStep( passed=strongest_high_volume_dip < self.avg_minute_range + (1.7 * self.stdev_minute_range), value= f'{(strongest_high_volume_dip - self.avg_minute_range) / self.stdev_minute_range:.0f} ' f'stdevs', step_id=Breakout1ModelSteps.STRONGEST_HIGH_VOL_DIP)
def check_ema_minute_volume( self, ema_minute_volume: float, med_ema_volume_prev_7_periods: float) -> ModelStep: """ Returns whether this period's moving-average volume is higher than the median of the moving-average volume during the same period on the previous 7 days. :param ema_minute_volume: average minute's volume exponentially weighted by time :param med_ema_volume_prev_7_periods: median of: moving-average volume during the same period on each of the last 7 days """ return ModelStep( passed=ema_minute_volume > 1.2 * med_ema_volume_prev_7_periods, value= f'{100 * ema_minute_volume / med_ema_volume_prev_7_periods:.1f}%', step_id=Breakout1ModelSteps.EMA_MINUTE_VOLUME)
def add_step(self, step: ModelStep = None, passed: bool = None, value: str = None, step_id: ModelSteps = None) -> None: """ Adds a step in the calculation of model output. Parameters: either a single ModelStep, or: passed: bool, value: str, step_id: ModelSteps """ if step is not None: self.steps.append(step) elif passed is not None and value is not None and step_id is not None: self.steps.append( ModelStep(passed=passed, value=value, step_id=step_id)) else: raise ValueError( 'Invalid arguments passed to model\'s output.add_step()')
def _check_ema_volume_excitement(self, output: Breakout1ModelOutput) -> bool: ema_volumes = [ema(output.minute_volumes)] prev_date = self.time().now().date() for i in range(7): # Load the previous day's data prev_date = self.time().get_prev_mkt_day(prev_date) day_data = self.mongo().load_symbol_day(output.symbol, prev_date) if not SymbolDay.validate_candles(day_data.candles): output.steps.append( ModelStep( passed=False, value= f'missing needed data on {prev_date.strftime(DATE_FORMAT)}', step_id=Breakout1ModelSteps.EMA_MINUTE_VOLUME)) return False # Aggregate the day's second-resolution candles into minute resolution prev_period_start = datetime.combine(prev_date, output.period.start_time) prev_period_end = datetime.combine(prev_date, output.period.end_time) prev_second_candles = [ candle for candle in day_data.candles if prev_period_start <= candle.moment <= prev_period_end ] prev_minute_candles = aggregate_minute_candles(prev_second_candles) # Calculate the previous day's moving-average volume ema_volumes.append( ema([candle.volume for candle in prev_minute_candles])) # Calculate the median ema volume of the same period on each of the past 7 days med_ema_volume_prev_7_periods = median(ema_volumes) # Calculate the ema volume of this period today ema_minute_volume = ema(output.minute_volumes) # Perform next step: ema minute volume check output.steps.append( output.check_ema_minute_volume(ema_minute_volume, med_ema_volume_prev_7_periods)) return output.steps[-1].passed