Assets-Rebalancing mit python-binance api

Wenn du dir nicht sicher bist, in welchem der anderen Foren du die Frage stellen sollst, dann bist du hier im Forum für allgemeine Fragen sicher richtig.
Antworten
greetings1
User
Beiträge: 51
Registriert: Donnerstag 22. Oktober 2020, 18:19

Hi, ich weiß über die API nicht richtig bescheid und meine Python-Kenntnisse sind auch schon eingerostet ...

Ich möchte gerne, dass alle Assets-Positionen zusammengezählt werden, dann durch die Anzahl geteilt wird, und dann zuerst alle Assets verkauft werden, die über dem Durchschnitt sind und dann alle Assets gekauft werden, die unter dem Durchschnitt sind... sodass dann alle Assets den gleichen Betrag haben.

Vielleicht nutzt hier auch jemand diese Library und kennt sich gut damit aus. Hier wäre mein Versuch/Ansatz:

Code: Alles auswählen

from binance import Client
client = Client(
    "",
    "")
info = client.get_account()
bal = info["balances"]
prices = client.get_all_tickers()
assets = []
for b in bal:
    f = float(b["free"])
    if f != 0:
        n = b["asset"]
        if n == "NFT" or n == "ETHW":
            continue
        busd = 1.0 if n == "USDT" else float(
            next(item for item in prices if item["symbol"] == b["asset"]+"BUSD")["price"])
        sum1 = f + float(b["locked"])
        sum2 = busd * sum1
        assets.append({
            "name": n,
            "busd": busd,
            "sum1": sum1,
            "sum2": sum2
        })
assets = sorted(assets, key=lambda d: d["sum2"], reverse=True)
sum3 = 0.0
for a in assets:
    sum3 += a["sum2"]
sum4 = sum3 / (6) # or... len(assets)...
print(sum3, sum4)
for a in assets:
    print(a)
    if a["name"] != "USDT" and a["sum2"] > 10.0:
        if a["sum2"] < sum4:
            print("Convert BUSD to " +
                  a["name"] + " for " + str(sum4 - a["sum2"]) + " $ (Buy)")
        else:
            print("Convert " + a["name"] + " to BUSD for " +
                  str(a["sum2"] - sum4) + " $ (Sell)")
greetings1
User
Beiträge: 51
Registriert: Donnerstag 22. Oktober 2020, 18:19

Bin jetzt einen Schritt weiter, aber vor dem nächsten Problem. :( :

Code: Alles auswählen

from binance.enums import *
from binance import Client
client = Client(
    "",
    "")
info = client.get_account()
balances = info["balances"]
prices = client.get_all_tickers()
assets = []
for b in balances:
    f = float(b["free"])
    if f != 0:
        n = b["asset"]
        if n == "NFT" or n == "ETHW":
            continue
        busd = 1.0 if n == "USDT" else float(
            next(item for item in prices if item["symbol"] == b["asset"]+"BUSD")["price"])
        sum1 = f + float(b["locked"])
        sum2 = round(busd * sum1, 2)
        assets.append({
            "name": n,
            "busd": busd,
            "sum1": sum1,
            "sum2": sum2
        })
assets = sorted(assets, key=lambda d: d["sum2"], reverse=True)
sum3 = sum(d["sum2"] for d in assets)
sum4 = round(sum3 / 6, 2)
print(sum3, sum4)
print(*assets, sep="\n")
assets = filter(lambda d: d["name"] != "USDT" and d["sum2"]
                > 10 and abs(sum4 - d["sum2"]) > 10, assets)
for d in assets:
    print(d)
    if d["sum2"] < sum4:
        print("USDT -> "+d["name"]+" (Buy): " + str(sum4 - d["sum2"]) + " $")
        amount = (sum4 - d["sum2"]) / d["busd"]
        print(amount)
        client.create_test_order(
            symbol=d["name"]+"USDT",
            side=SIDE_BUY,
            type=ORDER_TYPE_MARKET,
            quantity=amount)
    else:
        print(d["name"]+" -> USDT (Sell): " + str(d["sum2"] - sum4) + " $")
        amount = (d["sum2"] - sum4) / d["busd"]
        print(amount)
        client.create_test_order(
            symbol=d["name"]+"USDT",
            side=SIDE_SELL,
            type=ORDER_TYPE_MARKET,
            quantity=amount)

Fehlermeldung:

Code: Alles auswählen

BNB -> USDT (Sell): 164.25000000000003 $
0.47719349215572354
Traceback (most recent call last):
  ...
binance.exceptions.BinanceAPIException: APIError(code=-1111): Precision is over the maximum defined for this asset.
Wie kann ich denn die Präzision festlegen, also sprich 0.47719349215572354 runden?
greetings1
User
Beiträge: 51
Registriert: Donnerstag 22. Oktober 2020, 18:19

Habe es jetzt hinbekommen :) , aber die for-Schleife in Zeile 65 wird nicht mehr ausgeführt... Weiß jemand, warum? Das muss doch ein Py Problem sein, oder mein System ist kaputt:

Code: Alles auswählen

from binance.helpers import round_step_size
from binance.enums import *
from binance import Client


def getStepSize(einfo, symbol):
    for s in einfo["symbols"]:
        if s["symbol"] == symbol:
            for f in s["filters"]:
                if f["filterType"] == "LOT_SIZE":
                    return float(f["stepSize"])
    raise Exception("Symbol on einfo not found.")


client = Client(
    "",
    "")
info = client.get_account()
einfo = client.get_exchange_info()
balances = info["balances"]
prices = client.get_all_tickers()
assets = []
for b in balances:
    f = float(b["free"])
    if f != 0:
        n = b["asset"]
        if n == "NFT" or n == "ETHW":
            continue
        busd = 1.0 if n == "USDT" else float(
            next(p for p in prices if p["symbol"] == n+"BUSD")["price"])
        sum1 = f + float(b["locked"])
        sum2 = round(busd * sum1, 2)
        assets.append({
            "name": n,
            "busd": busd,
            "sum1": sum1,
            "sum2": sum2
        })
assets = sorted(assets, key=lambda d: d["sum2"], reverse=True)
sum3 = sum(d["sum2"] for d in assets)
sum4 = round(sum3 / 6, 2)
print(sum3, sum4)
print(*assets, sep="\n")
assets = filter(lambda d: d["name"] != "USDT" and d["sum2"]
                > 10 and abs(sum4 - d["sum2"]) > 10, assets)
for d in assets:
    print(d)
    symbol = d["name"]+"USDT"
    if d["sum2"] >= sum4:
        print(d["name"]+" -> USDT (Sell): " + str(d["sum2"] - sum4) + " $")
        amount = (d["sum2"] - sum4) / d["busd"]
        print(getStepSize(einfo, symbol))
        amount = round_step_size(amount, getStepSize(einfo, symbol))
        print(amount)
        client.create_test_order(
            symbol=symbol,
            side=SIDE_SELL,
            type=ORDER_TYPE_MARKET,
            quantity=amount)

# to avoid negative balance:
sum3 -= 1.0
sum4 = round(sum3 / 6, 2)
print(sum3, sum4)
for d in assets:  # wird nicht mehr betreten...
    print(d)
    symbol = d["name"]+"USDT"
    if d["sum2"] < sum4:
        print("USDT -> "+d["name"]+" (Buy): " + str(sum4 - d["sum2"]) + " $")
        amount = (sum4 - d["sum2"]) / d["busd"]
        print(getStepSize(einfo, symbol))
        amount = round_step_size(amount, getStepSize(einfo, symbol))
        print(amount)
        client.create_test_order(
            symbol=symbol,
            side=SIDE_BUY,
            type=ORDER_TYPE_MARKET,
            quantity=amount)

Benutzeravatar
__blackjack__
User
Beiträge: 13061
Registriert: Samstag 2. Juni 2018, 10:21
Wohnort: 127.0.0.1
Kontaktdaten:

@greetings1: `filter()` liefert einen Iterator, keine Sequenz. Da kann man nur einmal drüber iterieren, dann ist der “verbraucht“. Der Code ist dort sowieso nicht so besonders weil da viel kopiert und leicht angepasst zu sein scheint.

Die Namen sind teilweise sehr schlecht. Nichtssagend und nummeriert oder gar nur ein Buchstabe. Warum steht beispielsweise `d` für `asset`?

Namen werden in Python klein_mit_unterstrichen geschrieben. Ausnahmen sind Konstanten (KOMPLETT_GROSS) und Klassen (PascalCase). Also `get_step_size()`.

Wörterbücher mit einem festen Satz von Schlüsseln sind eigentlich Objekte. Da würde ich eher ein `collections.namedtuple` für nehmen oder eine Klasse für schreiben.

Das zusammenstückeln von Zeichenketten und Werten mittels ``+`` und `str()` ist eher BASIC als Python. Dafür gibt es die `format()`-Methode auf Zeichenketten und f-Zeichenkettenliterale.
„All religions are the same: religion is basically guilt, with different holidays.” — Cathy Ladman
greetings1
User
Beiträge: 51
Registriert: Donnerstag 22. Oktober 2020, 18:19

Danke für die Info @__blackjack__ !

Kann ich dann einfach = list(filter(...)) schreiben?

Alleine wäre ich nie auf diese Idee gekommen...
greetings1
User
Beiträge: 51
Registriert: Donnerstag 22. Oktober 2020, 18:19

Noch schnell ein Hinweis ...

https://i.postimg.cc/zDnJq4dK/grafik.png

Das finde ich "fehlerhaft" beschrieben ... denn round_step_size erwartet tatsächlich eine step_size , und nicht tick_size ...

Hat lange gedauert, diesen Fehler zu finden.
greetings1
User
Beiträge: 51
Registriert: Donnerstag 22. Oktober 2020, 18:19

Es lief jetzt gerade ohne nennenswerte Vorkommnisse durch :geek: :mrgreen: Danke nochmals

Code: Alles auswählen

from binance.helpers import round_step_size
from binance.enums import *
from binance import Client


def getStepSize(einfo, symbol):
    for s in einfo["symbols"]:
        if s["symbol"] == symbol:
            for f in s["filters"]:
                if f["filterType"] == "LOT_SIZE":
                    return float(f["stepSize"])
    raise Exception("Symbol on einfo not found.")


client = Client(
    "",
    "")
info = client.get_account()
einfo = client.get_exchange_info()
balances = info["balances"]
prices = client.get_all_tickers()
assets = []
for b in balances:
    f = float(b["free"])
    if f != 0:
        n = b["asset"]
        if n == "NFT" or n == "ETHW":
            continue
        busd = 1.0 if n == "USDT" else float(
            next(p for p in prices if p["symbol"] == n+"BUSD")["price"])
        sum1 = f + float(b["locked"])
        sum2 = round(busd * sum1, 2)
        assets.append({
            "name": n,
            "busd": busd,
            "sum1": sum1,
            "sum2": sum2
        })
assets = sorted(assets, key=lambda d: d["sum2"], reverse=True)
sum3 = sum(d["sum2"] for d in assets)
sum4 = round(sum3 / 6, 2)
print(sum3, sum4)
print(*assets, sep="\n")

assets = list(filter(lambda d: d["name"] != "USDT" and d["sum2"] > 10 and abs(sum4 - d["sum2"]) > 10, assets))
for d in assets:
    symbol = d["name"]+"USDT"
    if d["sum2"] >= sum4:
        print(d)
        print(d["name"]+" -> USDT (Sell): " + str(d["sum2"] - sum4) + " $")
        amount = (d["sum2"] - sum4) / d["busd"]
        amount = round_step_size(amount, getStepSize(einfo, symbol))
        print(client.create_order(
            symbol=symbol,
            side=SIDE_SELL,
            type=ORDER_TYPE_MARKET,
            quantity=amount))

# to avoid negative balance:
sum3 -= 5
sum4 = round(sum3 / 6, 2)
print(sum3, sum4)
for d in assets:
    symbol = d["name"]+"USDT"
    if d["sum2"] < sum4:
        print(d)
        print("USDT -> "+d["name"]+" (Buy): " + str(sum4 - d["sum2"]) + " $")
        amount = (sum4 - d["sum2"]) / d["busd"]
        amount = round_step_size(amount, getStepSize(einfo, symbol))
        print(client.create_order(
            symbol=symbol,
            side=SIDE_BUY,
            type=ORDER_TYPE_MARKET,
            quantity=amount))

greetings1
User
Beiträge: 51
Registriert: Donnerstag 22. Oktober 2020, 18:19

Ich hab nun etwas aufgeräumt und deine Ratschläge beherzigt ... Geht das denn jetzt so?(1)

Code: Alles auswählen

import time
from binance.helpers import round_step_size
from binance.enums import *
from binance import Client


def getStepSize(einfo, symbol):
    for s in einfo["symbols"]:
        if s["symbol"] == symbol:
            for f in s["filters"]:
                if f["filterType"] == "LOT_SIZE":
                    return float(f["stepSize"])
    raise Exception("Symbol on einfo not found.")


def get_assets(client, prices):
    info = client.get_account()
    balances = info["balances"]
    assets = []
    for b in balances:
        f = float(b["free"])
        if f != 0:
            n = b["asset"]
            if n == "NFT" or n == "ETHW":
                continue
            usdt = 1.0 if n == "USDT" else float(
                next(p for p in prices if p["symbol"] == n+"USDT")["price"])
            sum1 = f + float(b["locked"])
            sum2 = round(usdt * sum1, 2)
            assets.append({
                "name": n,
                "usdt": usdt,
                "sum1": sum1,
                "sum2": sum2
            })
    return assets


def sell_overboughts(to_sell_list, sum4, client):
    for d in to_sell_list:
        symbol = d["name"]+"USDT"
        sum = abs(sum4 - d["sum2"])
        print(f"Sell {symbol} for {sum} $")
        if sum > 10:
            amount = round_step_size(sum / d["usdt"], getStepSize(einfo, symbol))
            print(client.create_test_order(
                symbol=symbol,
                side=SIDE_SELL,
                type=ORDER_TYPE_MARKET,
                quantity=amount))
        else:
            amount = round_step_size(20 / d["usdt"], getStepSize(einfo, symbol))
            print(client.create_test_order(
                symbol=symbol,
                side=SIDE_SELL,
                type=ORDER_TYPE_MARKET,
                quantity=amount))
            amount = round_step_size((20 - sum) / d["usdt"], getStepSize(einfo, symbol))
            print(client.create_test_order(
                symbol=symbol,
                side=SIDE_BUY,
                type=ORDER_TYPE_MARKET,
                quantity=amount))


def buy_oversolds(to_buy_list, sum4, client):
    for d in to_buy_list:
        symbol = d["name"]+"USDT"
        sum = abs(sum4 - d["sum2"])
        print(f"Buy {symbol} for {sum} $")
        if sum > 10:
            amount = round_step_size(sum / d["usdt"], getStepSize(einfo, symbol))
            print(client.create_test_order(
                symbol=symbol,
                side=SIDE_BUY,
                type=ORDER_TYPE_MARKET,
                quantity=amount))
        else:
            amount = round_step_size(12 / d["usdt"], getStepSize(einfo, symbol))
            print(client.create_test_order(
                symbol=symbol,
                side=SIDE_SELL,
                type=ORDER_TYPE_MARKET,
                quantity=amount))
            amount = round_step_size((12 + sum) / d["usdt"], getStepSize(einfo, symbol))
            print(client.create_test_order(
                symbol=symbol,
                side=SIDE_BUY,
                type=ORDER_TYPE_MARKET,
                quantity=amount))


client = Client(
    "",
    "")

einfo = client.get_exchange_info()
prices = client.get_all_tickers()

assets = get_assets(client, prices)
assets = sorted(assets, key=lambda d: d["sum2"], reverse=True)

sum3 = sum(d["sum2"] for d in assets)
sum4 = round(sum3 / 6, 2) - 0.5

print(*assets, sep="\n")
print(sum3, sum4)

to_sell_list = list(filter(lambda d: d["name"] != "USDT" and d["sum2"] > 10 and d["sum2"] >= sum4 and abs(sum4 - d["sum2"]) > 1, assets))
to_buy_list = list(filter(lambda d: d["name"] != "USDT" and d["sum2"] > 10 and d["sum2"] < sum4 and abs(sum4 - d["sum2"]) > 1, assets))

sell_overboughts(to_sell_list, sum4, client)
time.sleep(3)  # Sleep for 3 seconds
buy_oversolds(to_buy_list, sum4, client)

Kann ich in Python3 eine "Präprozessordirektive" verwenden (einen Schalter), um einfach zwischen "create_test_order" und "create_order" zu wechseln?

(1): Damit sei gemeint, könnte man den Code nach einem halben Jahr oder nach 5 Jahren noch einfach verstehen?
Benutzeravatar
Dennis89
User
Beiträge: 1152
Registriert: Freitag 11. Dezember 2020, 15:13

Hallo,

Namen schreibt man klein_mit_unterstrich. Ausnahmen sind Konstanten, die schreibt man GANZ_GROSS und Klassen, die schreibt man in Pascal-Schreibweise.

Auf Modulebene, der Code ohne Einrückungen, darf kein ausführbarer Code stehen. Ausnahme ist die 'if'-Abfrage, die der Einstiegspunkt in die 'main'-Funktion ist.

Nach ein paar Monaten würde ich mich fragen was 'einfo' und die durchnummerierten Summen sein sollen und ich würde mich auch wundern, wieso hinter 'sum4' keine Summe berechnet wird und wo die '6' und die '0.5' herkommt. Für die Zahlen könnte man Konstanten erstellen.

In 'buy_overslds' ist 'd' auch ein schlechter Name. Mit 'sum' überschreibst du die 'sum'-Funktion, die Python schon mit bringt. 'SIDE_BUY', 'ORDER_TYPE_MARKET' kommen aus dem *-Import? Das ist mehr raten wie offensichtlich und kann auch zu Namenskollisionen führen. Deswegen vermeidet man *-Importe und importiert das was man braucht.
'sell_overboughts' und 'buy_oversolds' beinhalten beide sehr viel gleichen Code, den würde man eher auslagern.
In 'get_assets' würde ich das mit den einbuchstabigen Abkürzungen auch nicht mehr blicken. Anstatt 'or' könntest du auch fragen ob 'n' in ["NFT, ETHW"] ist.
'continue' macht das lesen schwerer, die Abfrage würde ich ändern, damit die Schleife nur durchläuft wenn die Abfrage mit 'n' False gibt und ansonsten machst du einfach nichts.

Also um deine Frage zu beantworten, ich denke nicht, dass der Code noch einfach zu lesen ist.
Das mit den aussagekräftigen Namen ist sehr wichtig. Mir fällt das öfters auch schwer gute Namen zu finden und ich ändere die auch während des programmieren ab und an, wenn mir was besseres einfällt.

Grüße
Dennis
"When I got the music, I got a place to go" [Rancid, 1993]
Antworten