def _create_price_watermark( plot: plt.Subplot, name: str, price: int, va_top: bool, pattern: Optional["models.PricePatterns"], ) -> None: y_text_offset = 5 if va_top: vertical_alignment = "top" y_text_offset *= -1 else: vertical_alignment = "bottom" if pattern is None: pattern_color = colors.CURRENT_PRICE_COLOR else: pattern_color = colors.PATTERN_COLORS[pattern] # Create a dotted line line at the break-even point that matches the price # progression price_line = mlines.Line2D( [0, 4], [price, price], color=pattern_color, linewidth=2, linestyle="--", ) plot.add_line(price_line) plot.annotate( f"{name}: {price}", (2, price), textcoords="offset points", xytext=(0, y_text_offset), horizontalalignment="center", verticalalignment=vertical_alignment, color=pattern_color, fontsize=LABEL_SIZE, bbox={ "boxstyle": "Square,pad=0.3", "facecolor": colors.color(0, 0, 0, alpha=0.08), "edgecolor": "none", "linewidth": 0, }, )
def _add_cursor(plot_prices: plt.Subplot, current_period: int) -> None: # Create a shaded region for the current period rect = patches.Rectangle( (current_period - 0.5, 0), 1, 701, linewidth=0, color=colors.CURRENT_PRICE_COLOR, alpha=0.1, ) # Add the patch to the Axes plot_prices.add_patch(rect) # Create a line at the head of the current price period current_period_line = mlines.Line2D( [current_period - 0.5, current_period - 0.5], [0, 700], color=colors.CURRENT_PRICE_COLOR, ) plot_prices.add_line(current_period_line)
def _create_pattern_line( price_plot: plt.Subplot, spike_breakdown: List[float], spike_pattern: models.PotentialPattern, spike_color: List[float], ) -> None: """Create a dashed line on the max price for periods with a potential spike""" # get highest possible price for each period first_period = spike_pattern.spike.start last_period = spike_pattern.spike.end high_price = spike_pattern.prices_summary.max period_highs: List[Optional[int]] = [ high_price for _ in range(first_period, last_period + 1) ] price_periods: List[float] = [ p for p in range(first_period, last_period + 1) ] # Instead of using a plot, we are just going to draw the line directly. # Create a line at the head of the current price period current_period_line = mlines.Line2D( [first_period - 0.5, last_period + 0.5], [high_price, high_price], color=spike_color, linestyle="--", linewidth=4, # We want this line to be always visible so our alpha floor is going to be .1 alpha=max(utils.chance_alpha(spike_pattern.chance), 0.1), ) price_plot.add_line(current_period_line) y_annotation_distance = 10 for period in price_periods: if period == 11.5: continue try: chance = spike_breakdown[int(period)] except TypeError: # If this is period '11.5', then we don't need to annotate it, that value # is just here for line continuation continue # We need to render the numbers ABOVE the spike patter so that they don't # get illegible as the spike placement becomes more certain. price_plot.annotate( utils.format_chance(chance), (period, high_price), textcoords="offset points", xytext=(0, y_annotation_distance), ha="center", va="bottom", fontsize=LABEL_SIZE, color=spike_color, ) # Annotate the total chance of this spike price_plot.annotate( utils.format_chance(spike_pattern.chance), (first_period - 0.5 - 0.1, high_price), ha="right", va="center", fontsize=LABEL_SIZE, color="white", alpha=0.75, bbox={ "boxstyle": "Circle,pad=0.6", "facecolor": colors.color(*spike_color), "edgecolor": spike_color, "linewidth": 0, }, ) # Add a label to the daily chances for clarity middle = first_period + (last_period - first_period) / 2 middle_price = next(p for p in period_highs if p) price_plot.annotate( "daily:", (middle, middle_price + y_annotation_distance * 2.5), textcoords="offset points", xytext=(0, y_annotation_distance), ha="center", va="bottom", fontsize=LABEL_SIZE * 1.1, color=spike_color, )