Exemplo n.º 1
0
 def _ideal_fiat_value_per_coin(self, market_state: MarketState) -> float:
     """We define "ideal" value as total value (in fiat) / # of available
     coins (including fiat).
     """
     total_value = market_state.estimate_total_value(
         market_state.balances, self.fiat)
     num_coins = len(market_state.available_coins())
     return total_value / num_coins
Exemplo n.º 2
0
    def reify_trade(
        cls,
        trade: AbstractTrade,
        market_state: MarketState,
    ) -> List[Order]:
        """Given an abstract trade, return a list of concrete orders that will
        accomplish the higher-level transaction described.
        """
        markets = market_state.available_markets()

        market = format_currency_pair(trade.sell_coin, trade.buy_coin)
        if market not in markets:
            market = format_currency_pair(trade.buy_coin, trade.sell_coin)
        if market not in markets:
            raise NoMarketAvailableError(
                f'No market available between {trade.sell_coin} and '
                f'{trade.buy_coin} and indirect trades are not yet supported'
            )
        base, quote = split_currency_pair(market)

        # Price is given as base currency / quote currency
        price = market_state.price(market)

        # Order amount is given with respect to the quote currency
        quote_amount = market_state.estimate_value(
            trade.reference_coin,
            trade.reference_value,
            quote,
        )

        # Order direction is given with respect to the quote currency
        if trade.sell_coin == base:
            # Buy quote currency; sell base currency
            direction = Order.Direction.BUY
        elif trade.sell_coin == quote:
            # Sell quote currency; buy quote currency
            direction = Order.Direction.SELL
        else:
            raise

        return [
            Order(
                market,
                price,
                quote_amount,
                direction,
                OrderType.fill_or_kill,
            )
        ]
Exemplo n.º 3
0
 def sell_to_achieve_value_of(
     self,
     desired_value: float,
     market_state: MarketState,
 ) -> None:
     '''
     Sets `self.sell_amount`, `self.buy_amount`, `self.price`
     such that the proposed trade would leave us with a
     holding of `desired_value`.
     '''
     self.estimate_price(market_state)
     if not self.price:
         logger.error(
             'Must set a price for ProposedTrade, or pass a chart object '
             'into estimate_price_with')
         raise
     # After rebalance, we want the value of the coin we're trading to
     # to be equal to the ideal value (in fiat).
     # First we'll find the value of the coin we currently hold.
     current_value = market_state.balance(self.sell_coin) * self.price
     # To find how much coin we want to sell,
     # we'll subtract our holding's value from the ideal value
     # to produce the value of coin we must sell
     value_to_sell = current_value - desired_value
     # Now we find the amount of coin equal to this value
     amount_to_sell = value_to_sell / self.price
     self.set_sell_amount(amount_to_sell, market_state)
Exemplo n.º 4
0
def test_simulate_trades():
    trades = [
        # Sell 0.5 BTC worth of BTC to buy ETH
        # -0.5 BTC, +6.737858883631113 ETH
        AbstractTrade('BTC', 'ETH', 'BTC', 0.5),
        # Sell 5 BCH worth of BTC to buy ETH
        # -0.60083005 BTC, +8.096616179890052 ETH
        AbstractTrade('BTC', 'ETH', 'BCH', 5),
        # Sell 1 ETH worth of ETH to buy BCH
        # -1 ETH, +0.612798695395699 BCH
        AbstractTrade('ETH', 'BCH', 'ETH', 1),
    ]

    chart_data = {
        'BTC_ETH': {
            'weighted_average': 0.07420755
        },  # BTC/ETH
        'BTC_BCH': {
            'weighted_average': 0.12016601
        },  # BTC/BCH
        'ETH_BCH': {
            'weighted_average': 1.63185726
        },  # ETH/BCH
    }
    balances = {'BTC': 8}
    state = MarketState(chart_data, balances, datetime.now(), 'BTC')

    result = simulate_trades(trades, state)
    assert result == {
        'BCH': 0.612798695395699,
        'BTC': 6.89916995,
        'ETH': 13.834475063521165,
    }
Exemplo n.º 5
0
    def propose_trades_for_total_rebalancing(
        self,
        market_state: MarketState,
    ) -> List[AbstractTrade]:
        """A total rebalancing should get us as close as possible to an equal
        distribution of value (w/r/t `self.fiat`) across all "reachable"
        markets (those in which the base currency is `self.fiat`).
        """
        ideal_fiat_value_per_coin = self._ideal_fiat_value_per_coin(
            market_state)

        est_values = market_state.estimate_values(market_state.balances,
                                                  self.fiat)

        coins_to_sell = {}
        coins_to_buy = {}
        for coin in sorted(self._possible_investments(market_state)):
            value = est_values.get(coin, 0)
            delta = value - ideal_fiat_value_per_coin
            if delta > 0:
                coins_to_sell[coin] = delta
            elif delta < 0:
                coins_to_buy[coin] = abs(delta)

        trades_to_fiat = [
            AbstractTrade(sell_coin, self.fiat, self.fiat, fiat_value)
            for sell_coin, fiat_value in coins_to_sell.items()
        ]

        trades_from_fiat = [
            AbstractTrade(self.fiat, buy_coin, self.fiat, fiat_value)
            for buy_coin, fiat_value in coins_to_buy.items()
        ]

        return trades_to_fiat + trades_from_fiat
Exemplo n.º 6
0
 def _possible_investments(self,
                           market_state: MarketState) -> FrozenSet[str]:
     '''
     Returns a set of all coins that the strategy might invest in, excluding
     `self.fiat`.
     '''
     return market_state.available_coins() - {self.fiat}
Exemplo n.º 7
0
 def __init__(
     self,
     history: MarketHistory,
     initial_balances: Dict[str, float],
     fiat: str,
 ) -> None:
     self.market_history = history
     self.fiat = fiat
     self.market_state = MarketState(None, initial_balances, None,
                                     self.fiat)
Exemplo n.º 8
0
def state():
    chart_data = {
        'BTC_ETH': {
            'weighted_average': 0.07096974
        },
        'ETH_BCH': {
            'weighted_average': 1.84201100
        },
    }
    balances = {}
    return MarketState(chart_data, balances, datetime.now(), 'BTC')
Exemplo n.º 9
0
def market_state():
    chart_data = {
        'BTC_ETH': {
            'weighted_average': 0.07420755
        },  # BTC/ETH
        'BTC_BCH': {
            'weighted_average': 0.12016601
        },  # BTC/BCH
        'ETH_BCH': {
            'weighted_average': 1.63185726
        },  # ETH/BCH
    }
    return MarketState(chart_data, {}, datetime.now(), 'BTC')
Exemplo n.º 10
0
    def rebalancing_proposed_trades(
        self,
        coins_to_rebalance: List[str],
        market_state: MarketState,
    ) -> List[ProposedTrade]:
        possible_investments = self._possible_investments(market_state)
        total_value = market_state.estimate_total_value()
        ideal_fiat_value_per_coin = total_value / len(possible_investments)

        proposed_trades_to_fiat = list(
            self._propose_trades_to_fiat(coins_to_rebalance,
                                         ideal_fiat_value_per_coin,
                                         market_state))

        # Next, we will simulate actually executing all of these trades
        # Afterward, we'll get some simulated balances
        est_bals_after_fiat_trades = market_state.simulate_trades(
            proposed_trades_to_fiat)

        if self.fiat in coins_to_rebalance and len(
                proposed_trades_to_fiat) > 0:
            fiat_after_trades = est_bals_after_fiat_trades[self.fiat]
            to_redistribute = fiat_after_trades - ideal_fiat_value_per_coin
            coins_divested_from = [
                proposed.sell_coin for proposed in proposed_trades_to_fiat
            ]
            coins_to_buy = possible_investments - set(coins_divested_from) - {
                self.fiat
            }
            to_redistribute_per_coin = to_redistribute / len(coins_to_buy)
            proposed_trades_from_fiat = self._propose_trades_from_fiat(
                coins_to_buy, to_redistribute_per_coin, market_state)
            trades = proposed_trades_to_fiat + list(proposed_trades_from_fiat)

            return trades

        return proposed_trades_to_fiat
Exemplo n.º 11
0
def simulate_trades(
    trades: List[AbstractTrade],
    market_state: MarketState,
) -> Dict[str, float]:
    """
    """
    new = market_state.balances.copy()

    for trade in trades:
        sell_amount = market_state.estimate_value(
            trade.reference_coin,
            trade.reference_value,
            trade.sell_coin,
        )
        new[trade.sell_coin] = new.get(trade.sell_coin, 0) - sell_amount

        buy_amount = market_state.estimate_value(
            trade.sell_coin,
            sell_amount,
            trade.buy_coin,
        )
        new[trade.buy_coin] = new.get(trade.buy_coin, 0) + buy_amount

    return new
Exemplo n.º 12
0
 def estimate_price(self, market_state: MarketState):
     '''
     Sets the approximate price of the quote value, given some chart data.
     '''
     base_price = market_state.price(self.market_name)
     # The price (when buying/selling)
     # should match the self.market_name.
     # So, we keep around a self.market_price to match
     # self.price is always in the quote currency.
     self.market_price = base_price
     # Now, we find out what price matters for our trade.
     # The base price is always in the base currency,
     # So we will need to figure out if we are trading from,
     # or to, this base currency.
     if self.buy_coin == self.market_base_currency:
         self.price = 1 / base_price
     else:
         self.price = base_price
Exemplo n.º 13
0
 def get_market_state(self, time: datetime) -> MarketState:
     # Get the latest chart data from the market
     charts = self.market_history.latest(time)
     balances = self.get_balances()
     self.market_state = MarketState(charts, balances, time, self.fiat)
     return self.market_state
Exemplo n.º 14
0
    def propose_trades_for_partial_rebalancing(
        self,
        market_state: MarketState,
        coins_to_rebalance: FrozenSet[str],
    ) -> List[AbstractTrade]:
        """TODO: Trade directly from X to Y without going through fiat.
        """
        ideal_fiat_value_per_coin = self._ideal_fiat_value_per_coin(
            market_state)

        est_values = market_state.estimate_values(market_state.balances,
                                                  self.fiat)

        # 1) Fan in to fiat, selling excess value in coins we want to rebalance
        trades_to_fiat = []
        for sell_coin in sorted(coins_to_rebalance):
            if sell_coin == self.fiat:
                continue
            value = est_values.get(sell_coin, 0)
            delta = value - ideal_fiat_value_per_coin
            if delta > 0:
                trades_to_fiat.append(
                    AbstractTrade(sell_coin, self.fiat, self.fiat, delta), )

        # 2) Simulate trades and estimate portfolio state afterwards
        est_balances_after_trades = simulate_trades(
            trades_to_fiat,
            market_state,
        )
        est_values_after_trades = market_state.estimate_values(
            est_balances_after_trades,
            self.fiat,
        )

        fiat_after_trades = est_balances_after_trades[self.fiat]
        fiat_to_redistribute = fiat_after_trades - ideal_fiat_value_per_coin
        if fiat_to_redistribute <= 0:
            return trades_to_fiat

        # 3) Find coins in which we don't hold enough value
        possible_buys = set()
        for buy_coin in self._possible_investments(market_state):
            value = est_values_after_trades.get(buy_coin, 0)
            if ideal_fiat_value_per_coin > value:
                possible_buys.add(buy_coin)

        fiat_to_redistribute_per_coin = fiat_to_redistribute / len(
            possible_buys)

        # 4) Plan trades, fanning back out from fiat to others
        trades_from_fiat = []
        for buy_coin in sorted(possible_buys):
            value = est_values_after_trades.get(buy_coin, 0)
            delta = ideal_fiat_value_per_coin - value
            if delta > 0:
                available_fiat = min(fiat_to_redistribute_per_coin, delta)
                trades_from_fiat.append(
                    AbstractTrade(self.fiat, buy_coin, self.fiat,
                                  available_fiat), )

        return trades_to_fiat + trades_from_fiat