From 5f7d90e7f443a2fcf87cdcb39b55c6261062d2cd Mon Sep 17 00:00:00 2001 From: Justin Ehrenhofer <12520755+SamsungGalaxyPlayer@users.noreply.github.com> Date: Mon, 24 Mar 2025 20:20:11 -0500 Subject: [PATCH] Add settlement_currency --- .env.example | 1 + README.md | 21 +++++++++++++--- src/autoconvert.py | 63 +++++++++++++++++++++++++++++++++------------- src/env.py | 1 + 4 files changed, 66 insertions(+), 20 deletions(-) diff --git a/.env.example b/.env.example index cb959bf..ff2e51f 100644 --- a/.env.example +++ b/.env.example @@ -15,6 +15,7 @@ KRAKEN_API_SECRET="" MAX_NETWORK_FEE_PERCENT="5" MAX_SLIPPAGE_PERCENT="0.5" +SETTLEMENT_CURRENCY="USD" BITCOIN_FEE_SOURCE="https://mempool.space/api/v1/fees/recommended" BITCOIN_FEE_RATE="halfHourFee" diff --git a/README.md b/README.md index 2df27d1..bd90c7c 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ # autoforward-autoconvert -Programs to auto-forward BTC and XMR wallets to Kraken, and then auto-convert to USD. +Programs to auto-forward BTC, LTC, LTC-MWEB, and XMR wallets to Kraken, and then auto-convert to your preferred settlement currency (e.g. USD, USDT, BTC). ## Requirements @@ -29,6 +29,7 @@ Create a `.env` file as a copy of `.env.example` and set the values for the empt | `LITECOIN_ELECTRUM_SERVER_ADDRESS` | **No** | - | The address of a Litecoin Electrum server you own or trust. E.g.: `localhost:50001:t` (no SSL) or `my.electrum.server:50001:s` (SSL). By leaving this blank you're letting Electrum select a random server for you, which may be a privacy concern. | | `MAX_NETWORK_FEE_PERCENT` | **No** | `5` | The maximum accepted miner fee percent when auto-forwarding. Not applied to XMR. | | `MAX_SLIPPAGE_PERCENT` | **No** | `0.5` | The maximum accepted slippage percent when auto-converting. | +| `SETTLEMENT_CURRENCY` | **No** | `USD` | The currency you wish to convert to. If there isn't a direct trading pair, it attempts to trade through USD first, e.g. XMR->USD->DAI. | | `BITCOIN_FEE_SOURCE` | **No** | `https://mempool.space/api/v1/fees/recommended` | The fee API source to use for Bitcoin transactions. | | `BITCOIN_FEE_RATE` | **No** | `halfHourFee` | The fee rate to use in the Bitcoin fee source API response. | | `LITECOIN_FEE_SOURCE` | **No** | `https://litecoinspace.org/api/v1/fees/recommended` | The fee API source to use for Litecoin transactions. | @@ -57,11 +58,25 @@ Change the seeds in the `.env` file and start all services again: $ docker-compose --env-file .env up -d ``` -# Contributing +## Trading Strategy + +The trading strategy utilized in this program is as follows: + +- Fetch the current balance to sell. +- First try to sell with one trade, e.g. XMR->USD. +- If that fails, try to sell via USD, e.g. XMR->USD->DAI. +- Sell by taking as many maarket trades as possible to not move the price down more than 0.5% from the calculated mid price. +- Wait a random amount of time before trying again between 30-90 seconds. + +We strongly recommend running this program with a dedicated Kraken account to ensure that it does not interfere with any of your other activities. + +This trading stategy may not be optimal for your needs. You can configure the `MAX_NETWORK_FEE_PERCENT` environment variable. For illiquid trading pairs that regularly have bid-ask spreads exceeding 1%, consider a higher value than 0.5%. You should consider a different trading strategy if you are sensitive to taker fees. + +## Contributing Pull requests welcome! Thanks for supporting MAGIC Grants. -# License +## License [MIT](LICENSE) diff --git a/src/autoconvert.py b/src/autoconvert.py index 0a5d1bc..93c36fc 100644 --- a/src/autoconvert.py +++ b/src/autoconvert.py @@ -13,39 +13,68 @@ order_min = { 'XMR': 0.03 } -def get_balance(asset: Literal['XBT', 'LTC', 'XMR']) -> str: +def get_balance(settlement_kraken_ticker) -> str: balances = util.kraken_request('/0/private/Balance') balance = '0' - if f'X{asset}' in balances: - balance = balances[f'X{asset}'] + if f'{settlement_kraken_ticker}' in balances: + balance = balances[f'{settlement_kraken_ticker}'] return balance -def get_orderbook(asset: Literal['XBT', 'LTC', 'XMR']): - return util.kraken_request('/0/public/Depth?count=1', {'pair': f'{asset}USD'})[f'X{asset}ZUSD'] +def get_orderbook(asset: Literal['XBT', 'LTC', 'XMR'], settlement_currency='USD', settlement_kraken_ticker='ZUSD'): + return util.kraken_request('/0/public/Depth?count=1', {'pair': f'{asset}{settlement_currency}'})[f'X{asset}{settlement_kraken_ticker}'] #TODO check -def attempt_sell(asset: Literal['XBT', 'XMR']): +def make_trade(orderbook, payload): + mid_market_price = float(orderbook['bids'][0][0]) + ((float(orderbook['asks'][0][0]) - float(orderbook['bids'][0][0])) / 2) # Example 212.55+((212.72-212.55)/2) = 212.635 + limit_price = mid_market_price * (1-env.MAX_SLIPPAGE_PERCENT/100) # Example 212.365*(1-0.5/100) = 211.303175 + payload['price'] = limit_price + util.kraken_request('/0/private/AddOrder', payload) + print(util.get_time(), f'Selling up to {balance} for pair {payload['pair']} at no less than {limit_price}') + +def attempt_sell(asset: Literal['XBT', 'LTC', 'XMR']): + balance = get_balance(f'X{asset}') if balance < order_min[asset]: print(util.get_time(), f'Not enough {asset} balance to sell. (Balance: {balance}, Min order: {order_min[asset]})') return - - orderbook = get_orderbook(asset) - mid_market_price = float(orderbook['bids'][0][0]) + ((float(orderbook['asks'][0][0]) - float(orderbook['bids'][0][0])) / 2) # Example 212.55+((212.72-212.55)/2) = 212.635 - limit_price = mid_market_price * (1-env.MAX_SLIPPAGE_PERCENT/100) # Example 212.365*(1-0.5/100) = 211.303175 + + settlement_currency = env.SETTLEMENT_CURRENCY + # https://support.kraken.com/hc/en-us/articles/360001206766-Bitcoin-currency-code-XBT-vs-BTC + if settlement_currency in ['AUD', 'CAD', 'EUR', 'GBP', 'JPY', 'USD']: + settlement_kraken_ticker = 'Z' + settlement_currency + elif settlement_currency in ['ETC', 'ETH', 'LTC', 'MLN', 'REP', 'XBT', 'XDG', 'XLM', 'XMR', 'XRP', 'ZEC']: + settlement_kraken_ticker = 'X' + settlement_currency + else: + settlement_kraken_ticker = settlement_currency payload = { 'ordertype': 'limit', 'type': 'sell', - 'pair': f'{asset}USD', + 'pair': f'{asset}{settlement_currency}', 'volume': balance, - 'price': limit_price, - 'timeinforce': 'IOC', # Immediately fill order to extent possible, then kill + 'timeinforce': 'IOC', # Immediately fill order to extent possible, then cancel 'validate': true # Remove this after tested } - util.kraken_request('/0/private/AddOrder', payload) - print(util.get_time(), f'Selling up to {balance} at no less than {limit_price}') + if settlement_currency != asset: + try: + orderbook = get_orderbook(asset, settlement_currency, settlement_kraken_ticker) + if orderbook.status_code == 200: + make_trade(orderbook, payload) + else: + raise Exception + except: + print(f'No direct trading pair for {payload['pair']}, trying USD') + payload['pair'] = f'{asset}USD' + orderbook = get_orderbook(asset) + make_trade(orderbook, payload) + payload['pair'] = f'{settlement_currency}USD' + payload['balance'] = get_balance('ZUSD') + orderbook = get_orderbook(settlement_currency) + make_trade(orderbook, payload) + + else: + print(f'Not converting {asset} since it is already in the settlement currency') while 1: for asset in ['XBT', 'LTC', 'XMR']: @@ -55,5 +84,5 @@ while 1: print(util.get_time(), f'Error attempting to sell {asset}:') print(traceback.format_exc()) - delay = random.randint(30, 90) - time.sleep(delay) + delay = random.uniform(30, 90) + time.sleep(round(delay, 2)) diff --git a/src/env.py b/src/env.py index eed8640..b5012f0 100644 --- a/src/env.py +++ b/src/env.py @@ -24,6 +24,7 @@ KRAKEN_API_SECRET = os.getenv('KRAKEN_API_SECRET', '') MAX_NETWORK_FEE_PERCENT = float(os.getenv('MAX_NETWORK_FEE_PERCENT', '5')) MAX_SLIPPAGE_PERCENT = float(os.getenv('MAX_SLIPPAGE_PERCENT', '0.5')) +SETTLEMENT_CURRENCY = os.getenv('SETTLEMENT_CURRENCY', 'USD') BITCOIN_FEE_SOURCE = os.getenv('BITCOIN_FEE_SOURCE', 'https://mempool.space/api/v1/fees/recommended') BITCOIN_FEE_RATE = os.getenv('BITCOIN_FEE_RATE', 'halfHourFee')