def normalize(cls, value: Any) -> Any: if isinstance(value, Dict): return { TimePeriod.normalize(key): price for key, price in value.items() } return value
def __init__(self, initial: Price): super().__init__(initial) mod: Modifier parent: Modifier # Slice 2: InitialDecay mod = InitialDecay(self.initial, None) self.timeline[TimePeriod(2)] = mod parent = mod # Slices 3-13: Decay for i in range(3, 14): time = TimePeriod(i) mod = SlowDecay(self.initial, parent) self.timeline[time] = mod parent = mod
def random_price(self, time: AnyTime): '''Provide a random price for the given time, unless that time has data.''' timeslice = TimePeriod.normalize(time) if self.timeline[timeslice].price.is_atomic: return self.timeline[timeslice].price.value return random.randint(self.timeline[timeslice].price.lower, self.timeline[timeslice].price.upper)
def __init__(self, initial: 'Price', pattern_start: int): low = self._pattern_earliest high = self._pattern_latest if not low <= pattern_start <= high: raise ValueError(f"pattern_start must be between {low} and {high}, inclusive") super().__init__(initial) self._pattern_start = pattern_start self._pattern_peak = TimePeriod(pattern_start + self._peak_time) self._tail = 9 - pattern_start
def __init__(self, initial: 'Price', pattern_start: int): super().__init__(initial, pattern_start) chain: List[Modifier] = [] decay_class: Type[Modifier] def _push_node(mod_cls: Type[Modifier], parent_override: Optional[Modifier] = None) -> Modifier: if parent_override is None: my_parent = chain[-1] if chain else None else: my_parent = parent_override mod = mod_cls(self.initial, my_parent) chain.append(mod) return mod decay_class = WideLoss for _ in range(2, pattern_start): _push_node(decay_class) decay_class = SlowDecay # Pattern starts: _push_node(SmallProfit) _push_node(SmallProfit) # And then gets weird! # Create an unlisted parent that represents the peak shared across the next three prices. cap = MediumProfit(self.initial, chain[-1]) _push_node(CappedPassthrough, cap).sub1 = True _push_node(Passthrough, cap) _push_node(CappedPassthrough, cap).sub1 = True # Alright, phew. decay_class = WideLoss for _ in range(0, self._tail): _push_node(decay_class) decay_class = SlowDecay # Build timeline assert len(chain) == 12 for i, mod in enumerate(chain): time = TimePeriod(i + 2) self.timeline[time] = mod
def fix_price(self, time: AnyTime, price: int) -> None: if not self: assert RuntimeError("No viable models to fix prices on!") timeslice = TimePeriod.normalize(time) remove_queue = [] for index, model in self._models.items(): try: model.fix_price(timeslice, price) except ArithmeticError as exc: logging.info(f"Ruled out model: {model.name}") logging.debug( f" Reason: {timeslice.name} price={price} not possible:") logging.debug(f" {str(exc)}") remove_queue.append(index) for i in remove_queue: del self._models[i]
def __init__(self, initial: 'Price', pattern_start: int): super().__init__(initial, pattern_start) def _get_pattern(value: int) -> Type[Modifier]: # The default: cls: Type[Modifier] = SlowDecay # Pattern takes priority: if value == pattern_start: cls = SmallProfit elif value == pattern_start + 1: cls = MediumProfit elif value == pattern_start + 2: cls = LargeProfit elif value == pattern_start + 3: cls = MediumProfit elif value == pattern_start + 4: cls = SmallProfit elif value >= pattern_start + 5: # Week finishes out with independent low prices cls = WideLoss elif value == 2: # Normal start-of-week pattern cls = InitialDecay return cls parent = None # Slices 2-13: Magic! for i in range(2, 14): time = TimePeriod(i) mod = _get_pattern(i)(self.initial, parent) self.timeline[time] = mod parent = mod
def plot_models_range(name: str, models: Sequence[Model], previous: ModelEnum, add_points: bool = False) -> None: ''' Plot a fill_between for all models' low and high values using an alpha (transparency) equal to 1/num_models. Plot a regular line for all fixed prices. Shows ~probability of various prices based on your possible models. ''' colors = { TRIPLE: 'orange', SPIKE: 'green', DECAY: 'red', BUMP: 'blue', } _fig, ax = plt.subplots() # cosmetics ax.set_title(f'Island {name}: current: !!ERROR!!; Last: {previous.name}') ax.set_ylabel('Turnip Price') ax.set_xticklabels(['Mon AM', 'Mon PM', 'Tue AM', 'Tue PM', 'Wed AM', 'Wed PM', 'Thu AM', 'Thu PM', 'Fri AM', 'Fri PM', 'Sat AM', 'Sat PM']) ax.xaxis.set_ticks(range(2, 14)) plt.xticks(rotation=45) plt.grid(axis='both', which='both', ls='--') ax.set_ylim(0, 660) plt.tight_layout() if len(models) == 0: return a_model = models[0] continuous_priced_days = [] continuous_priced_chunk = set() continuous_unpriced_days = [] continuous_unpriced_chunk = set() for day in range(2, 14): # does this day have data? if a_model.timeline[TimePeriod(day)].price.is_atomic: # is tomorrow a valid day to have data? if day < 13: # does tomorrow have data? if a_model.timeline[TimePeriod(day + 1)].price.is_atomic: # build onto the chunk continuous_priced_chunk.update([day, day + 1]) # chunk broken. else: continuous_priced_chunk.update([day]) continuous_priced_days.append(list(continuous_priced_chunk)) continuous_priced_chunk = set() else: # end of the week, finish the priced_days continuous_priced_days.append(list(continuous_priced_chunk)) # today does not have data else: # is tomorrow a valid day to have data? if day < 13: # does it? if not a_model.timeline[TimePeriod(day + 1)].price.is_atomic: # build the chunk if day != 2: # add yesterday unless today is monday_am continuous_unpriced_chunk.update([day - 1, day, day + 1]) else: continuous_unpriced_chunk.update([day, day + 1]) # chunk broken else: if day != 2: continuous_unpriced_chunk.update([day - 1, day, day + 1]) else: continuous_unpriced_chunk.update([day, day + 1]) continuous_unpriced_days.append(list(sorted(continuous_unpriced_chunk, key=lambda x: x))) continuous_unpriced_chunk = set() else: # end of the week, finish the unpriced_days continuous_unpriced_days.append(list(continuous_unpriced_chunk)) for chunk in continuous_priced_days: vals = [a_model.timeline[TimePeriod(day)].price.value for day in chunk] plt.plot(chunk, vals, c='black') model_counts = Counter(x.model_type for x in models) remaining_model_types = model_counts.keys() remaining_probability = sum(MARKOV[previous][rem_mod] for rem_mod in remaining_model_types) adjusted_priors = {model: MARKOV[previous][model] / remaining_probability for model in model_counts.keys()} for chunk in continuous_unpriced_days: if len(chunk) == 1: # if this is one day of unpriced data, connect it to the neighbors. value = chunk[0] chunk = [value - 1, value, value + 1] for model in models: low_vals = [model.timeline[TimePeriod(day)].price.lower for day in chunk] high_vals = [model.timeline[TimePeriod(day)].price.upper for day in chunk] if previous != ModelEnum.unknown: alpha = adjusted_priors[model.model_type] / model_counts[model.model_type] else: alpha = 1 / len(models) plt.fill_between(chunk, low_vals, high_vals, alpha=alpha, color=colors[model.model_type]) if add_points: plt.scatter(chunk, low_vals, c='black', s=2) plt.scatter(chunk, high_vals, c='black', s=2) # cosmetics msummary = '+'.join(['{}_{{{}}}^{{{:.2f}}}'.format(t, l.name, adjusted_priors[l]) for l, t in model_counts.items()]) ax.set_title(f'Island {name}: ${len(models)}_{{total}}={msummary}$, Last: {previous.name}')
def chatty_fix_price(self, time: AnyTime, price: int) -> None: timeslice = TimePeriod.normalize(time) self.fix_price(time, price) print(f"Added {timeslice.name} @ {price};") self.report()
def __init__(self, initial: Price, length_phase1: int, length_decay1: int, length_phase2: int): super().__init__(initial) if not 0 <= length_phase1 <= 6: raise ValueError("Phase1 length must be between [0, 6]") self._length_phase1 = length_phase1 if not 2 <= length_decay1 <= 3: raise ValueError("Decay1 length must be 2 or 3") self._length_decay1 = length_decay1 self._length_decay2 = 5 - length_decay1 remainder = 7 - length_phase1 if not 1 <= length_phase2 <= remainder: raise ValueError(f"Phase2 must be between [1, {remainder}]") self._length_phase2 = length_phase2 self._length_phase3 = remainder - length_phase2 assert (self._length_phase1 + self._length_phase2 + self._length_phase3 + self._length_decay1 + self._length_decay2) == 12 chain: List[Modifier] = [] decay_class: Type[Modifier] def _push_node(mod_cls: Type[Modifier]) -> None: mod = mod_cls(self.initial, chain[-1] if chain else None) chain.append(mod) # Phase 1 [0, 6] for _ in range(0, self._length_phase1): _push_node(SmallProfit) # Decay 1 [2, 3] decay_class = MediumLoss for _ in range(0, self._length_decay1): _push_node(decay_class) decay_class = RapidDecay # Phase 2 [1, 6] for _ in range(0, self._length_phase2): _push_node(SmallProfit) # Decay 2 [2, 3] decay_class = MediumLoss for _ in range(0, self._length_decay2): _push_node(decay_class) decay_class = RapidDecay # Phase 3 [0, 6] for _ in range(0, self._length_phase3): _push_node(SmallProfit) # Build timeline assert len(chain) == 12 for i, mod in enumerate(chain): time = TimePeriod(i + 2) self.timeline[time] = mod
def fix_price(self, time: AnyTime, price: int) -> None: timeslice = TimePeriod.normalize(time) self.timeline[timeslice].fix_price(price)