def _setup(self, config): # Initialize a Buda.com and reference exchange clients self.market = Market.from_code(config['investment']['market']) self.ref_market = Market.from_code(config['investment']['ref_market']) self.buda = buda.BudaTrading(market=self.market, dry_run=self.dry_run) self.reference = self.get_market_client( config['investment']['ref_exchange'], self.ref_market) # Initialize Algorithm configs self.daily_investment = Money( str(config['investment']['monthly_amount'] / 30), self.market.quote) self.interval_hours = config['investment']['interval_hours'] self.transactions = self.store.get( f'transactions_{self.market}'.lower()) or [] self.amount_investment = self.calculate_amount_investment() assert self.amount_investment > Money( '1', self.market.quote), 'Amount investment too low' self.overprice_limit = config['investment']['overprice_limit'] # Currency converter to use self.converter = config['currency_converter'] self.rate = self.get_converter_rate() # Set withdrawal configs self.withdrawal_enabled = config['withdrawal']['enabled'] self.withdrawal_address = config['withdrawal']['address'] w_amount = str(config['withdrawal']['min_amount']) self.minimum_withdrawal_amount = Money( w_amount, config['withdrawal']['amount_currency'])
def update_deposits(self): # Set wallet from relevant currency according to side from_wallet = ( self.buda.wallets.quote if self.side == Side.BUY else self.buda.wallets.base ) # Get and filter deposits new_deposits = from_wallet.fetch_deposits_since(self.start_timestamp) if self.from_address != "Any": new_deposits = [ deposit for deposit in new_deposits if deposit.data.address == self.from_address ] # Update states on existing keys and add new keys with base structure for deposit in new_deposits: idx = str(deposit.id) if idx in self.deposits.keys(): if deposit.status.value != self.deposits[idx]["status"]: self.deposits[idx]["status"] = deposit.status else: self.deposits[idx] = { "status": deposit.status.value, "amounts": { "original_amount": repr(deposit.amount), "converted_amount": repr(Money(0, self.from_currency)), "converted_value": repr(Money(0, self.to_currency)), }, "orders": [], "pending_withdrawal": self.to_withdraw, } self.store.set(self.from_currency + "_deposits", self.deposits)
def _setup(self, config): # Set buda trading client client_params = dict(timeout=self.timeout) self.buda = BudaTrading( config["market"], client_params, self.dry_run, self.log, self.store ) # Set price multipliers self.buy_multiplier = Decimal(str(config["prices"]["buy_multiplier"])) self.sell_multiplier = Decimal(str(config["prices"]["sell_multiplier"])) self.max_base = Money(config["amounts"]["max_base"], self.buda.market.base) self.max_quote = Money(config["amounts"]["max_quote"], self.buda.market.quote)
def _algorithm(self): if not self.invest: self.log.info('Not investing') return balance = self.buda.wallets.quote.fetch_balance().free international_price = self.reference.fetch_ticker().last buy_amount = self.get_amount_to_buy() buy_price = truncate_money( Money(self.amount_investment.amount / buy_amount.amount, self.market.quote)) reference_price = truncate_money( Money(international_price.amount * self.rate.amount, self.rate.currency)) self.log.info(f'I have {balance}') self.log.info(f'I will invest {self.amount_investment}') self.log.info( f'{self.ref_market.quote}{self.market.quote}: {self.rate}') self.log.info(f'{self.market.base} to buy: {buy_amount}') # Log the price self.log.info( f'International {self.ref_market} price: {international_price}') self.log.info(f'International {self.market} price: {reference_price}') self.log.info(f'---------------------------------------') self.log.info(f'Buy price: {buy_price}') self.log.info( f'Overprice: {(self.get_overprice(buy_price, reference_price)):.2%}' ) self.log.info( f'Should buy? {(self.should_buy(buy_price, reference_price, balance))}' ) self.log.info(f'---------------------------------------') if not self.already_transacted(): if self.should_buy(buy_price, reference_price, balance): if self.send_buy_order(buy_amount): self.store_transaction(buy_price, self.amount_investment, buy_amount) else: self.log.info(f'Problem with order') else: self.log.info(f'Not investing') else: self.log.info(f'Already transacted') if self.withdrawal_enabled: self.log.info( f'Withdrawal enabled to address {self.withdrawal_address}') self.withdraw_to_own_wallet(reference_price) else: self.log.info(f'Withdrawal not enabled')
def _get_reference_prices(self): ticker = self.reference.fetch_ticker() ref_bid, ref_ask = ticker.bid, ticker.ask # Convert reference_price if reference market differs from current market if self.reference.market != self.buda.market: # Get conversion rate (eg CLP/USD from OpenExchangeRates) rate = self.converter.get_rate_for(self.reference.market.quote, self.buda.market.quote) self.log.info( f'{self.reference.market.quote}/{self.buda.market.quote} rate: {rate:.2f}' f' from {self.converter.name}') # Get market price according to reference (eg BTC/CLP converted from converter's BTC/USD) ref_bid = Money(ref_bid.amount * rate, self.buda.market.quote) ref_ask = Money(ref_ask.amount * rate, self.buda.market.quote) return ref_bid, ref_ask
def get_converter_rate(self): # Set currency converter if self.converter == 'OpenExchangeRates': app_id = settings.credentials['OpenExchangeRates']['app_id'] converter = OpenExchangeRates(return_decimal=True, client_params=dict(app_id=app_id)) return truncate_money( Money( converter.convert(1, self.ref_market.quote, self.market.quote), self.market.quote)) elif self.converter == 'Currencyconverterapi': api_key = settings.credentials['Currencyconverter']['key'] code = f'{self.ref_market.quote}_{self.market.quote}' rate = requests.get( f'https://free.currencyconverterapi.com/api/v6/convert?q={code}&compact=y&apiKey={api_key}' ).json()[code]['val'] return truncate_money(Money(rate, self.market.quote))
def process_withdrawals(self): # Set wallet from relevant currency according to side to_wallet = ( self.buda.wallets.base if self.side == Side.BUY else self.buda.wallets.quote ) for deposit in self.deposits.values(): # Load money amounts original_amount = Money.loads(deposit["amounts"]["original_amount"]) converted_amount = Money.loads(deposit["amounts"]["converted_amount"]) converted_value = Money.loads(deposit["amounts"]["converted_value"]) remaining = original_amount - converted_amount if ( self.side == Side.BUY ): # Change remaining amount to base currency for minimum order amount check remaining = ( self.buda.fetch_order_book().quote(self.side, remaining).base_amount ) # Filter deposits already converted and pending withdrawal original_amount_is_converted = remaining < self.buda.min_order_amount if ( deposit["status"] == TxStatus.OK.value and deposit["pending_withdrawal"] and original_amount_is_converted ): withdrawal_amount = truncate_money(converted_value) available = to_wallet.fetch_balance().free if ( withdrawal_amount <= available ): # We cannot withdraw more than available balance w = to_wallet.request_withdrawal( withdrawal_amount, self.to_address, subtract_fee=True ) if ( w.status == TxStatus.PENDING ): # Check state to set and store updated values self.log.info( f"{self.to_currency} withdrawal request received, updating store values" ) deposit["pending_withdrawal"] = False self.store.set(self.from_currency + "_deposits", self.deposits) else: self.log.warning("Withdrawal failed") else: self.log.warning( f"Available balance not enough for withdrawal amount {withdrawal_amount}" )
def process_conversions(self): for deposit in self.deposits.values(): # Calculate remaining amount to convert original_amount = Money.loads(deposit["amounts"]["original_amount"]) converted_amount = Money.loads(deposit["amounts"]["converted_amount"]) converted_value = Money.loads(deposit["amounts"]["converted_value"]) remaining = original_amount - converted_amount if ( self.side == Side.BUY ): # Change remaining amount to base currency for order creation purposes remaining = ( self.buda.fetch_order_book().quote(self.side, remaining).base_amount ) if ( deposit["status"] == TxStatus.OK.value and remaining > self.buda.min_order_amount ): remaining = truncate_money(remaining) # Convert remaining amount using market order order = self.buda.place_market_order(self.side, remaining) # Wait for traded state to set updated values if order: self.log.info( f"{self.side} market order placed, waiting for traded state" ) while order.status != OrderStatus.CLOSED: order = self.buda.fetch_order(order.id) maya.time.sleep(1) self.log.info(f"{self.side} order traded, updating store values") if self.side == Side.BUY: converted_amount += order.cost converted_value += order.filled - order.fee if self.side == Side.SELL: converted_amount += order.filled converted_value += order.cost - order.fee deposit["orders"].append( order.id ) # Save related orders for debugging # Save new values __str__ deposit["amounts"]["converted_amount"] = repr(converted_amount) deposit["amounts"]["converted_value"] = repr(converted_value) # Store all deposits self.store.set(self.from_currency + "_deposits", self.deposits)
def _setup(self, config): # Set buda trading client client_params = dict(timeout=self.timeout) self.buda = buda.BudaTrading( config["market"], client_params, self.dry_run, self.log, self.store ) self.store_keys = ("trades", self.buda.market.code.lower()) # Set reference market client self.candle_interval = config["reference"]["candle_interval"] self.reference = self._get_market_client( config["reference"]["name"], config["reference"]["market"] ) assert self.reference.market.base == self.buda.market.base # Init variables self.max_base = Money(config["amounts"]["max_base"], self.buda.market.base) self.max_quote = Money(config["amounts"]["max_quote"], self.buda.market.quote) self.position = self.store.get("position") or dict(status="closed") # Set talib configs self.bbands_periods = config["talib"]["bbands"]["periods"] self.rsi_periods = config["talib"]["rsi"]["periods"] self.rsi_overbought = config["talib"]["rsi"]["overbought"] self.rsi_oversold = config["talib"]["rsi"]["oversold"]
def _setup(self, config): # Set buda trading client client_params = dict(timeout=self.timeout) self.buda = buda.BudaTrading( config["market"], client_params, self.dry_run, self.log, self.store ) # Set reference market client self.reference = self._get_market_client( config["reference"]["name"], config["reference"]["market"] ) assert self.reference.market.base == self.buda.market.base # Set converter app_id = settings.credentials["OpenExchangeRates"]["app_id"] self.converter = OpenExchangeRates( return_decimal=True, client_params=dict(app_id=app_id) ) # Set price multipliers self.buy_multiplier = Decimal(str(config["prices"]["buy_multiplier"])) self.sell_multiplier = Decimal(str(config["prices"]["sell_multiplier"])) # Set max amounts self.max_base = Money(config["amounts"]["max_base"], self.buda.market.base) self.max_quote = Money(config["amounts"]["max_quote"], self.buda.market.quote)
def _setup(self, config): # Set buda trading client client_params = dict(timeout=self.timeout) self.buda = buda.BudaTrading(config['market'], client_params, self.dry_run, self.log, self.store) self.store_keys = ('trades', self.buda.market.code.lower()) # Set reference market client self.candle_interval = config['reference']['candle_interval'] self.reference = self._get_market_client(config['reference']['name'], config['reference']['market']) assert self.reference.market.base == self.buda.market.base # Init variables self.max_base = Money(config['amounts']['max_base'], self.buda.market.base) self.max_quote = Money(config['amounts']['max_quote'], self.buda.market.quote) self.position = self.store.get('position') or dict(status='closed') # Set talib configs self.bbands_periods = config['talib']['bbands']['periods'] self.rsi_periods = config['talib']['rsi']['periods'] self.rsi_overbought = config['talib']['rsi']['overbought'] self.rsi_oversold = config['talib']['rsi']['oversold']
def _algorithm(self): # Setup self.log.info( f"Preparing prices using {self.reference.name} {self.reference.market.code}" ) ref_bid, ref_ask = self._get_reference_prices() self.log.info( f"Reference prices on {self.reference.name}: Bid: {ref_bid} Ask: {ref_ask}" ) # Set offset prices from reference and price multiplier price_buy = truncate_money(ref_bid * self.buy_multiplier) price_sell = truncate_money(ref_ask * self.sell_multiplier) self.log.info( f"{self.buda.market} calculated prices: Buy: {price_buy} Sell: {price_sell}" ) # Cancel open orders self.log.info("Closing open orders") self.buda.cancel_all_orders() # Get available balances self.log.info(f"Preparing amounts") # Fetch available balances available_base = self.buda.wallets.base.fetch_balance().free available_quote = self.buda.wallets.quote.fetch_balance().free # Adjust amounts to max in config amount_base = min(self.max_base, available_base) amount_quote = min(self.max_quote, available_quote) self.log.info(f"Amounts | Bid: {amount_base} | Ask: {amount_quote}") # Get order buy and sell amounts # *quote amount must be converted to base amount_buy = truncate_money( Money(amount_quote / price_buy, self.buda.market.base) ) amount_sell = truncate_money(amount_base) self.log.info(f"Amounts | Buy {amount_buy} | Sell {amount_sell}") # PLACE ORDERS self.log.info("Starting order deployment") if amount_buy >= self.buda.min_order_amount: self.buda.place_limit_order( side=Side.BUY, amount=amount_buy, price=price_buy ) if amount_sell >= self.buda.min_order_amount: self.buda.place_limit_order( side=Side.SELL, amount=amount_sell, price=price_sell )
def withdraw_to_own_wallet(self, reference_price): if self.minimum_withdrawal_amount.currency == self.market.quote: min_amount = truncate_money( Money( self.minimum_withdrawal_amount.amount / reference_price.amount, self.market.base)) else: min_amount = truncate_money(self.minimum_withdrawal_amount) assert min_amount.currency == self.market.base, 'Something is wrong with withdrawal currency' balance = self.buda.wallets.base.fetch_balance().free if balance >= min_amount: self.log.info( f'Requesting withdrawals of {balance} to {self.withdrawal_address}' ) self.buda.wallets.base.request_withdrawal(balance, self.withdrawal_address, substract_fee=True) else: self.log.info(f'Not enough balance: {balance} < {min_amount}')
def _algorithm(self): # PREPARE ORDER PRICES # Get middle price ticker = self.buda.fetch_ticker() self.log.info( f"Ticker prices | Bid: {ticker.bid} | Ask: {ticker.ask} | Mid: {ticker.mid}" ) # Offset prices from middle using configured price multipliers price_buy = truncate_money(ticker.mid * self.buy_multiplier) price_sell = truncate_money(ticker.mid * self.sell_multiplier) self.log.info(f"Relative prices | Buy: {price_buy} | Sell: {price_sell}") # PREPARE ORDER AMOUNTS # Cancel open orders to get correct available amounts self.log.info("Closing open orders") self.buda.cancel_all_orders() # Fetch available balances available_base = self.buda.wallets.base.fetch_balance().free available_quote = self.buda.wallets.quote.fetch_balance().free # Adjust amounts to max in config amount_base = min(self.max_base, available_base) amount_quote = min(self.max_quote, available_quote) # Get order buy and sell amounts # *quote amount must be converted to base amount_buy = truncate_money( Money(amount_quote / price_buy, self.buda.market.base) ) amount_sell = truncate_money(amount_base) self.log.info(f"Amounts | Buy {amount_buy} | Sell {amount_sell}") # PLACE ORDERS self.log.info("Starting order deployment") if amount_buy >= self.buda.min_order_amount: self.buda.place_limit_order( side=Side.BUY, amount=amount_buy, price=price_buy ) if amount_sell >= self.buda.min_order_amount: self.buda.place_limit_order( side=Side.SELL, amount=amount_sell, price=price_sell )
def _algorithm(self): # Update candle data and TA indicators self.log.info( f"Getting trades from {self.reference.name} {self.reference.market.code}" ) from_time = maya.when("1 day ago").epoch trades = self.get_trades(from_time) # Create pandas DataFrame from trades and set date as index df = pd.DataFrame(trades) df.index = df.timestamp.apply(lambda x: pd.datetime.utcfromtimestamp(x)) # Build 5min candles from trades DataFrame [open, high, close, low] df = df.rate.resample(self.candle_interval).ohlc() # Calculate Bollinger Bands and RSI from talib df["bb_lower"], df["bb_middle"], df["bb_upper"] = talib.BBANDS( df.close, timeperiod=self.bbands_periods ) df["rsi"] = talib.RSI(df.close, timeperiod=self.rsi_periods) lower = self.truncate_price(df.bb_lower[-1]) middle = self.truncate_price(df.bb_middle[-1]) upper = self.truncate_price(df.bb_upper[-1]) self.log.info(f"BB_lower: {lower} | BB_middle: {middle} | BB_upper: {upper}") self.log.info(f"RSI: {df.rsi[-1]:.2f}") # Check if our position is open or closed if self.position["status"] == "closed": # Try conditions to open position self.log.info(f"Position is closed, checking to open") if df.close[-1] < df.bb_lower[-1] and df.rsi[-1] < self.rsi_oversold: # Last price is lower than the lower BBand and RSI is oversold, BUY! self.log.info(f"Market oversold! BUY!") amount = self.get_amount(Side.BUY) if amount >= self.buda.min_order_amount: tx = self.buda.place_market_order(Side.BUY, amount) self.position = { "status": "open", "side": Side.BUY.value, "amount": repr(tx.amount), } else: self.log.warning( f"Available amount is lower than minimum order amount" ) elif df.close[-1] > df.bb_upper[-1] and df.rsi[-1] > self.rsi_overbought: # Last price is higher than the upper BBand and RSI is overbought, SELL! self.log.info(f"Market overbought! SELL!") amount = self.get_amount(Side.SELL) if amount >= self.buda.min_order_amount: tx = self.buda.place_market_order(Side.SELL, amount) self.position = { "status": "open", "side": Side.SELL.value, "amount": repr(tx.amount), } else: self.log.warning( f"Available amount is lower than minimum order amount" ) else: self.log.info(f"Market conditions unmet to open position") else: self.log.info(f"Position is open, checking to close") if self.position["side"] == Side.BUY.value and df.rsi[-1] >= 30: # RSI is back to normal, close Buy position self.log.info(f"Market is back to normal, closing position") amount = Money.loads(self.position["amount"]) if amount >= self.buda.min_order_amount: tx = self.buda.place_market_order(Side.SELL, amount) remaining = amount - tx.amount if remaining < self.buda.min_order_amount: self.position = {"status": "closed"} else: self.position["amount"] = remaining else: self.log.warning( f"Available amount is lower than minimum order amount" ) elif self.position["side"] == Side.SELL.value and df.rsi[-1] <= 70: # RSI is back to normal, close Sell position self.log.info(f"Market is back to normal, closing position") amount = Money.loads(self.position["amount"]) if amount >= self.buda.min_order_amount: tx = self.buda.place_market_order(Side.BUY, amount) remaining = amount - tx.amount if remaining < self.buda.min_order_amount: self.position = {"status": "closed"} else: self.position["amount"] = repr(remaining) else: self.log.warning( f"Available amount is lower than minimum order amount" ) else: self.log.info(f"Market conditions unmet to close position") self.store.set("position", self.position)