From f5f43be1a15bf019b88f8593a1597aef94c1f48c Mon Sep 17 00:00:00 2001 From: stevekeyharvey Date: Fri, 8 May 2026 03:02:02 +0000 Subject: [PATCH] moving to extravm --- algo.ipynb | 725 ++++++++++++++++++++------------------------- main.py | 43 ++- modules/structs.py | 6 +- ng.py | 190 +++++++++--- 4 files changed, 517 insertions(+), 447 deletions(-) diff --git a/algo.ipynb b/algo.ipynb index cbd4882..fc4f260 100644 --- a/algo.ipynb +++ b/algo.ipynb @@ -2,7 +2,7 @@ "cells": [ { "cell_type": "code", - "execution_count": 1, + "execution_count": 10, "id": "d1eed397", "metadata": {}, "outputs": [], @@ -22,7 +22,7 @@ }, { "cell_type": "code", - "execution_count": 2, + "execution_count": 11, "id": "c6151613", "metadata": {}, "outputs": [], @@ -2600,88 +2600,6 @@ "Decimal('1.0').quantize()" ] }, - { - "cell_type": "code", - "execution_count": 16, - "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "1.0" - ] - }, - "execution_count": 16, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "x = 1.0\n", - "x" - ] - }, - { - "cell_type": "code", - "execution_count": 8, - "id": "70b57870", - "metadata": {}, - "outputs": [], - "source": [ - "f = ['b','a','z','e']" - ] - }, - { - "cell_type": "code", - "execution_count": 9, - "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "['b', 'a', 'z', 'e']" - ] - }, - "execution_count": 9, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "f" - ] - }, - { - "cell_type": "code", - "execution_count": 10, - "id": "9fd60c6e", - "metadata": {}, - "outputs": [], - "source": [ - "f.sort()" - ] - }, - { - "cell_type": "code", - "execution_count": 11, - "id": "cd8b41de", - "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "['a', 'b', 'e', 'z']" - ] - }, - "execution_count": 11, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "f" - ] - }, { "cell_type": "code", "execution_count": 46, @@ -2699,125 +2617,42 @@ { "cell_type": "code", "execution_count": null, - "id": "57fac02c", "metadata": {}, "outputs": [], - "source": [ - "### Locked Values 🔒🔑 ###" - ] + "source": [] }, { "cell_type": "code", - "execution_count": 124, - "id": "8c2d003f", + "execution_count": 13, "metadata": {}, "outputs": [], "source": [ - "from sqlalchemy.util.typing import Self\n", - "from typing import Any\n", - "from collections.abc import Sequence, Callable\n", - "\n", - "class Locked_Value(Sequence):\n", - " def __init__(self, initial_value: Any, unlock_func: Callable):\n", - " self._value: Any = initial_value\n", - " self._unlock_func: Callable = unlock_func\n", - " self._is_locked: bool = True\n", - "\n", - " def __repr__(self):\n", - " return str((self._value, self._is_locked, self._unlock_func))\n", - "\n", - " def __len__(self):\n", - " return len((self._value, self._is_locked, self._unlock_func))\n", - "\n", - " def __getitem__(self, index):\n", - " return (self._value, self._is_locked, self._unlock_func)[index]\n", - "\n", - " def __str__(self):\n", - " return str((self._value))\n", - "\n", - " def unlock(self) -> Self:\n", - " if self._unlock_func():\n", - " self._is_locked = False\n", - " return self\n", - "\n", - " @property\n", - " def is_locked(self):\n", - " return self._is_locked\n", - "\n", - " @property\n", - " def value(self):\n", - " return self._value\n", - "\n", - " @value.setter\n", - " def value(self, v):\n", - " if not(self._is_locked):\n", - " self._value = v\n", - " else:\n", - " raise ValueError(f'Failed to set value, item is locked: {str(self.__repr__)}')\n" - ] - }, - { - "cell_type": "code", - "execution_count": 155, - "id": "6fd6a46c", - "metadata": {}, - "outputs": [], - "source": [ - "class Current_Previous_Value:\n", - " def __init__(self, value: Any = None, previous_value: Any = None):\n", - " self._value: Any = value\n", - " self._previous_value: Any = previous_value\n", - "\n", - " def __repr__(self):\n", - " return str((self._value, self._previous_value))\n", - "\n", - " def __len__(self):\n", - " return len((self._value, self._previous_value))\n", - "\n", - " def __getitem__(self, index):\n", - " return (self._value, self._previous_value)[index]\n", - "\n", - " def __str__(self):\n", - " return str(self._value)\n", - "\n", - " @property\n", - " def value(self):\n", - " return self._value\n", - "\n", - " @property\n", - " def previous_value(self):\n", - " return self._previous_value\n", - "\n", - " @value.setter\n", - " def value(self, v):\n", - " self._previous_value = self._value\n", - " self._value = v\n" - ] - }, - { - "cell_type": "code", - "execution_count": 156, - "metadata": {}, - "outputs": [], - "source": [ - "vo = Current_Previous_Value()" - ] - }, - { - "cell_type": "code", - "execution_count": 163, - "id": "b557d932", - "metadata": {}, - "outputs": [], - "source": [ - "vo.value = 1\n", - "vo.value = 2" + "df = pd.DataFrame(json.loads(VAL_KEY.get('fr_engine_best_fund_rate_master'))) # ty:ignore[invalid-argument-type]" ] }, { "cell_type": "code", "execution_count": null, - "id": "ef1d4ce0", + "id": "d1413506", + "metadata": {}, + "outputs": [], + "source": [ + "\n", + "\n", + "col_extras = {\n", + " 'symbol_ast': {\n", + " 'editable': False, \n", + " 'sortable': True\n", + " }\n", + "}\n", + "cols = [ {'field': v, **col_extras.get(v, {})} for v in df.columns ]\n", + "rows = df.to_dict(orient='records')" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "fcad79c3", "metadata": {}, "outputs": [], "source": [] @@ -2825,142 +2660,318 @@ { "cell_type": "code", "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [] - }, - { - "cell_type": "code", - "execution_count": 131, - "id": "04efe6cc", - "metadata": {}, - "outputs": [], - "source": [ - "v = 1" - ] - }, - { - "cell_type": "code", - "execution_count": 132, - "id": "36b56800", + "id": "d6f23c60", "metadata": {}, "outputs": [ { "data": { "text/plain": [ - "1" + "[{'symbol_ast': 'LITUSDT',\n", + " 'max_leverage_ast': 50,\n", + " 'lh_asset_ast': 'LIT',\n", + " 'rh_asset_ast': 'USDT',\n", + " 'funding_rate_ast': 0.00011009,\n", + " 'min_price_ast': '0.0001000',\n", + " 'min_order_size_ast': '1',\n", + " 'min_lot_size_ast': '1',\n", + " 'min_notional_ast': '5',\n", + " 'buy_ratio_ast': -0.0005956543,\n", + " 'symbol_ext': 'LIT-USD',\n", + " 'max_leverage_ext': 25,\n", + " 'lh_asset_ext': 'LIT',\n", + " 'rh_asset_ext': 'USD',\n", + " 'funding_rate_ext': 1.3e-05,\n", + " 'min_price_ext': '0.0001',\n", + " 'min_order_size_ext': '10',\n", + " 'min_lot_size_ext': '1',\n", + " 'min_notional_ext': 0.0,\n", + " 'buy_ratio_ext': 0.0005956543,\n", + " 'buy_ratio_std': 0.0007698555,\n", + " 'next_funding_at_same_time': True,\n", + " 'id': 0},\n", + " {'symbol_ast': 'HYPEUSDT',\n", + " 'max_leverage_ast': 300,\n", + " 'lh_asset_ast': 'HYPE',\n", + " 'rh_asset_ast': 'USDT',\n", + " 'funding_rate_ast': 5e-05,\n", + " 'min_price_ast': '0.00100',\n", + " 'min_order_size_ast': '0.01',\n", + " 'min_lot_size_ast': '0.01',\n", + " 'min_notional_ast': '5',\n", + " 'buy_ratio_ast': -0.0002001154,\n", + " 'symbol_ext': 'HYPE-USD',\n", + " 'max_leverage_ext': 50,\n", + " 'lh_asset_ext': 'HYPE',\n", + " 'rh_asset_ext': 'USD',\n", + " 'funding_rate_ext': 1.3e-05,\n", + " 'min_price_ext': '0.001',\n", + " 'min_order_size_ext': '0.1',\n", + " 'min_lot_size_ext': '0.01',\n", + " 'min_notional_ext': 0.0,\n", + " 'buy_ratio_ext': 0.0002001154,\n", + " 'buy_ratio_std': 0.0002123501,\n", + " 'next_funding_at_same_time': False,\n", + " 'id': 1},\n", + " {'symbol_ast': 'SOLUSDT',\n", + " 'max_leverage_ast': 100,\n", + " 'lh_asset_ast': 'SOL',\n", + " 'rh_asset_ast': 'USDT',\n", + " 'funding_rate_ast': 0.0001,\n", + " 'min_price_ast': '0.4200',\n", + " 'min_order_size_ast': '0.01',\n", + " 'min_lot_size_ast': '0.01',\n", + " 'min_notional_ast': '5',\n", + " 'buy_ratio_ast': -0.0004271435,\n", + " 'symbol_ext': 'SOL-USD',\n", + " 'max_leverage_ext': 50,\n", + " 'lh_asset_ext': 'SOL',\n", + " 'rh_asset_ext': 'USD',\n", + " 'funding_rate_ext': -1.4e-05,\n", + " 'min_price_ext': '0.01',\n", + " 'min_order_size_ext': '0.1',\n", + " 'min_lot_size_ext': '0.01',\n", + " 'min_notional_ext': 0.0,\n", + " 'buy_ratio_ext': 0.0004271435,\n", + " 'buy_ratio_std': 9.37564e-05,\n", + " 'next_funding_at_same_time': False,\n", + " 'id': 2},\n", + " {'symbol_ast': 'BNBUSDT',\n", + " 'max_leverage_ast': 100,\n", + " 'lh_asset_ast': 'BNB',\n", + " 'rh_asset_ast': 'USDT',\n", + " 'funding_rate_ast': 0.00012242,\n", + " 'min_price_ast': '0.010',\n", + " 'min_order_size_ast': '0.01',\n", + " 'min_lot_size_ast': '0.01',\n", + " 'min_notional_ast': '5',\n", + " 'buy_ratio_ast': -0.0011992245,\n", + " 'symbol_ext': 'BNB-USD',\n", + " 'max_leverage_ext': 50,\n", + " 'lh_asset_ext': 'BNB',\n", + " 'rh_asset_ext': 'USD',\n", + " 'funding_rate_ext': 1.3e-05,\n", + " 'min_price_ext': '0.01',\n", + " 'min_order_size_ext': '0.01',\n", + " 'min_lot_size_ext': '0.001',\n", + " 'min_notional_ext': 0.0,\n", + " 'buy_ratio_ext': 0.0011992245,\n", + " 'buy_ratio_std': 5.93637e-05,\n", + " 'next_funding_at_same_time': False,\n", + " 'id': 3},\n", + " {'symbol_ast': 'XRPUSDT',\n", + " 'max_leverage_ast': 100,\n", + " 'lh_asset_ast': 'XRP',\n", + " 'rh_asset_ast': 'USDT',\n", + " 'funding_rate_ast': 4.038e-05,\n", + " 'min_price_ast': '0.0143',\n", + " 'min_order_size_ast': '0.1',\n", + " 'min_lot_size_ast': '0.1',\n", + " 'min_notional_ast': '5',\n", + " 'buy_ratio_ast': -0.0001086996,\n", + " 'symbol_ext': 'XRP-USD',\n", + " 'max_leverage_ext': 50,\n", + " 'lh_asset_ext': 'XRP',\n", + " 'rh_asset_ext': 'USD',\n", + " 'funding_rate_ext': 1.3e-05,\n", + " 'min_price_ext': '0.0001',\n", + " 'min_order_size_ext': '10',\n", + " 'min_lot_size_ext': '1',\n", + " 'min_notional_ext': 0.0,\n", + " 'buy_ratio_ext': 0.0001086996,\n", + " 'buy_ratio_std': 8.12797e-05,\n", + " 'next_funding_at_same_time': False,\n", + " 'id': 4},\n", + " {'symbol_ast': 'DOGEUSDT',\n", + " 'max_leverage_ast': 75,\n", + " 'lh_asset_ast': 'DOGE',\n", + " 'rh_asset_ast': 'USDT',\n", + " 'funding_rate_ast': -5.33e-06,\n", + " 'min_price_ast': '0.002440',\n", + " 'min_order_size_ast': '1',\n", + " 'min_lot_size_ast': '1',\n", + " 'min_notional_ast': '5',\n", + " 'buy_ratio_ast': 0.0010192971,\n", + " 'symbol_ext': 'DOGE-USD',\n", + " 'max_leverage_ext': 50,\n", + " 'lh_asset_ext': 'DOGE',\n", + " 'rh_asset_ext': 'USD',\n", + " 'funding_rate_ext': 1.3e-05,\n", + " 'min_price_ext': '0.00001',\n", + " 'min_order_size_ext': '100',\n", + " 'min_lot_size_ext': '10',\n", + " 'min_notional_ext': 0.0,\n", + " 'buy_ratio_ext': -0.0010192971,\n", + " 'buy_ratio_std': 8.60126e-05,\n", + " 'next_funding_at_same_time': False,\n", + " 'id': 5},\n", + " {'symbol_ast': 'SUIUSDT',\n", + " 'max_leverage_ast': 75,\n", + " 'lh_asset_ast': 'SUI',\n", + " 'rh_asset_ast': 'USDT',\n", + " 'funding_rate_ast': 0.0001,\n", + " 'min_price_ast': '0.000100',\n", + " 'min_order_size_ast': '0.1',\n", + " 'min_lot_size_ast': '0.1',\n", + " 'min_notional_ast': '5',\n", + " 'buy_ratio_ast': 0.0002862497,\n", + " 'symbol_ext': 'SUI-USD',\n", + " 'max_leverage_ext': 50,\n", + " 'lh_asset_ext': 'SUI',\n", + " 'rh_asset_ext': 'USD',\n", + " 'funding_rate_ext': 1.3e-05,\n", + " 'min_price_ext': '0.0001',\n", + " 'min_order_size_ext': '10',\n", + " 'min_lot_size_ext': '1',\n", + " 'min_notional_ext': 0.0,\n", + " 'buy_ratio_ext': -0.0002862497,\n", + " 'buy_ratio_std': 0.0005447464,\n", + " 'next_funding_at_same_time': False,\n", + " 'id': 6},\n", + " {'symbol_ast': 'BTCUSDT',\n", + " 'max_leverage_ast': 150,\n", + " 'lh_asset_ast': 'BTC',\n", + " 'rh_asset_ast': 'USDT',\n", + " 'funding_rate_ast': -5.216e-05,\n", + " 'min_price_ast': '1',\n", + " 'min_order_size_ast': '0.001',\n", + " 'min_lot_size_ast': '0.001',\n", + " 'min_notional_ast': '5',\n", + " 'buy_ratio_ast': -0.000104008,\n", + " 'symbol_ext': 'BTC-USD',\n", + " 'max_leverage_ext': 50,\n", + " 'lh_asset_ext': 'BTC',\n", + " 'rh_asset_ext': 'USD',\n", + " 'funding_rate_ext': -8e-06,\n", + " 'min_price_ext': '1',\n", + " 'min_order_size_ext': '0.0001',\n", + " 'min_lot_size_ext': '0.00001',\n", + " 'min_notional_ext': 0.0,\n", + " 'buy_ratio_ext': 0.000104008,\n", + " 'buy_ratio_std': 0.0001018788,\n", + " 'next_funding_at_same_time': False,\n", + " 'id': 7},\n", + " {'symbol_ast': 'CHIPUSDT',\n", + " 'max_leverage_ast': 50,\n", + " 'lh_asset_ast': 'CHIP',\n", + " 'rh_asset_ast': 'USDT',\n", + " 'funding_rate_ast': 1.25e-05,\n", + " 'min_price_ast': '0.0000100',\n", + " 'min_order_size_ast': '1',\n", + " 'min_lot_size_ast': '1',\n", + " 'min_notional_ast': '5',\n", + " 'buy_ratio_ast': -0.0007168459,\n", + " 'symbol_ext': 'CHIP-USD',\n", + " 'max_leverage_ext': 5,\n", + " 'lh_asset_ext': 'CHIP',\n", + " 'rh_asset_ext': 'USD',\n", + " 'funding_rate_ext': -4.6e-05,\n", + " 'min_price_ext': '0.000001',\n", + " 'min_order_size_ext': '100',\n", + " 'min_lot_size_ext': '10',\n", + " 'min_notional_ext': 0.0,\n", + " 'buy_ratio_ext': 0.0007168459,\n", + " 'buy_ratio_std': 0.0014437729,\n", + " 'next_funding_at_same_time': True,\n", + " 'id': 8},\n", + " {'symbol_ast': 'ASTERUSDT',\n", + " 'max_leverage_ast': 75,\n", + " 'lh_asset_ast': 'ASTER',\n", + " 'rh_asset_ast': 'USDT',\n", + " 'funding_rate_ast': 5e-05,\n", + " 'min_price_ast': '0.00010',\n", + " 'min_order_size_ast': '0.01',\n", + " 'min_lot_size_ast': '0.01',\n", + " 'min_notional_ast': '5',\n", + " 'buy_ratio_ast': -0.0001497055,\n", + " 'symbol_ext': 'ASTER-USD',\n", + " 'max_leverage_ext': 25,\n", + " 'lh_asset_ext': 'ASTER',\n", + " 'rh_asset_ext': 'USD',\n", + " 'funding_rate_ext': 1.3e-05,\n", + " 'min_price_ext': '0.00001',\n", + " 'min_order_size_ext': '10',\n", + " 'min_lot_size_ext': '1',\n", + " 'min_notional_ext': 0.0,\n", + " 'buy_ratio_ext': 0.0001497055,\n", + " 'buy_ratio_std': 0.0001520848,\n", + " 'next_funding_at_same_time': False,\n", + " 'id': 9},\n", + " {'symbol_ast': 'ETHUSDT',\n", + " 'max_leverage_ast': 150,\n", + " 'lh_asset_ast': 'ETH',\n", + " 'rh_asset_ast': 'USDT',\n", + " 'funding_rate_ast': -2.051e-05,\n", + " 'min_price_ast': '0.01',\n", + " 'min_order_size_ast': '0.001',\n", + " 'min_lot_size_ast': '0.001',\n", + " 'min_notional_ast': '5',\n", + " 'buy_ratio_ast': -8.96077e-05,\n", + " 'symbol_ext': 'ETH-USD',\n", + " 'max_leverage_ext': 50,\n", + " 'lh_asset_ext': 'ETH',\n", + " 'rh_asset_ext': 'USD',\n", + " 'funding_rate_ext': 4e-06,\n", + " 'min_price_ext': '0.1',\n", + " 'min_order_size_ext': '0.01',\n", + " 'min_lot_size_ext': '0.001',\n", + " 'min_notional_ext': 0.0,\n", + " 'buy_ratio_ext': 8.96077e-05,\n", + " 'buy_ratio_std': 8.24728e-05,\n", + " 'next_funding_at_same_time': False,\n", + " 'id': 10},\n", + " {'symbol_ast': 'ZECUSDT',\n", + " 'max_leverage_ast': 75,\n", + " 'lh_asset_ast': 'ZEC',\n", + " 'rh_asset_ast': 'USDT',\n", + " 'funding_rate_ast': -1.06e-06,\n", + " 'min_price_ast': '0.0100',\n", + " 'min_order_size_ast': '0.001',\n", + " 'min_lot_size_ast': '0.001',\n", + " 'min_notional_ast': '5',\n", + " 'buy_ratio_ast': 0.000456781,\n", + " 'symbol_ext': 'ZEC-USD',\n", + " 'max_leverage_ext': 10,\n", + " 'lh_asset_ext': 'ZEC',\n", + " 'rh_asset_ext': 'USD',\n", + " 'funding_rate_ext': 1.3e-05,\n", + " 'min_price_ext': '0.001',\n", + " 'min_order_size_ext': '0.1',\n", + " 'min_lot_size_ext': '0.1',\n", + " 'min_notional_ext': 0.0,\n", + " 'buy_ratio_ext': -0.000456781,\n", + " 'buy_ratio_std': 0.0008927931,\n", + " 'next_funding_at_same_time': True,\n", + " 'id': 11},\n", + " {'symbol_ast': 'WLFIUSDT',\n", + " 'max_leverage_ast': 25,\n", + " 'lh_asset_ast': 'WLFI',\n", + " 'rh_asset_ast': 'USDT',\n", + " 'funding_rate_ast': 5e-05,\n", + " 'min_price_ast': '0.0001000',\n", + " 'min_order_size_ast': '1',\n", + " 'min_lot_size_ast': '1',\n", + " 'min_notional_ast': '5',\n", + " 'buy_ratio_ast': 0.0008577801,\n", + " 'symbol_ext': 'WLFI-USD',\n", + " 'max_leverage_ext': 10,\n", + " 'lh_asset_ext': 'WLFI',\n", + " 'rh_asset_ext': 'USD',\n", + " 'funding_rate_ext': 1.3e-05,\n", + " 'min_price_ext': '0.00001',\n", + " 'min_order_size_ext': '100',\n", + " 'min_lot_size_ext': '10',\n", + " 'min_notional_ext': 0.0,\n", + " 'buy_ratio_ext': -0.0008577801,\n", + " 'buy_ratio_std': 0.0006254649,\n", + " 'next_funding_at_same_time': False,\n", + " 'id': 12}]" ] }, - "execution_count": 132, + "execution_count": 34, "metadata": {}, "output_type": "execute_result" } ], - "source": [ - "v" - ] - }, - { - "cell_type": "code", - "execution_count": 133, - "id": "e24ff466", - "metadata": {}, - "outputs": [], - "source": [ - "v = 2" - ] - }, - { - "cell_type": "code", - "execution_count": 134, - "id": "b5ee5afa", - "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "2" - ] - }, - "execution_count": 134, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "v" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "74051e1d", - "metadata": {}, - "outputs": [], - "source": [ - "def test():\n", - " return False" - ] - }, - { - "cell_type": "code", - "execution_count": 128, - "id": "38ee912d", - "metadata": {}, - "outputs": [], - "source": [ - "v = Locked_Value('locked', unlock_func=test)" - ] - }, - { - "cell_type": "code", - "execution_count": 129, - "id": "bb703d87", - "metadata": {}, - "outputs": [ - { - "ename": "ValueError", - "evalue": "Failed to set value, item is locked: )>", - "output_type": "error", - "traceback": [ - "\u001b[31m---------------------------------------------------------------------------\u001b[39m", - "\u001b[31mValueError\u001b[39m Traceback (most recent call last)", - "\u001b[36mCell\u001b[39m\u001b[36m \u001b[39m\u001b[32mIn[129]\u001b[39m\u001b[32m, line 1\u001b[39m\n\u001b[32m----> \u001b[39m\u001b[32m1\u001b[39m v.unlock().value = \u001b[33m'unlocked'\u001b[39m\n", - "\u001b[36mCell\u001b[39m\u001b[36m \u001b[39m\u001b[32mIn[124]\u001b[39m\u001b[32m, line 41\u001b[39m, in \u001b[36mLocked_Value.value\u001b[39m\u001b[34m(self, v)\u001b[39m\n\u001b[32m 37\u001b[39m \u001b[38;5;28;01mdef\u001b[39;00m value(self, v):\n\u001b[32m 38\u001b[39m \u001b[38;5;28;01mif\u001b[39;00m \u001b[38;5;28;01mnot\u001b[39;00m(self._is_locked):\n\u001b[32m 39\u001b[39m self._value = v\n\u001b[32m 40\u001b[39m \u001b[38;5;28;01melse\u001b[39;00m:\n\u001b[32m---> \u001b[39m\u001b[32m41\u001b[39m \u001b[38;5;28;01mraise\u001b[39;00m ValueError(f'Failed to set value, item is locked: {str(self.__repr__)}')\n", - "\u001b[31mValueError\u001b[39m: Failed to set value, item is locked: )>" - ] - } - ], - "source": [ - "v.unlock().value = 'unlocked'" - ] - }, - { - "cell_type": "code", - "execution_count": 123, - "id": "76c44d97", - "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "('locked', True, )" - ] - }, - "execution_count": 123, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "v" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], "source": [] }, { @@ -2973,66 +2984,7 @@ { "cell_type": "code", "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [] - }, - { - "cell_type": "code", - "execution_count": 62, - "metadata": {}, - "outputs": [], - "source": [ - "lv = Locked_Value(initial_value=999)\n", - "# lv.unlock()" - ] - }, - { - "cell_type": "code", - "execution_count": 65, - "id": "76e21865", - "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "2" - ] - }, - "execution_count": 65, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "len(lv)" - ] - }, - { - "cell_type": "code", - "execution_count": 32, - "id": "10258f5b", - "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "(999, False)" - ] - }, - "execution_count": 32, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "lv" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "9270dd8d", + "id": "93323174", "metadata": {}, "outputs": [], "source": [] @@ -3044,29 +2996,6 @@ "outputs": [], "source": [] }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "a938b2e0", - "metadata": {}, - "outputs": [], - "source": [] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "39667bd8", - "metadata": {}, - "outputs": [], - "source": [] - }, { "cell_type": "code", "execution_count": null, diff --git a/main.py b/main.py index 2cd0851..c6e72e8 100644 --- a/main.py +++ b/main.py @@ -1,3 +1,20 @@ +''' +Atwater Trading: Perpetual Futures Funding Rate + +TODO: +- Update switch signal to account for diff in leverage (right now its just pure rate) + - Can do by calculating based on expected alpha in dollar terms and then back over the collateral for percent - be careful fees are correctly accounted for in dollar terms. +- WS Stability + - Make sure every WS has reconnect and can handle failure scenarios + - Create health service (or add to orchestrator) to check that WS outputs are recent and not delayed, kill algo and alert if delayed. +- GUI + - NG basic dashboard to show algo summary, bfr, grid of trades/positions/orders/pnl, graph of ratios, prices, orders, fills, pnl, etc. +- Infra + - Move off AWS to a cheaper option (NYCServers, QuantVPS) + - Shutdown old VPS in NYC + +''' + from x10.utils.http import WrappedApiResponse from x10.perpetual.trading_client.trading_client import PerpetualTradingClient import asyncio @@ -28,6 +45,7 @@ import modules.aster_auth as aster_auth import modules.extended_auth as extend_auth import modules.structs as structs + ### Clients ### EXTEND_CLIENT: PerpetualTradingClient CON: AsyncContextManager @@ -535,6 +553,10 @@ async def handle_order_updates(exch: str, local_open_orders: list[dict], ws_open if order_update_status in ['CANCELLED','CANCELED']: logging.info(f'{exch} ORDER CANCELLED: {order_id}') local_open_orders.pop(idx) + if exch=='ASTER': + Aster.cancel_request_pending = False + else: + Extend.cancel_request_pending = False # utils.send_tg_alert(f'FR_ALGO - {exch} REJECTED ({order_id})') elif order_update_status in ['EXPIRED','REJECTED']: logging.info(f'{exch} ORDER REJECTED or EXPIRED: {order_id}') @@ -542,9 +564,11 @@ async def handle_order_updates(exch: str, local_open_orders: list[dict], ws_open if exch=='ASTER': Aster.just_rejected_count = Aster.just_rejected_count + 1 Config.Config.Price_Worsener_Aster=1 + Aster.cancel_request_pending = False else: Extend.just_rejected_count = Extend.just_rejected_count + 1 # Config.Config.Price_Worsener_Extend=1 + Extend.cancel_request_pending = False if Aster.just_rejected_count > 1 or Extend.just_rejected_count > 1: time.sleep(1) Aster.just_rejected_count = 0 @@ -556,8 +580,10 @@ async def handle_order_updates(exch: str, local_open_orders: list[dict], ws_open if exch=='ASTER': await get_aster_notional_position(resp=ws_pos_updates) Last_Aster_Fill_Time_Ts = datetime.now().timestamp()*1000 + Aster.cancel_request_pending = False else: await get_extend_notional(resp=ws_pos_updates) + Extend.cancel_request_pending = False utils.send_tg_alert(f'FR_ALGO - {exch} PARTIALLY FILLED ({order_id})') elif order_update_status in ['FILLED']: logging.info(f'{exch} ORDER FILLED: {order_id}') @@ -567,8 +593,10 @@ async def handle_order_updates(exch: str, local_open_orders: list[dict], ws_open # await aster_cancel_all_orders() await get_aster_notional_position(resp=ws_pos_updates) Last_Aster_Fill_Time_Ts = datetime.now().timestamp()*1000 + Aster.cancel_request_pending = False else: await extend_cancel_all_orders() + Extend.cancel_request_pending = False await get_extend_notional() utils.send_tg_alert(f'FR_ALGO - {exch} FILLED ({order_id})') else: @@ -796,6 +824,9 @@ async def get_extend_exch_info(symbol_override: str | None = None): ### CANCEL ORDERS ### async def aster_cancel_all_orders(): + global Aster + global Aster_Open_Orders + cancel_all_open_orders = { "url": "/fapi/v3/allOpenOrders", "method": "DELETE", @@ -803,7 +834,12 @@ async def aster_cancel_all_orders(): 'symbol': Aster.symbol, } } - r = await aster_auth.post_authenticated_url(cancel_all_open_orders) + r: dict = await aster_auth.post_authenticated_url(cancel_all_open_orders) # ty:ignore[invalid-assignment] + if r.get('code') == 200 and Aster_Open_Orders: + Aster_Open_Orders.pop(0) + Aster.cancel_request_pending = False + else: + Aster.cancel_request_pending = True logging.info(f'ASTER CANCEL ALL OPEN ORDERS RESP: {r}') async def extend_cancel_all_orders(): @@ -1082,7 +1118,10 @@ async def run_algo(): if not(aster_target.is_orderable(price=price)) and Aster_Open_Orders: logging.info('ASTER HAS NO TAIL BUT OPEN ORDERS - CANCELLING OPEN ORDERS; SKIPPING') skip = True - await aster_cancel_all_orders() + if not Aster.cancel_request_pending: + await aster_cancel_all_orders() + print_summary(use_logging=True) + if aster_target.is_orderable(price=price) and aster_target.base_tail(price=price) == Decimal('0.00'): logging.info(f'ASTER TRYNG TO ORDER 0.00 BASE QTY, SKIPPING: base_qty: {aster_target.base_tail(price=price)}; {aster_target}') skip = True diff --git a/modules/structs.py b/modules/structs.py index e1c84e8..c2b2580 100644 --- a/modules/structs.py +++ b/modules/structs.py @@ -252,11 +252,11 @@ class Perpetual_Exchange: # Collateral_Updates: Collateral # Funding_Rate: Funding_Rate # Markets: Markets_Details + mult: int lh_asset: str rh_asset: str symbol: str = '' symbol_asset_separator: str = '' - mult: int initial_funding_rate: float = 0 fund_rate_at_same_time: bool = False min_price: float = 0 @@ -264,11 +264,13 @@ class Perpetual_Exchange: min_lot_size: float = 0 min_notional: float = 0 buy_ratio: float = 0 + buy_ratio_std: float = 0 + notional_obj: dict = field(default_factory=dict) notional_position: float = 0 unrealized_pnl: float = 0 - buy_ratio_std: float = 0 just_rejected_count: int = 0 + cancel_request_pending: bool = False # async def update(self): # await self.Collateral_Updates.update() diff --git a/ng.py b/ng.py index c181e88..c8bd62c 100644 --- a/ng.py +++ b/ng.py @@ -1,7 +1,8 @@ import os from nicegui import ui, app, html -from sqlalchemy import create_engine +from sqlalchemy import create_engine, text # import requests +import pandas as pd import json # import time # import re @@ -9,6 +10,8 @@ import valkey import asyncio from datetime import datetime from dataclasses import dataclass, field +from sqlalchemy.ext.asyncio import create_async_engine +from typing import AsyncContextManager # from random import random # from nicegui_modules import data # from nicegui_modules import ui_components @@ -16,13 +19,16 @@ from dataclasses import dataclass, field ALLOW_BODY_SCROLL: bool = True LOOKBACK: int = 60 LOOKBACK_RT_TV_MAX_POINTS: int = 3000 -# REFRESH_INTERVAL_SEC: float = 10 +REFRESH_INTERVAL_SEC: float = 10 REFRESH_INTERVAL_RT_SEC: float = 1/10 - +# CON: AsyncContextManager +# ENGINE = create_async_engine('mysql+asyncmy://root:pwd@localhost/fund_rate') ENGINE = create_engine('mysql+pymysql://root:pwd@localhost/fund_rate') -VALKEY = valkey.Valkey(host='localhost', port=6379, db=0, decode_responses=True) + +VALKEY = valkey.Valkey(host='localhost', port=6379, db=0, decode_responses=True) + CHARTS = [ { 'type': 'AREA', @@ -110,6 +116,81 @@ CHARTS_OPTIONS = { } } +### Data ### +async def get_bfr_master_data() -> pd.DataFrame: + df = pd.DataFrame(json.loads(VALKEY.get('fr_engine_best_fund_rate_master'))) # ty:ignore[invalid-argument-type] + df.reset_index(drop=True) + df['id'] = df.index + return df + +async def get_trades_hist() -> pd.DataFrame: + start_ts = (round(datetime.now().timestamp()*1000)-(60*60*24*1000)) + ### ASTER ### + aster_orders = text(f''' + SELECT * + FROM fr_aster_user_order_trade + WHERE timestamp_arrival > {start_ts} + ''') + df_aster_orders = pd.read_sql(aster_orders, con=ENGINE) + df_aster_orders['timestamp_dt'] = pd.to_datetime(df_aster_orders['timestamp_transaction'], unit='ms') + df_aster_orders_fill = df_aster_orders.loc[df_aster_orders['execution_type']=='TRADE',:] + df_aster_orders_fill = df_aster_orders_fill[['timestamp_transaction','order_trade_time_ts','timestamp_dt','order_id','trade_id','client_order_id','order_status','side','last_filled_qty','filled_accumulated_qty','commission','last_filled_price','realized_profit']].reset_index(drop=True) + + df_aster_trades = df_aster_orders_fill.groupby('order_id').agg({'timestamp_transaction': 'first','order_trade_time_ts':'last','order_status':'last','side':'last','last_filled_qty':'sum','filled_accumulated_qty':'last','commission':'sum','last_filled_price':'mean','realized_profit':'sum'}).reset_index() + df_aster_trades['is_mkt_maker'] = df_aster_trades['commission'] == 0.00 + df_aster_trades['timestamp_ts'] = pd.to_datetime(df_aster_trades['order_trade_time_ts'], unit='ms') + + df_aster_trades = df_aster_trades.rename({'order_status':'status','filled_accumulated_qty':'filled_qty','commission':'payed_fee','last_filled_price':'price'}, axis=1) + + ### EXTEND ### + # Load and Transform Orders + extend_orders = text(f''' + SELECT * + FROM fr_extended_user_order + WHERE timestamp_arrival > {start_ts} + ''') + df_extend_orders = pd.read_sql(extend_orders, con=ENGINE) + df_extend_orders['timestamp_dt'] = pd.to_datetime(df_extend_orders['updated_time_ts'], unit='ms') + df_extend_orders_fill = df_extend_orders.loc[df_extend_orders['status'].isin(['FILLED','PARTIALLY_FILLED']),:] + df_extend_orders_fill = df_extend_orders_fill[['created_time_ts','updated_time_ts','timestamp_dt','order_id','external_id','status','side','qty','filled_qty','payed_fee','price','averagePrice']].reset_index(drop=True) + + # Trades + df_extend_trades = df_extend_orders_fill.groupby('order_id').agg({'created_time_ts':'first','updated_time_ts':'last','status': 'last','side': 'last', 'filled_qty':'last','payed_fee':'sum','price':'last'}).reset_index() + df_extend_trades['duration_sec_ast'] = ( df_extend_trades['updated_time_ts'] - df_extend_trades['created_time_ts'] ) / 1000 + df_extend_trades['is_mkt_maker'] = df_extend_trades['payed_fee'] == 0.00 + df_extend_trades['timestamp_ts'] = pd.to_datetime(df_extend_trades['updated_time_ts'], unit='ms') + + def tie_trades_together_get_extend_from_aster(row): + row = row.to_frame().T + row.index=[1] + + extend_row = df_extend_trades[['order_id','timestamp_ts','status','side','filled_qty','payed_fee','price','is_mkt_maker']].loc[df_extend_trades['timestamp_ts']>row['timestamp_ts'].iloc[0],:].iloc[0] + extend_row = extend_row.to_frame().T + extend_row.index=[1] + + return_row = row.merge(extend_row, left_index=True, right_index=True, suffixes=('_ast','_ext')) + + return return_row.iloc[0] + + df_comb_trades = df_aster_trades[['order_id','timestamp_ts','status','side','filled_qty','payed_fee','price','is_mkt_maker']].apply(tie_trades_together_get_extend_from_aster, axis=1) + df_comb_trades['buy_price'] = df_comb_trades['price_ast'].where(df_comb_trades['side_ast']=='BUY', df_comb_trades['price_ext']) + df_comb_trades['sell_price'] = df_comb_trades['price_ast'].where(df_comb_trades['side_ast']=='SELL', df_comb_trades['price_ext']) + df_comb_trades['buy_qty'] = df_comb_trades['filled_qty_ast'].where(df_comb_trades['side_ast']=='BUY', df_comb_trades['filled_qty_ext']) + df_comb_trades['sell_qty'] = df_comb_trades['filled_qty_ast'].where(df_comb_trades['side_ast']=='SELL', df_comb_trades['filled_qty_ext']) + df_comb_trades['buy_side'] = df_comb_trades['order_id_ast'].where(df_comb_trades['side_ast']=='BUY', df_comb_trades['order_id_ext']) + df_comb_trades['buy_side'] = df_comb_trades['order_id_ast'] == df_comb_trades['buy_side'] + df_comb_trades['buy_side'] = df_comb_trades['buy_side'].replace(True, 'ASTER').replace(False,'EXTEND') + + df_comb_trades['per_trade_pnl'] = ( ( df_comb_trades['sell_price'] - df_comb_trades['buy_price'] ) * df_comb_trades['sell_qty'] ) - df_comb_trades['payed_fee_ast'] - df_comb_trades['payed_fee_ext'] + df_comb_trades['per_trade_pnl_pct'] = ( (df_comb_trades['sell_price']*df_comb_trades['sell_qty']) - (df_comb_trades['buy_price']*df_comb_trades['buy_qty']) ) / (df_comb_trades['buy_price']*df_comb_trades['buy_qty']) + + + df = df_comb_trades.apply(lambda x: x.dt.strftime('%Y-%m-%d %H:%M:%S.%f') if hasattr(x, 'dt') else x) + + df.reset_index(drop=True) + df['id'] = df.index + return df + ### Utils ### def update_body_scroll(e=None, bool_override=False): if e is None: @@ -128,7 +209,6 @@ async def update_tv(): series_update_aster_tob = json.loads(VALKEY.get('fut_ticker_aster')) # ty:ignore[invalid-argument-type] series_update_extend_tob = json.loads(VALKEY.get('fut_ticker_extended')) # ty:ignore[invalid-argument-type] series_update_algo_status = json.loads(VALKEY.get('algo_status')) # ty:ignore[invalid-argument-type] - master_data = json.loads(VALKEY.get(name='fr_engine_best_fund_rate_master')) # ty:ignore[invalid-argument-type] timestamp_aster_tob = round( ( series_update_aster_tob['timestamp_transaction'] / 1000 ) , 2) timestamp_extend_tob = round( ( series_update_extend_tob['timestamp_msg'] / 1000 ) , 2) @@ -165,13 +245,57 @@ async def update_tv(): ui.run_javascript(f'await update_tv(data_list={data_list}, lookback_max_points={LOOKBACK_RT_TV_MAX_POINTS});') +async def create_bfr_aggrid() -> ui.aggrid: + df = await get_bfr_master_data() + col_extras = { + 'symbol_ast': { + 'editable': False, + 'sortable': True + } + } + cols = [ {'field': v, **col_extras.get(v, {})} for v in df.columns ] + rows = df.to_dict(orient='records') + grid = ui.aggrid( + { + 'columnDefs': cols, + 'rowData': rows, + 'autoSizeStrategy': { + 'type': 'fitCellContents', + }, + # 'rowSelection': {'mode': 'multiRow'}, + # 'stopEditingWhenCellsLoseFocus': True, + } + ).classes('auto-fit flex-grow w-full col-span-2 md:col-span-1') + + return grid + +async def create_pnl_aggrid() -> ui.aggrid: + df = await get_trades_hist() + col_extras = {} + cols = [ {'field': v, **col_extras.get(v, {})} for v in df.columns ] + rows = df.to_dict(orient='records') + grid = ui.aggrid( + { + 'columnDefs': cols, + 'rowData': rows, + 'autoSizeStrategy': { + 'type': 'fitCellContents', + }, + # 'rowSelection': {'mode': 'multiRow'}, + # 'stopEditingWhenCellsLoseFocus': True, + } + ).classes('auto-fit flex-grow w-full col-span-2 md:col-span-1') + + return grid + ### Pages ### async def rt_chart_page(): global LOOKBACK LOOKBACK = app.storage.user.get('lookback', LOOKBACK) - timer = ui.timer(REFRESH_INTERVAL_RT_SEC, update_tv) + timer_tv = ui.timer(REFRESH_INTERVAL_RT_SEC, update_tv) + # timer_sql = ui.timer(REFRESH_INTERVAL_SEC) # ui.query('.q-page').classes('flex flex-col h-screen') @@ -182,22 +306,11 @@ async def rt_chart_page(): # ui.switch('▶️', value=True).bind_value_to(timer, 'active') # with ui.column().style('position: absolute; right: 20px; font-family: monospace; align-self: center;'): # ui.label('Atwater Trading - Funding Rate') - + ui.query('.nicegui-content').classes('p-0 w-full') + ui.query('.q-page').classes('flex') + with ui.grid(columns=2, rows=2).classes('h-screen w-full flex-grow gap-2 auto-fit '): - aggrid = ui.aggrid({ - 'columnDefs': [ - {'field': 'name', 'editable': True, 'sortable': True}, - {'field': 'age', 'editable': True}, - {'field': 'id'}, - ], - 'rowData': [ - {'id': 0, 'name': 'Alice', 'age': 18}, - {'id': 1, 'name': 'Bob', 'age': 21}, - {'id': 2, 'name': 'Carol', 'age': 20}, - ], - 'rowSelection': {'mode': 'multiRow'}, - 'stopEditingWhenCellsLoseFocus': True, - }).classes('auto-fit flex-grow w-full col-span-2 md:col-span-1') + # aggrid = await create_bfr_aggrid() # with ui.element(tag='div').classes('auto-fit flex-grow w-full').style("height:100%; width: 100%;"): # with ui.tabs().classes('w-full') as tabs: # one = ui.tab('One').classes('auto-fit flex-grow w-full').style("height:100%; width: 100%;") @@ -210,31 +323,17 @@ async def rt_chart_page(): ui.run_javascript(f'await create_tv(charts_list={CHARTS}, create_chart_options={CHARTS_OPTIONS});') with ui.element(tag='div').classes('col-span-2').style("height:100%; width: 100%;"): - with ui.tabs().classes('w-full') as tabs: - one = ui.tab('One') - two = ui.tab('Two') - with ui.tab_panels(tabs, value=two).classes('w-full').style("height:100%; width: 100%;"): - with ui.tab_panel(one): - ui.label('First tab') - with ui.tab_panel(two): - aggrid_2 = ui.aggrid({ - 'columnDefs': [ - {'field': 'name', 'editable': True, 'sortable': True}, - {'field': 'age', 'editable': True}, - {'field': 'id'}, - ], - 'rowData': [ - {'id': 0, 'name': 'Alice', 'age': 18}, - {'id': 1, 'name': 'Bob', 'age': 21}, - {'id': 2, 'name': 'Carol', 'age': 20}, - ], - 'rowSelection': {'mode': 'multiRow'}, - 'stopEditingWhenCellsLoseFocus': True, - }) + with ui.tabs().props('align=justify').classes('justify-start') as tabs: + tab_pnl = ui.tab('PnL').classes('justify-start') + tab_bfr = ui.tab('BFR').classes('justify-start') + with ui.tab_panels(tabs, value=tab_pnl).classes('w-full').style("height:100%; width: 100%;"): + with ui.tab_panel(tab_pnl): + ag_pnl = await create_pnl_aggrid() + with ui.tab_panel(tab_bfr): + ag_bfr = await create_bfr_aggrid() - -def root(): +async def root(): app.add_static_files(max_cache_age=0, url_path='/static', local_directory=os.path.join(os.path.dirname(__file__), 'nicegui_modules/static')) ui.add_head_html(''' @@ -251,4 +350,5 @@ def root(): '/': rt_chart_page, }).classes('w-full') -ui.run(root, storage_secret="123ABC", reload=True, dark=True, title='Atwater Trading') \ No newline at end of file + +ui.run(root, storage_secret="123ABC", reload=True, dark=True, title='Atwater Trading', port=9090) \ No newline at end of file