def _plot_current_prices(plot_prices: plt.Subplot, ticker: models.Ticker) -> int: current_period = -1 prices: List[Optional[int]] = [ None for _ in range(0, PRICE_PERIOD_COUNT + 2) ] if ticker.purchase_price != 0: prices[0] = ticker.purchase_price for i in range(0, PRICE_PERIOD_COUNT): try: this_price = ticker.prices[i] except IndexError: continue if this_price != 0: prices[i + 1] = this_price current_period = i if ticker.current_period > current_period: current_period = ticker.current_period plot_prices.step( [i for i in range(-1, PRICE_PERIOD_COUNT + 1)], prices, where="mid", linestyle="--", linewidth=4, dash_capstyle="projecting", color=colors.CURRENT_PRICE_COLOR, ) # Annotate the prices for i, this_price in enumerate(ticker.prices): # Zero means unknown, skip it. if this_price == 0: continue plot_prices.annotate( str(this_price), (i, this_price), textcoords="offset points", xytext=(0, 10), ha="center", va="bottom", fontsize=LABEL_SIZE * 1.2, color=colors.CURRENT_PRICE_COLOR, ) return current_period
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 _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, )
def plot_pattern_chances(plot: plt.Subplot, forecast: models.Forecast) -> None: total = 0.0 valid_patterns = sorted( (x for x in forecast.patterns if x.chance != 0), key=lambda x: x.chance, reverse=True, ) for i, potential_pattern in enumerate(valid_patterns): if i > 0: # Add a little bit of space between sections total += 0.01 chance = potential_pattern.chance # If the chance gets below 2.5%, the bar disappears, so we need to have a # minimum visual value for it. adjusted_chance = max(potential_pattern.chance, 0.015) pattern_color = colors.PATTERN_COLORS[potential_pattern.pattern] plot.barh( 0.5, height=0.8, width=adjusted_chance, left=total, color=colors.color(*pattern_color, alpha=chance), linewidth=0, ) patten_name = PATTERN_NAMES[potential_pattern.pattern] plot.annotate( f"{patten_name}: {utils.format_chance(chance)}", (total + (adjusted_chance / 2), 0.5), color="white", alpha=0.75, fontsize=LABEL_SIZE * chance**0.9 + LABEL_SIZE / 2, fontweight="bold", ha="center", va="center", ) total += adjusted_chance plot.set_xlim((0, total)) plot.set_ylim((0, 1)) # remove axis labels plot.tick_params( axis="both", labeltop=False, labelbottom=False, labelright=False, labelleft=False, top=False, bottom=False, left=False, right=False, ) plot.patch.set_visible(False) for spine in plot.spines.values(): spine.set_visible(False)