ManifoldMarketManager/ManifoldMarketManager/rule/manifold/this.py

123 lines
4.8 KiB
Python

"""Contain rules that reference the market that is calling them."""
from __future__ import annotations
from typing import TYPE_CHECKING, Mapping, Union, cast
from attrs import Factory, define
from ... import Rule
from ...consts import (BinaryResolution, FreeResponseResolution, MultipleChoiceResolution, Outcome,
PseudoNumericResolution, T)
from ...util import fibonacci, market_to_answer_map, normalize_mapping
from . import ManifoldMarketMixin
from .other import OtherMarketClosed, OtherMarketUniqueTraders, OtherMarketValue
if TYPE_CHECKING: # pragma: no cover
from typing import Any, ClassVar, Optional
from pymanifold.lib import ManifoldClient
from pymanifold.types import Market as APIMarket
from ...account import Account
from ...market import Market
@define(slots=False)
class ThisToOtherConverter(ManifoldMarketMixin):
"""A mixin class that converts market accesses to reuse `other` code."""
id_: str = "N/A"
def api_market(self, client: Optional[ManifoldClient] = None, market: Optional[Market] = None) -> APIMarket:
"""Return an APIMarket object associated with this rule's market."""
assert market is not None
market.refresh()
return market.market
@define(slots=False)
class ThisMarketClosed(OtherMarketClosed, ThisToOtherConverter):
"""A rule that checks whether its associated market is closed."""
def _explain_abstract(self, indent: int = 0, **kwargs: Any) -> str:
return "If this market reaches its close date\n"
@define(slots=False)
class CurrentValueRule(OtherMarketValue[T], ThisToOtherConverter):
"""Resolve to the current market-consensus value."""
def _explain_abstract(self, indent: int = 0, **kwargs: Any) -> str:
return "Resolves to the current market value\n"
@define(slots=False)
class UniqueTradersRule(ThisToOtherConverter, OtherMarketUniqueTraders):
"""Resolve to the current number of unique traders."""
def _explain_abstract(self, indent: int = 0, **kwargs: Any) -> str:
return "Resolves to the current number of unique traders\n"
@define(slots=False)
class FibonacciValueRule(Rule[Union[float, Mapping[int, float]]]):
"""Resolve each value with a fibonacci weight, ranked by probability."""
exclude: set[int] = Factory(set)
min_rewarded: float = 0.0001
def _value(self, market: Market, account: Account) -> float | dict[int, float]:
market.refresh()
items = market_to_answer_map(market, self.exclude, (lambda id_, probability: probability < self.min_rewarded))
rank = sorted(items, key=items.__getitem__)
ret = {item: fib for item, fib in zip(rank, fibonacci())}
return normalize_mapping(ret)
def _explain_abstract(self, indent: int = 0, **kwargs: Any) -> str:
ret = f"{' ' * indent}- Weight each* answer to the fibonacci rank of their probability\n"
block = ' ' * (indent + 1)
ret += f"{block}- Filter out IDs in {self.exclude}, probabilities below {self.min_rewarded * 100}%\n"
ret += f"{block}- Sort by probability\n"
ret += f"{block}- Iterate over this and the fibonacci numbers in lockstep. Those are the weights\n"
return ret
class RoundValueRule(CurrentValueRule[Union[BinaryResolution, PseudoNumericResolution]]):
"""Resolve to the current market-consensus value, but rounded."""
_explainer_stub: ClassVar[str] = "Resolves to round(MKT)"
def _value(self, market: Market, account: Account) -> float:
if market.market.outcomeType in Outcome.MC_LIKE():
raise RuntimeError()
elif market.market.outcomeType == Outcome.BINARY:
assert market.market.probability
return bool(round(market.market.probability))
return round(cast(float, super()._value(market, account)))
@define(slots=False)
class PopularValueRule(Rule[Union[MultipleChoiceResolution, FreeResponseResolution]]):
"""Resolve to the n most likely market-consensus values, weighted by their probability."""
size: int = 1
def _value(self, market: Market, account: Account) -> FreeResponseResolution | MultipleChoiceResolution:
market.refresh()
answers = market_to_answer_map(market)
final_answers: dict[int, float] = {}
try:
for _ in range(self.size):
next_answer = max(answers, key=answers.__getitem__)
final_answers[next_answer] = answers[next_answer]
del answers[next_answer]
except ValueError as e:
if "arg is an empty sequence" not in e.args[0]:
raise
return normalize_mapping(final_answers)
def _explain_abstract(self, indent: int = 0, **kwargs: Any) -> str:
return f"{' ' * indent}- Resolves to the {self.size} most probable answers, weighted by their probability.\n"