Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Cronjobs updates #503

Merged
merged 9 commits into from
Oct 8, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions .github/dependabot.yml
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,9 @@ updates:
commit-message:
# Prefix all commit messages with "npm"
prefix: "npm"
ignore:
- dependency-name: "*"
update-types: ["version-update:semver-minor", "version-update:semver-patch"]

- package-ecosystem: "pip"
directory: "/"
Expand All @@ -18,3 +21,6 @@ updates:
prefix: "pip prod"
prefix-development: "pip dev"
include: "scope"
ignore:
- dependency-name: "*"
update-types: ["version-update:semver-minor", "version-update:semver-patch"]
1 change: 0 additions & 1 deletion .github/workflows/pr.yml
Original file line number Diff line number Diff line change
Expand Up @@ -86,7 +86,6 @@ jobs:
-e MONGO_AUTH_PASSWORD=${{ env.MONGO_AUTH_PASSWORD }} \
-e PYTHONUNBUFFERED=TRUE \
-e ENV=ci -d binbot_streaming
docker logs binbot_streaming
- name: Tag image
if: ${{ github.actor != 'dependabot[bot]' }}
run: |
Expand Down
108 changes: 103 additions & 5 deletions api/account/assets.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,9 @@
from charts.models import CandlestickParams
from charts.models import Candlestick
from db import setup_db
from tools.handle_error import InvalidSymbol, json_response, json_response_message
from tools.round_numbers import round_numbers
from tools.handle_error import InvalidSymbol, json_response, json_response_error, json_response_message
from tools.round_numbers import round_numbers, round_numbers_ceiling, supress_notation
from binance.exceptions import BinanceAPIException


class Assets(Account):
Expand Down Expand Up @@ -247,7 +248,7 @@ async def retrieve_gainers_losers(self, market_asset="USDT"):
)

def match_series_dates(
self, dates, balance_date, i: int = 0, count=0
self, dates, balance_date, i: int = 0
) -> int | None:
if i == len(dates):
return None
Expand Down Expand Up @@ -298,7 +299,7 @@ async def get_balance_series(self, end_date, start_date):
lte_tp_id = ObjectId.from_datetime(obj_end_date)
params["_id"]["$lte"] = lte_tp_id

balance_series = list(self.db.balances.find(params).sort([("_id", -1)]))
balance_series = list(self.db.balances.find(params).sort([("time", -1)]))

# btc candlestick data series
params = CandlestickParams(
Expand All @@ -314,7 +315,6 @@ async def get_balance_series(self, end_date, start_date):
balances_series_diff = []
balances_series_dates = []
balance_btc_diff = []
balance_series.sort(key=lambda item: item["_id"], reverse=False)

for index, item in enumerate(balance_series):
btc_index = self.match_series_dates(dates, item["time"], index)
Expand All @@ -337,3 +337,101 @@ async def get_balance_series(self, end_date, start_date):
}
)
return resp

async def clean_balance_assets(self):
"""
Check if there are many small assets (0.000.. BTC)
if there are more than 5 (number of bots)
transfer to BNB
"""
data = self.signed_request(url=self.account_url)
assets = []
for item in data["balances"]:
if item["asset"] not in ["USDT", "NFT", "BNB"] and float(item["free"]) > 0:
assets.append(item["asset"])

if len(assets) > 5:
self.transfer_dust(assets)
resp = json_response_message("Sucessfully cleaned balance.")
else:
resp = json_response_error("Amount of assets in balance is low. Transfer not needed.")

return resp

async def disable_isolated_accounts(self):
"""
Check and disable isolated accounts
"""
info = self.signed_request(url=self.isolated_account_url, payload={})
for item in info["assets"]:
if float(item["liquidatePrice"]) == 0:
self.disable_isolated_margin_account(item["symbol"])
msg = "Sucessfully finished disabling isolated margin accounts."
else:
msg = "Disabling isolated margin account not required yet."

resp = json_response_message(msg)
return resp

def one_click_liquidation(self, asset, balance="USDT", json=True):
"""
Emulate Binance Dashboard
One click liquidation function
"""
pair = asset + balance
isolated_balance = self.get_isolated_balance(pair)
# Check margin account balance first
balance = float(isolated_balance[0]["quoteAsset"]["free"])
qty_precision = self.get_qty_precision(pair)
if balance > 0:
# repay
repay_amount = float(
isolated_balance[0]["baseAsset"]["borrowed"]
) + float(isolated_balance[0]["baseAsset"]["interest"])
# Check if there is a loan
# Binance may reject loans if they don't have asset
# or binbot errors may transfer funds but no loan is created
query_loan = self.signed_request(
url=self.loan_record_url,
payload={"asset": asset, "isolatedSymbol": pair},
)
if query_loan["total"] > 0 and repay_amount > 0:
# Only supress trailling 0s, so that everything is paid
repay_amount = round_numbers_ceiling(repay_amount, qty_precision)
try:
self.repay_margin_loan(
asset=asset,
symbol=pair,
amount=repay_amount,
isIsolated="TRUE",
)
# get new balance
isolated_balance = self.get_isolated_balance(pair)
print(f"Transfering leftover isolated assets back to Spot")
if float(isolated_balance[0]["quoteAsset"]["free"]) != 0:
# transfer back to SPOT account
self.transfer_isolated_margin_to_spot(
asset=asset,
symbol=pair,
amount=isolated_balance[0]["quoteAsset"]["free"],
)
if float(isolated_balance[0]["baseAsset"]["free"]) != 0:
self.transfer_isolated_margin_to_spot(
asset=asset,
symbol=pair,
amount=isolated_balance[0]["baseAsset"]["free"],
)

self.disable_isolated_margin_account(symbol=pair)
return json_response_message(f"Successfully liquidated {pair}")

except BinanceAPIException as error:
if error.code == -3041:
# Most likely not enough funds to pay back
# Get fiat (USDT) to pay back
return json_response_error(error.message)
if error.code == -3015:
# false alarm
pass
if error.code == -3051:
return json_response_error(error.message)
12 changes: 12 additions & 0 deletions api/account/routes.py
Original file line number Diff line number Diff line change
Expand Up @@ -79,3 +79,15 @@ async def get_balance_series():
today = datetime.now()
month_ago = today - timedelta(30)
return await Assets().get_balance_series(start_date=datetime.timestamp(month_ago), end_date=datetime.timestamp(today))

@account_blueprint.get("/clean", response_model=BalanceSeriesResponse, tags=["assets"])
async def clean_balance():
return await Assets().clean_balance_assets()

@account_blueprint.get("/disable-isolated", response_model=BalanceSeriesResponse, tags=["assets"])
async def disable_isolated():
return await Assets().disable_isolated_accounts()

@account_blueprint.get("/one-click-liquidation/{asset}", response_model=BalanceSeriesResponse, tags=["assets"])
def one_click_liquidation(asset):
return Assets().one_click_liquidation(asset, json=True)
11 changes: 9 additions & 2 deletions api/apis.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
from typing import List
import hashlib
import hmac
import os
Expand All @@ -10,8 +11,6 @@
class BinanceApi:
"""
Binance API URLs

To test:
https://binance.github.io/binance-api-swagger/
"""

Expand Down Expand Up @@ -149,6 +148,14 @@ def get_isolated_balance_total(self):
raise IsolateBalanceError("Hit symbol 24hr restriction or not available (requires transfer in)")
return assets

def transfer_dust(self, assets: List[str]):
"""
Transform small balances to BNB
"""
list_assets = ",".join(assets)
response = self.signed_request(url=self.dust_transfer_url, method="POST", payload={"asset": list_assets})
return response

class BinbotApi(BinanceApi):
"""
API endpoints on this project itself
Expand Down
23 changes: 9 additions & 14 deletions api/deals/margin.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,8 @@
class MarginShortError(Exception):
pass

# To be removed one day when commission endpoint found that provides this value
ESTIMATED_COMMISSIONS_RATE = 0.0075

class MarginDeal(BaseDeal):
def __init__(self, bot, db_collection_name: str) -> None:
Expand Down Expand Up @@ -77,13 +79,12 @@ def compute_margin_buy_back(
):
return None

qty = float(self.isolated_balance[0]["baseAsset"]["borrowed"]) + float(
self.isolated_balance[0]["baseAsset"]["interest"]
)
qty = float(self.isolated_balance[0]["baseAsset"]["borrowed"]) + float(self.isolated_balance[0]["baseAsset"]["interest"]) + float(self.isolated_balance[0]["baseAsset"]["borrowed"]) * ESTIMATED_COMMISSIONS_RATE
qty = round_numbers_ceiling(qty, self.qty_precision)

return round_numbers_ceiling(qty, self.qty_precision), float(
self.isolated_balance[0]["baseAsset"]["free"]
)
free = float(self.isolated_balance[0]["baseAsset"]["free"])

return qty, free

def get_remaining_assets(self) -> tuple[float, float]:
"""
Expand Down Expand Up @@ -310,14 +311,12 @@ def retry_repayment(self, query_loan, buy_back_fiat):
self.save_bot_streaming()
self.terminate_margin_short(buy_back_fiat)
except Exception as error:
print(error)
try:
self.transfer_spot_to_isolated_margin(
asset=self.active_bot.balance_to_use,
symbol=self.active_bot.pair,
amount=total_base_qty,
)
self.retry_repayment(query_loan, buy_back_fiat)
except Exception as error:
print(error)
self._append_errors(
Expand Down Expand Up @@ -377,7 +376,7 @@ def terminate_margin_short(self, buy_back_fiat: bool = True):
pass
except BinanceErrors as error:
if error.code == -3041:
self.retry_repayment(query_loan, buy_back_fiat)
self.active_bot.errors.append(error.message)
pass
except Exception as error:
self.update_deal_logs(error)
Expand Down Expand Up @@ -706,11 +705,7 @@ def execute_stop_loss(self):
try:
quote, base = self.get_remaining_assets()
price = self.matching_engine(self.active_bot.pair, True, qty)
# No need to round?
# qty = round_numbers(
# float(quote) / float(price), self.qty_precision
# )
# If still qty = 0, it means everything is clear

if qty == 0:
return

Expand Down
5 changes: 2 additions & 3 deletions api/deals/spot.py
Original file line number Diff line number Diff line change
Expand Up @@ -29,11 +29,10 @@ def __init__(self, bot, db_collection_name: str) -> None:

def switch_margin_short(self, close_price):
msg = "Resetting bot for margin_short strategy..."
print(msg)
self.update_deal_logs(msg)
self.save_bot_streaming()

margin_deal = MarginDeal(self.active_bot, db_collection_name="bots")
margin_deal.margin_short_base_order()
self.active_bot = MarginDeal(self.active_bot, db_collection_name="bots").margin_short_base_order()
self.save_bot_streaming()


Expand Down
50 changes: 35 additions & 15 deletions api/market_updates.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
import logging
import time

from apscheduler.schedulers.background import BackgroundScheduler
from apscheduler.schedulers.background import BlockingScheduler
from streaming.streaming_controller import StreamingController
from account.assets import Assets
from websocket import (
Expand All @@ -12,7 +12,6 @@
)



logging.Formatter.converter = time.gmtime # date time in GMT/UTC
logging.basicConfig(
level=logging.INFO,
Expand All @@ -22,34 +21,55 @@
)

if os.getenv("ENV") != "ci":
scheduler = BackgroundScheduler()

scheduler = BlockingScheduler()
assets = Assets()

assets.store_balance()
# scheduler.add_job(
# func=assets.store_balance,
# trigger="cron",
# timezone="Europe/London",
# hour=1,
# minute=0,
# id='store_balance'
# )
# scheduler.start()
# atexit.register(lambda: scheduler.shutdown(wait=False))
scheduler.add_job(
func=assets.store_balance,
trigger="cron",
timezone="Europe/London",
hour=1,
minute=1,
id="store_balance",
)

scheduler.add_job(
func=assets.disable_isolated_accounts,
trigger="cron",
timezone="Europe/London",
hour=2,
minute=1,
id="disable_isolated_accounts",
)

scheduler.add_job(
func=assets.clean_balance_assets,
trigger="cron",
timezone="Europe/London",
hour=3,
minute=1,
id="clean_balance_assets",
)

try:
mu = StreamingController()
mu.get_klines()

scheduler.start()

except WebSocketException as e:
if isinstance(e, WebSocketConnectionClosedException):
logging.error("Lost websocket connection")
mu = StreamingController()
mu.get_klines()
else:
logging.error(f"Websocket exception: {e}")

atexit.register(lambda: scheduler.shutdown(wait=False))

except Exception as error:
logging.error(f"Streaming controller error: {error}")
mu = StreamingController()
mu.get_klines()

atexit.register(lambda: scheduler.shutdown(wait=False))
11 changes: 11 additions & 0 deletions web/src/assets/scss/paper-dashboard/_misc.scss
Original file line number Diff line number Diff line change
Expand Up @@ -173,4 +173,15 @@ a[data-toggle="collapse"][aria-expanded="true"] .caret,

.redux-toastr {
position: absolute;
}

// Dashboard grid
#dashboard-grid {
display: grid;
column-gap: 1rem;
grid-template-columns: repeat(2, 3fr);

@media screen and (min-width: 991px) {
grid-template-columns: 1fr;
}
}
Loading
Loading