Crypto Margin Trading Bots: Getting Started Using Python

MORITZ PUTZHAMMER

22 November 202210 min read

Table of contents

Margin trading is very similar to spot trading. In both cases, a trade is simply the conversion of one currency for another.

The main difference with margin trading is that it introduces the ability for a trader to borrow a currency. While the currency is borrowed, interest is paid on the loan. Borrowing gives traders the ability to short sell the market as well as take more risk. In the case of short selling, an asset can be borrowed and sold immediately in the hope that the price will be lower at a later date. When the loan is repaid, the profit is the price for which the borrowed asset was sold and the price at the time the loan was repaid.

Let’s take an example. If a trader has a strong conviction that the price of BTC is going up, then they can borrow USDT to purchase BTC. This is known as leverage trading since it is possible to take a bigger position in BTC than would be possible without borrowing. This leverage multiplies both the profits and losses as a percentage of the traders account balance, which is why it can be risky.

In the following article, we'll explore how you can get started with crypto margin trading bots by using Python, covering topics such as margin trading interest and fees, examples of short selling and leverage trading, margin ratio limits, and the issue of liquidation before concluding with a number of Python code examples (e.g., OCO, long-short QQE strategy, and calculating daily interest, among others).

Margin Trading Bots Use Cases

Essentially, there are three main use cases for a margin trading bot.

  • Short selling: taking a position that profits from the price of the asset going down.
  • Buying on leverage: long-only strategy but is able to take more risk than would be possible without borrowing.
  • Long/short strategies: these are used by relative value strategies that buy one asset and sell another asset to make money from the relative performance.

Margin Trading and Borrowing

Margin trading can seem complicated, but it is much simpler to think of margin trading as similar to spot trading and separately understand the process of borrowing and repaying crypto loans. If the trader wants to buy ETH with BNB, for example, but currently does not have BNB, then they can either sell another asset to buy BNB or they can borrow the required BNB.

The ability to borrow cryptocurrencies the trader does not own is what makes short selling possible. To sell BTC in the spot markets, the trader is required to already own some. However, with margin trading, BTC can be borrowed and then sold without ever owning it. When the BTC is bought back at a later date, the BTC loan and interest can be repaid.

Margin Trading Interest and Fees

Borrowed currency accrues interest in the same currency. If BTC is borrowed, the interest is payable in BTC. The interest is computed by the exchange and, in the case of you run a Binance margin trading bot, it is compounded hourly at a rate of 1/24 times the daily interest rate. These rates can be found at Binance Margin Interest Rates.

The minimum amount of time that currency can be borrowed on Binance is 1 hour. It is also important to note that, when placing a limit order on margin, the currency is borrowed when the order is placed and not when it is filled. So if the order is not filled, interest will still be owed on the transaction.

An Example of Short Selling

Short selling using borrowing can be better understood by looking at the cash flow that occurs during a short sale.

short selling, Python, margin trading bots

In the end, the USDT balance has grown from 20,000 USDT to 30,000 USDT, which is the same magnitude as the price change of BTC from 35,000 USDT to 25,000 USDT.

An Example of Leverage Trading

A trader can also trade with up to 3x leverage on Binance Cross Margin, which allows a trader to buy more of an asset than they would otherwise.

leverage trading, Python, margin trading bots

The final balance is 55,000 USDT or a profit of 30,000 USDT from the starting balance of 25,000 USDT. The price of BTC during the trade changed from 25,000 USD to 40,000 USD, and so using 2x leverage doubles the profits.

Margin Account Balances

Margin balances are similar to spot balances with the addition of borrowed amount and an interest owed amount. Borrowed amount is the total amount borrowed of an asset and interest is the total interest accrued.

Spot balances example

spot balances, Python, margin trading bots

Margin balances example

margin balances, Python, margin trading bots

Free amount

This is the total owned and can be used to repay loans or buy assets, or allocated for limit orders.

Locked amount

This is the total of owned assets that are reserved for a limit or stop orders and cannot be used to trade or repay loans. When a limit order is placed, then the amount will be transferred from free to locked. Only free assets can be locked.

Borrowed amount

This is the total of all the loans on an asset and can only be repaid by free assets.

Interest amount

This is the total interest accrued on all of the loans for this asset. Interest is paid before the loan's principal.

Margin Ratio

The margin ratio is calculated as the value of assets / the total liabilities. The total liabilities is the borrowed principal amount and all interest owed. The exchange ensures that this ratio is greater than 1.1. If this is not the case, then the assets owned by the trader will be forced sold and the liabilities repaid. The trader must do everything possible to avoid liquidation.

Margin ratio limits

  • Margin trading limit: this is the minimum margin ratio at which no more borrowing will be allowed.
  • Margin call limit: this is the margin ratio at which the trader will be notified that their account is at risk of liquidation if the loans are not repaid or if more collateral is added.
  • Liquidation limit: below this margin ratio, the assets in the account will be automatically sold and interest and borrowed amount paid.

Liquidation

Liquidation will take place if the margin ratio drops below 1.1. This is an automatic process that is done by the exchange in which assets are sold to increase the margin ratio. Loans are repaid from the proceeds of sales and a fee is also taken by the exchange. In the case of Binance, the liquidation fee is 2% of the value of the assets sold. It is important to note that assets not associated with the trade may be sold to repay borrowed currencies. If the trader owns BTC and also has a 3x leverage long on LUNA, then some of the BTC will be sold if LUNA goes to zero.

Risk Associated with Margin Trading

It is important to understand the risks associated with margin trading, of which there are two main types: shorting risk and leverage risk.

Margin trading and shorting risk

The risk associated with short is that the asset price moves up without stopping. In the case of buying an asset, the price can only go to 0. If the price goes up, there is no limit. A short trade will be closed when the losses exceed the value of the collateral in the account. The position will be liquidated as a result and the loans on the shorted asset repaid.

Margin trading and leverage risk

If the trader has borrowed to buy an asset, then the margin account may be liquidated before the asset price has reached zero. In the case where leverage is 2x, if the value of the asset falls by about 50%, then the account will be liquidated. The maximum leverage allowed for cross margin on Binance is 3x. If a position is 3x leveraged, then it only takes a drop of about 33% for the account to be liquidated.

Loan and Repayment Refresh

When loans are placed, they are not immediately executed. They are similar to orders in that they are requests that can fail, if, for instance, the requested loan or repayment violates the margin ratios or available balances. The margin borrow repay logic returns a loan or repayment object that can be stored in the state and refreshed and queried later.

state.loan = margin_borrow("BTC", 0.01)

if state.loan is not None:
   state.loan.refresh()
   print(state.loan)
state.repayment = margin_repay("BTC", 0.01)

if state.repayment is not None:
   state.repayment.refresh()
   print(state.repayment)

On the next execution after the loan or repayment is created, the status will change from “Pending” to “Confirmed” or “Failed”.

Python Code Samples for Your Margin Trading Bot

Now let's take a look at a few specific coding examples, which you can use with your margin bot.

One-Cancels-the-Other (OCO)

from enum import Enum

class Mode(Enum):
    Buy = 1
    Sell = 2

@enable_margin_trading()
def initialize(state):
    state.order = None
    state.tp_order = None
    state.sl_order = None

    state.mode = Mode.Buy

def has_position(data):
    free = query_balance_free(data.base)
    locked = query_balance_locked(data.base)
    borrowed = query_balance_borrowed(data.base)
    interest = query_balance_interest(data.base)

    # calculate risk exposure
    exposure = free+locked-borrowed-interest

    price = Decimal(f"{data.close[-1]}")
    limit = symbol_limits(data.symbol)
    has_exposure = abs(exposure) * price > Decimal(limit.costMin)
    has_liabilities = (borrowed+interest) * price > Decimal(limit.costMin)

    return has_exposure or has_liabilities

@schedule(interval="1h", symbol="BTCBUSD")
def handler(state, data):

    has_pos = has_position(data)
    if not has_pos:

        free = query_balance_free(data.base)
        borrowed = query_balance_borrowed(data.base)
        interest = query_balance_interest(data.base)
        if (borrowed + interest) > 0 and free > (borrowed + interest):
            # clean up any residual borrowed amount by repaying loans
            print(f"repaying {data.base} borrowed {borrowed} interest: {interest}")
            margin_repay(data.base, borrowed + interest)
        elif state.mode == Mode.Buy:
            state.mode = Mode.Sell
            # buy margin order with borrowing
            value = float(query_portfolio_value()) * 1.1
            state.order = margin_order_market_value(symbol = data.symbol, value = value, side_effect = OrderMarginSideEffect.AutoDetermine)

            with OrderScope.one_cancels_others():
                # adjust for fees since purchase amount will be smaller
                order_qty = subtract_order_fees(state.order.quantity)
                tp_price = data.close[-1] * 1.01
                sl_price = data.close[-1] / 1.01

                # exit orders should repay.
                print("long stops", state.order.quantity, order_qty)
                state.tp_order = margin_order_iftouched_market_amount(symbol = data.symbol, amount = -order_qty, stop_price=tp_price,
                                                    side_effect = OrderMarginSideEffect.Repay)
                state.sl_order = margin_order_iftouched_market_amount(symbol = data.symbol, amount = -order_qty, stop_price=sl_price,
                                                    side_effect = OrderMarginSideEffect.Repay)

        elif state.mode == Mode.Sell:
            state.mode = Mode.Buy
            # sell short with borrowing
            value = float(query_portfolio_value()) * -0.5
            state.order = margin_order_market_value(symbol = data.symbol, value = value, side_effect = OrderMarginSideEffect.AutoDetermine)

            with OrderScope.one_cancels_others():
                # fees of sale are paid in quoted so no adjustment for fees is needed
                order_qty = state.order.quantity
                tp_price = data.close[-1] / 1.01   # take profit is below price
                sl_price = data.close[-1] * 1.01   # stop loss is above current price

                # exit orders should repay.
                # exit orders should be buys to close
                # note: this will leave dust when the position is closed due to accumulated interest
                print("short stops", state.order.quantity, order_qty)
                state.tp_order = margin_order_iftouched_market_amount(symbol = data.symbol, amount = order_qty, stop_price=tp_price,
                                                    side_effect = OrderMarginSideEffect.Repay)
                state.sl_order = margin_order_iftouched_market_amount(symbol = data.symbol, amount = order_qty, stop_price=sl_price,
                                                    side_effect = OrderMarginSideEffect.Repay)

Short selling

@enable_margin_trading()
def initialize(state):
    pass

@schedule(interval="1d", symbol="BTCUSDT")
def handler(state, data):
    print(query_balances())
    pos = query_open_position_by_symbol(data.symbol, include_dust=False)

    macd = data.macd(12,26, 9)

    if pos is None :
        if macd["macd_histogram"][-1] < 0:
            # short btc
            # since the btc free balance is 0 the btc will be borrowed first and then sold
            margin_order_market_amount(symbol = data.symbol, amount = -0.01,  side_effect = OrderMarginSideEffect.AutoDetermine)
    else:
        # close position by repaying loan
        if macd["macd_histogram"][-1] > 0:
            amount = float(query_balance_borrowed(data.base) + query_balance_interest(data.base))
            amount *= 1.01  # adjust for order fees

            margin_order_market_amount(symbol = data.symbol, amount = amount,  side_effect = OrderMarginSideEffect.AutoDetermine)

Buying with leverage

@enable_margin_trading()
def initialize(state):
    pass

@schedule(interval="1d", symbol="BTCUSDT")
def handler(state, data):
    print(query_balances())
    pos = query_open_position_by_symbol(data.symbol, include_dust=False)

    macd = data.macd(12,26, 9)

    if pos is None :
        if macd["macd_histogram"][-1] > 0:
            amount = 2.0 * float(query_balance_free("USDT")) /data.close[-1]
            # leverage long btc
            # since the usdt free balance is not enoug the extra usdt will be borrowed first
            buy_order = margin_order_market_amount(symbol = data.symbol, amount = amount,  side_effect = OrderMarginSideEffect.AutoDetermine)
            print("buy order side effect", buy_order.margin_side_effect)
    else:
        # close position by repaying loan
        if macd["macd_histogram"][-1] < 0:
            amount = -float(query_balance_free(data.base))

            sell_order = margin_order_market_amount(symbol = data.symbol, amount = amount,  side_effect = OrderMarginSideEffect.AutoDetermine)
            print("sell order side effect", sell_order.margin_side_effect)

Long-short QQE strategy

TRADE_SIZE = 500
MIN_TRADE_SIZE = 50

@enable_margin_trading()
def initialize(state):
    state.buy_order = None
    state.sell_order = None

@schedule(interval="1d", symbol="BTCUSDT", window_size=200)
def handler(state, data):
    portfolio = query_portfolio()
    pos = query_open_position_by_symbol(data.symbol)

    # use qqe indicator as the trend
    trend = -1 if data.qqe(21, 5, 4.2)["trend"][-1]  < 0 else +1

    exposure = 0.0 if pos is None else float(pos.exposure)

    # plot data
    plot_line("leverage", query_position_weight(data.symbol), data.symbol)
    plot_line("exposure", exposure, data.symbol)
    plot_line("value", portfolio.portfolio_value, data.symbol)

    if trend > 0:
        target_pos = TRADE_SIZE / data.close[-1]
        trade_size = target_pos - exposure

        # check trade size is large enough to be accepted by the exchange
        if trade_size * data.close[-1] > +MIN_TRADE_SIZE :
            print(f"buying {trade_size:.5f} {data.base}")
            state.buy_order = margin_order_market_amount(symbol = "BTCUSDT", amount = trade_size,
                                                    side_effect = OrderMarginSideEffect.AutoDetermine)

    if trend < 0:
        target_pos = -TRADE_SIZE / data.close[-1]
        trade_size = target_pos - exposure

        # check trade is size is large enough to be accepted by the exchange
        if trade_size * data.close[-1] < -MIN_TRADE_SIZE :
            print(f"selling {trade_size:.5f} {data.base}")
            state.sell_order = margin_order_market_amount(symbol = "BTCUSDT", amount = trade_size,
                                                    side_effect = OrderMarginSideEffect.AutoDetermine)

Calculate daily interest cost

@enable_margin_trading()
def initialize(state):
    state.run = 0

@schedule(interval="1d", symbol="BTCUSDT")
def handler(state, data):
    if state.run == 0:
        margin_borrow("BTC", 500/data.close[-1])
        margin_borrow("USDT", 500)

    print(f"margin_level {query_margin_ratio():.3f}")

    btc_debt = query_balance_borrowed(data.base) + query_balance_interest(data.base)
    base_borrow_cost = btc_debt * query_margin_daily_interest_rate(data.base) * Decimal(f"{data.close[-1]}")
    print(f"{data.base} {btc_debt} debt daily borrow cost is {base_borrow_cost:.2f} {data.quoted}")

    usdt_debt = query_balance_borrowed(data.quoted) + query_balance_interest(data.quoted)
    quoted_borrow_cost = usdt_debt * query_margin_daily_interest_rate(data.quoted)
    print(f"{data.quoted} {usdt_debt} debt daily borrow cost  {quoted_borrow_cost:.2f} {data.quoted}")

    state.run += 1

Final Thoughts on Margin Trading Bots with Trality

Obviously, there is far more to say on the subject, but the above guide is meant to serve as an informative introduction to getting started with margin trading bots by using Python.

Once you’re happy with your margin bot, the next step is to link it with the best crypto margin trading exchange. In terms of trading on a centralized exchange, a Binance trading bot will likely offer the best benefits to a broader range of traders, but there are excellent decentralized exchanges from which to choose if you decide to go that route.

Here at Trality HQ, we are constantly refining and optimizing our platform's tools to meet the needs of our users. If there are any specific code instances that you would like to see covered, then by all means get in touch!