def get_fund_with(self, currency, gain_greater_than: float = 0.0, force_include_target=True) -> Optional[Fund]: if currency == self.target_currency and force_include_target: return Fund(self._currency_positions[currency].total_amount, currency) profitable_amount = self._currency_positions[currency].get_amount_with( self._market, gain_greater_than) if profitable_amount > 0: return Fund(profitable_amount, currency) return None
def funds(self) -> List[Fund]: result = [] for position in self._currency_positions.values(): if position.total_amount > 0: result.append(Fund(position.total_amount, position.currency)) return result
def _web_request_transfer(self, source_amount, source_currency, target_currency): portfolio: PortfolioController = self._last_portfolio state = portfolio._current_portfolio_state try: if source_amount.strip().lower() == "all": source_amount = portfolio.get_fund_with( source_currency, gain_greater_than=0).amount else: source_amount = float(source_amount) commands = portfolio.create_transfer_commands( Fund(source_amount, source_currency), target_currency) if not commands: return f"Transfer path is not available between {source_currency} to {target_currency}" state = deepcopy(state) for command in commands: command.apply(state, portfolio._market) except Exception as e: return str(e) is_success = True for command in commands: log_command(command) is_success = is_success and self.portfolio_connector.execute( command) if is_success: return f"Successful transfer: {commands}" else: return f"Transfer declined: {commands}"
def total_value(self): present = self.present accumulator = Fund(0, self._market.target_currency) for fund in self.funds: accumulator += present.get_value(fund) return accumulator
def get_funds_with(self, gain_greater_than: float, force_include_target: bool = True) -> List[Fund]: result = [] for position in self._currency_positions.values(): if position.currency == self.target_currency and force_include_target: if position.total_amount > 0: result.append( Fund(position.total_amount, position.currency)) continue for bucket_amount in position.get_bucket_amounts_with( self._market, gain_greater_than): if bucket_amount > 0: result.append(Fund(bucket_amount, position.currency)) return result
def to_convert(self, fund: Fund): is_reversed = self.target_currency == fund.currency is_valid = is_reversed or self.source_currency == fund.currency if not is_valid: raise ValueError( f"Can't process sell of {fund} on pair {self._reader.pair}") # todo consider levels # todo consider fees bid, ask = self.bid_ask if is_reversed: price_per_source_unit = bid return Fund(fund.amount / price_per_source_unit, self.source_currency) else: price_per_source_unit = ask return Fund(fund.amount * price_per_source_unit, self.target_currency)
def update_portfolio(self, portfolio: PortfolioController): if not self._is_web_started: self._is_web_started = True self._start_server(portfolio.target_currency, portfolio.currencies, portfolio.pairs) self._last_portfolio = portfolio present = portfolio.present state = {} state["timestamp"] = present.timestamp state["total_value"] = portfolio.total_value.amount state["funds"] = funds = {} for currency in portfolio.currencies: funds[currency] = { "amount": 0.0, "initial_value": 0.0, "current_value": 0.0 } for currency, position in portfolio._current_portfolio_state[ "positions"].items(): amount = 0.0 initial_value = 0.0 for bucket in position: amount += bucket["amount"] initial_value += bucket["initial_value"] current_value = present.get_value(Fund(amount, currency)).amount funds[currency].update({ "amount": amount, "initial_value": initial_value, "current_value": current_value }) state["prices"] = prices = {} for pair in portfolio.pairs: orderbook = present.get_pricebook(*parse_pair(pair)) prices[pair] = { "b": orderbook.buy_levels[0][0], "s": orderbook.sell_levels[-1][0] } self.interface_state = state
def update_portfolio(self, portfolio: PortfolioController): single_buy_value = 10 # how much will be sent for every buy required_gain_threshold = 1.001 # how much the bought currency must gain in value to be sold stair_height = (required_gain_threshold - 1.0) * 2 present = portfolio.present for currency in portfolio.non_target_currencies: # run scalping algorithm on every currency that can be bought by target currency current_level = present.get_unit_cost(currency) last_level = self._last_stair_levels[currency] # detect whether price stepped down/up the stair diff = current_level / last_level if diff < 1 - stair_height: # we stepped down the whole stair # we expect the price to go high after some time - lets buy if not portfolio.can_sell(single_buy_value, portfolio.target_currency): continue # we don't have enough to buy self._last_stair_levels[ currency] = current_level # update last level portfolio.request_transfer( Fund(single_buy_value, portfolio.target_currency), currency) elif not portfolio.get_funds(currency): # no pending funds - move upstairs freely self._last_stair_levels[currency] = max( last_level, current_level) # scalp all the profitable funds we collected for fund in portfolio.get_funds_with( gain_greater_than=required_gain_threshold): if fund.currency == portfolio.target_currency: continue # target fund can't be converted to target again :) portfolio.request_transfer(fund, portfolio.target_currency) self._last_stair_levels[fund.currency] = present.get_unit_cost( fund.currency)
def get_value(self, amount, currency): return self.get_history(0).get_value(Fund(amount, currency))
def get_unit_cost(self, currency: str) -> float: return self.get_cost(Fund(1.0, currency)).amount
def get_funds(self, currency): result = [] for amount in self._currency_positions[currency].get_bucket_amounts(): result.append(Fund(amount, currency)) return result
def apply(self, portfolio_state, market: Market): if self._source_amount <= DUST_LEVEL: raise PortfolioUpdateException( f"Requested source amount {self._source_amount} is too low.") present = market.present target_amount = present.after_conversion( Fund(self._source_amount, self._source), self._target).amount if target_amount < self._min_target_amount: # the real target amount is not meeting the expectations raise PortfolioUpdateException( f"Requested {self._min_target_amount} but got only {target_amount} of {self._target}" ) target_value = present.get_value(Fund(target_amount, self._target)).amount source_initial_value = present.get_value( Fund(self._source_amount, self._source)).amount if source_initial_value < target_value: raise PortfolioUpdateException( f"Value can't increase during portfolio update. Source value: {source_initial_value} < Target value: {target_value}" ) positions = portfolio_state["positions"] # subtract amount from source positions pending_amount = self._source_amount closed_initial_value = 0.0 # get required amount from source buckets for source_bucket in positions[self._source]: amount = source_bucket["amount"] diff = min(amount, pending_amount) if diff < DUST_LEVEL: continue # calculate proportional value according to the amount subtracted partial_initial_value = max( 0.0, diff / amount) * source_bucket["initial_value"] source_bucket["amount"] -= diff source_bucket["initial_value"] -= partial_initial_value closed_initial_value += partial_initial_value pending_amount -= diff if pending_amount <= 0: break if pending_amount > DUST_LEVEL: raise PortfolioUpdateException( f"Missing {pending_amount} {self._source}.") if self._target not in positions: # ensure the target position exists positions[self._target] = [] # add amount to target bucket target_buckets = positions[self._target] target_buckets.append({ "amount": target_amount, "initial_value": source_initial_value }) self._postprocess_buckets(positions, market) return { "source_currency": self._source, "source_amount": self._source_amount, "min_target_amount": self._min_target_amount, "target_currency": self._target, "target_amount": target_amount, "source_initial_value": source_initial_value, "closed_initial_value": closed_initial_value }
def get_value(self, fund): if fund.currency == self.target_currency: return fund # value of target currency can't change because value is relative to target currency predicted_value = self._actual_unit_values[fund.currency] * fund.amount return Fund(predicted_value, self.target_currency)