Add settlement_currency

This commit is contained in:
Justin Ehrenhofer
2025-03-24 20:20:11 -05:00
parent 2da7424c56
commit 5f7d90e7f4
4 changed files with 66 additions and 20 deletions

View File

@@ -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"

View File

@@ -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)

View File

@@ -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))

View File

@@ -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')