diff --git a/.dockerignore b/.dockerignore new file mode 100644 index 0000000..eb42b74 --- /dev/null +++ b/.dockerignore @@ -0,0 +1,2 @@ +.env** +venv/ \ No newline at end of file diff --git a/.env.example b/.env.example index f6ffd4c..83a8e08 100644 --- a/.env.example +++ b/.env.example @@ -1,2 +1,14 @@ TEST_TONCENTER_API_KEY= -MNEMONICS=a b c d e f g \ No newline at end of file +MNEMONICS=a b c d e f g +ORACLE_ADDRESS=kQCFEtu7e-su_IvERBf4FwEXvHISf99lnYuujdo0xYabZQgW +QUOTE_JETTON_WALLET_ADDRESS= + +REDIS_HOST=ticton-oracle-bot-redis-1 +REDIS_PORT=6379 +REDIS_DB=0 + +MYSQL_HOST=ticton-oracle-bot-mariadb-1 +MYSQL_PASSWORD=secure-password +MYSQL_ROOT_PASSWORD=secure.password +MYSQL_DATABASE=your_dbname +MYSQL_USER=your_name diff --git a/.github/workflows/build.yaml b/.github/workflows/build.yaml new file mode 100644 index 0000000..5c52c42 --- /dev/null +++ b/.github/workflows/build.yaml @@ -0,0 +1,56 @@ +name: Build docker images and push +on: + push: + branches: + - "main" + - "develop" + - "1-containerize-the-bot" + tags: + - "*" + paths-ignore: + - "**.md" + - "*.toml" + - "*.env*" + +jobs: + build-and-push: + name: Push Docker image to multiple registries + runs-on: ubuntu-latest + permissions: + packages: write + contents: read + steps: + - name: Check out the repo + uses: actions/checkout@v4 + - name: Set up QEMU + uses: docker/setup-qemu-action@v3 + - name: Set up Docker Buildx + uses: docker/setup-buildx-action@v3 + - name: Log in to Docker Hub + uses: docker/login-action@v3 + with: + username: ${{ secrets.DOCKER_USERNAME }} + password: ${{ secrets.DOCKER_PASSWORD }} + - name: Log in to the Container registry + uses: docker/login-action@v3 + with: + registry: ghcr.io + username: ${{ github.actor }} + password: ${{ secrets.GITHUB_TOKEN }} + - name: Extract metadata (tags, labels) for Docker + id: meta + uses: docker/metadata-action@v5 + with: + images: | + skyline9981/ticton-oracle-bot + ghcr.io/${{ github.repository }} + - name: Build and push Docker images + uses: docker/build-push-action@v5 + with: + context: . + file: "Dockerfile" + push: true + provenance: false + sbom: false + tags: ${{ steps.meta.outputs.tags }} + labels: ${{ steps.meta.outputs.labels }} diff --git a/.gitignore b/.gitignore index 6231e91..cb389d9 100644 --- a/.gitignore +++ b/.gitignore @@ -159,3 +159,5 @@ cython_debug/ # option (not recommended) you can uncomment the following to ignore the entire idea folder. #.idea/ data/ + +.python-version diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..f2ee3a4 --- /dev/null +++ b/Dockerfile @@ -0,0 +1,10 @@ +FROM python:3.10-slim + +WORKDIR /usr/src/app + +COPY . . + +RUN pip install --no-cache-dir -r requirements.txt + +CMD ["./run.sh"] + diff --git a/README.md b/README.md index 4e1770c..f8cf0d9 100644 --- a/README.md +++ b/README.md @@ -1 +1,27 @@ -# ticton-oracleBot +# Ticton Oracle Bot + +## Overview +The **Ticton Oracle Bot** is an arbitrage bot that operates by fetching the average market price of TON/USDT from multiple exchanges. It then quotes this price to the Ticton Oracle. In doing so, it seeks arbitrage opportunities among other quoters. + +## Prerequisites +- Docker +- Docker Compose + +## Setting Up the Environment +1. **Clone the Repository**: Clone this repository to your local machine. + +2. **Environment Variables**: + - Create a `.env` file in the root directory of the project. + - Fill out your `.env` file using `.env.example` as a guide. + - Obtain your Ton Center Testnet API key from [@tonapibot](https://t.me/tonapibot) + +## Running the Application +1. **Docker Compose**: Navigate to the root directory of the project where the `docker-compose.yml` file is located. +2. **Start the Application**: + - Run the following command: + ``` + docker-compose up -d + ``` + - This command will start all the services defined in your `docker-compose.yml` file. + - Ensure that the `.env` file is correctly set up, as the Docker containers will rely on these environment variables. + diff --git a/bot.py b/bot.py index 2ea2a82..3486111 100644 --- a/bot.py +++ b/bot.py @@ -1,7 +1,8 @@ import asyncio -import json import os from dotenv import load_dotenv +import logging + from tonsdk.utils import Address from tonsdk.contract.wallet import Wallets, WalletVersionEnum @@ -16,47 +17,57 @@ ) from oracle_interface import to_usdt, to_ton, to_bigint from utils import float_conversion, int_conversion -from market_price import ton_usdt_prices_generator +from market_price import get_ton_usdt_price +from mariadb_connector import get_alarm_from_db, update_alarm_to_db load_dotenv() +# set up logger +logger = logging.getLogger(__name__) +logger.setLevel(logging.INFO) + +console_handler = logging.StreamHandler() +console_handler.setLevel(logging.DEBUG) +formatter = logging.Formatter("%(asctime)s - %(name)s - %(levelname)s - %(message)s") +console_handler.setFormatter(formatter) +logger.addHandler(console_handler) + THRESHOLD_PRICE = float_conversion(1) * to_usdt(1) // to_ton(1) MIN_BASEASSET_THRESHOLD = to_ton(1) EXTRA_FEES = to_ton(1) -ORACLE = Address("kQCFEtu7e-su_IvERBf4FwEXvHISf99lnYuujdo0xYabZQgW") +ORACLE = Address(os.getenv("ORACLE_ADDRESS")) MNEMONICS, PUB_K, PRIV_K, WALLET = Wallets.from_mnemonics( mnemonics=str(os.getenv("MNEMONICS")).split(" "), version=WalletVersionEnum.v4r2, workchain=0, ) -QUOTE_JETTON_WALLET = Address("kQCQ1B7B7-CrvxjsqgYT90s7weLV-IJB2w08DBslDdrIXucv") -PATH_TO_ALARM_JSON = "data/alarm.json" +QUOTE_JETTON_WALLET = Address(os.getenv(("QUOTE_JETTON_WALLET_ADDRESS"))) async def load_alarms(): - with open(PATH_TO_ALARM_JSON, "r") as file: - return json.load(file) + return await get_alarm_from_db() async def save_alarms(updated_alarms): - with open(PATH_TO_ALARM_JSON, "w") as file: - json.dump(updated_alarms, file, indent=4) + await update_alarm_to_db(updated_alarms) async def find_active_alarm(): alarms = await load_alarms() total_alarms = await get_total_amount() + if alarms is None: + return [] + # Determine if there are new alarms and which are active alarms_to_check = [] for i in range(total_alarms): - str_i = str(i) - if str_i not in alarms or ( - alarms[str_i]["address"] != "is Mine" and alarms[str_i]["state"] == "active" + if i not in alarms or ( + alarms[i]["address"] != "is Mine" and alarms[i]["state"] == "active" ): - alarms_to_check.append(str_i) - print("alarms: ", alarms_to_check) + alarms_to_check.append(i) + logger.info(f"Alarms to Check: {alarms_to_check}") # Check alarms and get active alarms [(id, address)] active_alarms = await check_alarms(alarms_to_check) @@ -64,6 +75,7 @@ async def find_active_alarm(): async def estimate(alarm: tuple, price: float, base_bal, quote_bal): + logger.info(f"Estimate Alarm {alarm[0]}") alarm_info = await get_alarm_info(alarm[1]) # alarm[1] is address new_price = float_conversion(price) * to_usdt(1) // to_ton(1) old_price = alarm_info["base_asset_price"] @@ -115,14 +127,15 @@ async def check_balance( need_quote: int, max_buy_num: int, ): + logger.info("Check Balance") if base_bal < need_base: - print("Insufficient base asset balance") + logger.info("Insufficient Base Asset Balance") return None if quote_bal < need_quote: - print("Insufficient quote asset balance") + logger.info("Insufficient Quote Asset Balance") return None if max_buy_num == 0: - print("Max buy num is 0") + logger.info("Max Buy Num is 0") return None # Check if enough balance @@ -154,9 +167,10 @@ async def wind_alarms(active_alarms, price, base_bal, quote_bal): ) base_bal -= alarm_info["need_base_asset"] quote_bal -= alarm_info["need_quote_asset"] - print("Alarm", alarm[0], "finished") + logger.info(f"Alarm {alarm[0]} Wind Successfully") - print("Alarm", alarm[0], "no need to wind") + else: + logger.info(f"Alarm {alarm[0]} No Need to Wind") async def tick_one_scale(price, base_bal, quote_bal): @@ -187,18 +201,28 @@ async def tick_one_scale(price, base_bal, quote_bal): async def main(): - price_generator = ton_usdt_prices_generator() - async for price in price_generator: - print("Price:", price) - base_bal = await get_address_balance(WALLET.address.to_string()) - quote_bal = await get_token_balance(QUOTE_JETTON_WALLET.to_string()) - active_alarms = await find_active_alarm() - print("Active alarms:", active_alarms) - if active_alarms == []: - print("No active alarms") - await tick_one_scale(price, base_bal, quote_bal) - - await wind_alarms(active_alarms, price, base_bal, quote_bal) + while True: + try: + price = await get_ton_usdt_price() + if price is None: + continue + price = round(float(price), 9) + # =========== New Price Get =========== + logger.info("========== New Price Get ===========") + logger.info(f"New Price: {price}") + base_bal = int(await get_address_balance(WALLET.address.to_string())) + quote_bal = int(await get_token_balance(QUOTE_JETTON_WALLET.to_string())) + active_alarms = await find_active_alarm() + logger.info(f"Active Alarms: {active_alarms}") + if active_alarms == []: + logging.info("No Active Alarms") + await tick_one_scale(price, base_bal, quote_bal) + + await wind_alarms(active_alarms, price, base_bal, quote_bal) + + except Exception as e: + logger.error(f"Error while running bot {e}") + continue if __name__ == "__main__": diff --git a/docker-compose.yml b/docker-compose.yml new file mode 100644 index 0000000..252457d --- /dev/null +++ b/docker-compose.yml @@ -0,0 +1,33 @@ +version: '3' +services: + app: + image: skyline9981/ticton-oracle-bot:1-containerize-the-bot + platform: linux/amd64 + restart: always + # build: + # context: . + # dockerfile: ./Dockerfile + env_file: + - .env + depends_on: + - redis + - mariadb + + redis: + image: "redis:alpine" + restart: always + ports: + - "6379:6379" + + mariadb: + image: "mariadb" + environment: + MYSQL_ROOT_PASSWORD: ${MYSQL_ROOT_PASSWORD} + MYSQL_DATABASE: ${MYSQL_DATABASE} + MYSQL_USER: ${MYSQL_USER} + MYSQL_PASSWORD: ${MYSQL_PASSWORD} + volumes: + - maria-db:/var/lib/mysql + +volumes: + maria-db: diff --git a/mariadb_connector.py b/mariadb_connector.py new file mode 100644 index 0000000..18e1178 --- /dev/null +++ b/mariadb_connector.py @@ -0,0 +1,127 @@ +import os +from dotenv import load_dotenv +import asyncio + +import mysql.connector as connector + +import logging + +# set up logger +logger = logging.getLogger(__name__) +logger.setLevel(logging.INFO) + +console_handler = logging.StreamHandler() +console_handler.setLevel(logging.DEBUG) +formatter = logging.Formatter("%(asctime)s - %(name)s - %(levelname)s - %(message)s") +console_handler.setFormatter(formatter) +logger.addHandler(console_handler) + + +load_dotenv() + + +async def create_connection(): + try: + connection = connector.connect( + host=os.getenv("MYSQL_HOST"), # Assuming MariaDB is running on localhost + database=os.getenv("MYSQL_DATABASE"), + user=os.getenv("MYSQL_USER"), + password=os.getenv("MYSQL_PASSWORD"), + ) + if connection.is_connected(): + return connection + else: + return None + except Exception as e: + logger.error(f"Error while connecting to MariaDB {e}") + + +async def init(): + try: + connection = await create_connection() + if connection is not None and connection.is_connected(): + cursor = connection.cursor() + create_table_sql = """ + CREATE TABLE IF NOT EXISTS alarms ( + id INT PRIMARY KEY, + address VARCHAR(255), + state VARCHAR(100), + price DECIMAL(16, 9) + ) + """ + cursor.execute(create_table_sql) + connection.commit() + connection.close() + logger.info("Successfully Initialized MariaDB") + + return True + + except Exception as e: + logger.error("Error while initializing MariaDB", e) + return False + + +async def get_alarm_from_db(): + try: + connection = await create_connection() + if connection is not None and connection.is_connected(): + cursor = connection.cursor() + select_sql = "SELECT * FROM {}" + select_sql = select_sql.format("alarms") + cursor.execute(select_sql) + result = {} + for id, address, state, price in cursor.fetchall(): + result[id] = {} + result[id]["address"] = address + result[id]["state"] = state + result[id]["price"] = price + + cursor.close() + connection.close() + return result + + except Exception as e: + logger.error(f"Error while fetching alarm info from MariaDB {e}") + return None + + +async def update_alarm_to_db(alarm_dict): + try: + if alarm_dict is None: + return None + connection = await create_connection() + if connection is not None and connection.is_connected(): + cursor = connection.cursor() + update_sql = """ + INSERT INTO {} (id, address, state, price) + VALUES (%s, %s, %s, %s) + ON DUPLICATE KEY UPDATE address = VALUES(address), state = VALUES(state), price = VALUES(price) + """ + update_sql = update_sql.format("alarms") + insert_list = [] + for alarm_id, alarm_info in alarm_dict.items(): + insert_list.append( + ( + alarm_id, + alarm_info["address"], + alarm_info["state"], + alarm_info["price"], + ) + ) + cursor.executemany(update_sql, insert_list) + connection.commit() + cursor.close() + connection.close() + + except Exception as e: + logger.error(f"Error while updating alarm info to MariaDB {e}") + + +async def main(): + flag = False + while not flag: + flag = await init() + + +if __name__ == "__main__": + asyncio.run(main()) diff --git a/market_price.py b/market_price.py index f56cf92..457b138 100644 --- a/market_price.py +++ b/market_price.py @@ -1,5 +1,26 @@ import ccxt.async_support as ccxt import asyncio +import os +import redis +from dotenv import load_dotenv + +import logging + +# set up logger +logger = logging.getLogger(__name__) +logger.setLevel(logging.INFO) + +console_handler = logging.StreamHandler() +console_handler.setLevel(logging.DEBUG) +formatter = logging.Formatter("%(asctime)s - %(name)s - %(levelname)s - %(message)s") +console_handler.setFormatter(formatter) +logger.addHandler(console_handler) + +load_dotenv() + +REDIS_HOST = os.getenv("REDIS_HOST", "localhost") +REDIS_PORT = int(os.getenv("REDIS_PORT", 6379)) +REDIS_DB = int(os.getenv("REDIS_DB", 0)) EXCHANGE_LIST = [ "bybit", @@ -7,6 +28,10 @@ "okx", ] +redis_client = redis.StrictRedis( + host=REDIS_HOST, port=REDIS_PORT, db=REDIS_DB, decode_responses=True +) + async def fetch_price_from_exchange(exchange_id, symbol="TON/USDT"): try: @@ -17,7 +42,7 @@ async def fetch_price_from_exchange(exchange_id, symbol="TON/USDT"): ticker = await exchange.fetch_ticker(symbol) return ticker["last"] except Exception as e: - print(f"Error fetching from {exchange_id}: {str(e)}") + logger.error(f"Error while fetching price from {exchange_id} {e}") return None @@ -27,10 +52,19 @@ async def fetch_ton_usdt_prices(): return [price for price in prices if price is not None] -async def ton_usdt_prices_generator(): +async def set_ton_usdt_prices(): while True: prices = await fetch_ton_usdt_prices() if prices: - yield sum(prices) / len(prices) + price = sum(prices) / len(prices) + redis_client.set("ton_usdt_price", price) else: - yield None + continue + + +async def get_ton_usdt_price(): + return redis_client.get("ton_usdt_price") + + +if __name__ == "__main__": + asyncio.run(set_ton_usdt_prices()) diff --git a/oracle_interface.py b/oracle_interface.py index 8708e95..d26a6a1 100644 --- a/oracle_interface.py +++ b/oracle_interface.py @@ -7,13 +7,24 @@ from decimal import Decimal from typing import Union from dotenv import load_dotenv +import logging import os import time import asyncio -import json from utils import float_conversion, int_conversion, to_token from ton_center_client import TonCenterTonClient +from mariadb_connector import get_alarm_from_db, update_alarm_to_db + +# set up logger +logger = logging.getLogger(__name__) +logger.setLevel(logging.INFO) + +console_handler = logging.StreamHandler() +console_handler.setLevel(logging.DEBUG) +formatter = logging.Formatter("%(asctime)s - %(name)s - %(levelname)s - %(message)s") +console_handler.setFormatter(formatter) +logger.addHandler(console_handler) load_dotenv() @@ -22,14 +33,13 @@ GAS_FEE = to_nano(1, "ton") API_KEY = os.getenv("TEST_TONCENTER_API_KEY") -ORACLE = Address("kQCFEtu7e-su_IvERBf4FwEXvHISf99lnYuujdo0xYabZQgW") +ORACLE = Address(os.getenv("ORACLE_ADDRESS")) MNEMONICS, PUB_K, PRIV_K, WALLET = Wallets.from_mnemonics( mnemonics=str(os.getenv("MNEMONICS")).split(" "), version=WalletVersionEnum.v4r2, workchain=0, ) -PATH_TO_ALARM_JSON = "data/alarm.json" def to_usdt(amount: Union[int, float, str, Decimal]) -> Decimal: @@ -88,25 +98,31 @@ async def get_token_balance(address: str): async def check_alarms(alarm_id_list: list): + logger.info("Checking Alarms State") client = TonCenterTonClient(API_KEY) - # open alarm.json - with open(PATH_TO_ALARM_JSON, "r") as f: - alarm_dict = json.load(f) + # get alarm dict + alarm_dict = await get_alarm_from_db() + if alarm_dict is None: + return [] # get alarm address bytes - tasks = [ - client.run_get_method( - ORACLE.to_string(), "getAlarmAddress", [["num", alarm_id]] - ) - for alarm_id in alarm_id_list - ] - results = await asyncio.gather(*tasks) - address_bytes_list = [result["bytes"] for result in results] + address_bytes_list = [] + # 5 tasks one time + for i in range(0, len(alarm_id_list), 5): + tasks = [ + client.run_get_method( + ORACLE.to_string(), "getAlarmAddress", [["num", alarm_id]] + ) + for alarm_id in alarm_id_list[i : i + 5] + ] + logger.info(f"Getting Alarms Address {alarm_id_list[i : i + 5]}") + results = await asyncio.gather(*tasks) + address_bytes_list += [result["bytes"] for result in results] # get alarm address address_list = [] for alarm_id, address_bytes in zip(alarm_id_list, address_bytes_list): - alarm_info = alarm_dict.get(str(alarm_id)) + alarm_info = alarm_dict.get(alarm_id) if alarm_info and alarm_info.get("address"): address_list.append(alarm_info["address"]) else: @@ -114,25 +130,32 @@ async def check_alarms(alarm_id_list: list): # cell_bytes = base64.b64decode(result[0][1]["bytes"]) cs = CellSlice(address_bytes) address = cs.load_address() - alarm_dict[str(alarm_id)] = {"address": address} + alarm_dict[alarm_id] = {"address": address} address_list.append(address) # get alarm state - tasks = [client.get_address_state(address) for address in address_list] - alarm_state_list = await asyncio.gather(*tasks) + alarm_state_list = [] + # 5 tasks one time + for i in range(0, len(address_list), 5): + tasks = [ + client.get_address_state(address) for address in address_list[i : i + 5] + ] + logger.info(f"Checking Alarms State {address_list[i : i + 5]}") + alarm_state_list += await asyncio.gather(*tasks) # update alarm dict for alarm_id, alarm_state in zip(alarm_id_list, alarm_state_list): - alarm_dict[str(alarm_id)]["state"] = alarm_state + alarm_dict[alarm_id]["state"] = alarm_state + alarm_dict[alarm_id]["price"] = -1 result = [] for alarm_id in alarm_id_list: - alarm_info = alarm_dict[str(alarm_id)] + alarm_info = alarm_dict[alarm_id] if alarm_info["state"] == "active": result.append((alarm_id, alarm_info["address"])) - # save alarm.json - with open(PATH_TO_ALARM_JSON, "w") as f: - json.dump(alarm_dict, f, indent=4) + + # update alarm dict to db + await update_alarm_to_db(alarm_dict) return result @@ -148,6 +171,9 @@ async def tick( expire_at=int(time.time()) + 1000, extra_fees=2, ): + logger.info("Tick") + logger.info(f"quote_asset_to_transfer: {quote_asset_to_transfer}") + logger.info(f"base_asset_to_transfer: {base_asset_to_transfer}") base_asset_price = float_conversion(to_usdt(quote_asset_to_transfer)) // to_ton( base_asset_to_transfer ) @@ -180,7 +206,7 @@ async def tick( ) query = watchmaker.create_transfer_message( - to_addr="kQCQ1B7B7-CrvxjsqgYT90s7weLV-IJB2w08DBslDdrIXucv", + to_addr=os.getenv("QUOTE_JETTON_WALLET_ADDRESS"), amount=to_nano(4, "ton"), seqno=int(seqno, 16), payload=body, @@ -199,6 +225,10 @@ async def tick( async def wind( timekeeper, oracle, alarm_id, buy_num, new_price, need_quoate_asset, need_base_asset ): + logger.info("Wind") + logger.info(f"alarm_id: {alarm_id}") + logger.info(f"buy_num: {buy_num}") + logger.info(f"new_price: {new_price}") client = TonCenterTonClient(API_KEY) seqno = await client.run_get_method(timekeeper.address.to_string(), "seqno", []) forward_info = ( @@ -223,7 +253,7 @@ async def wind( .end_cell() ) query = timekeeper.create_transfer_message( - to_addr="kQCQ1B7B7-CrvxjsqgYT90s7weLV-IJB2w08DBslDdrIXucv", + to_addr=os.getenv("QUOTE_JETTON_WALLET_ADDRESS"), amount=to_bigint(need_base_asset) + GAS_FEE, seqno=int(seqno, 16), payload=body, @@ -237,15 +267,15 @@ async def wind( wind_result = results[1] if wind_result["@type"] == "ok": - with open(PATH_TO_ALARM_JSON, "r") as f: - alarm_dict = json.load(f) - alarm_dict[str(alarm_id)] = { - "address": "is Mine", - "state": "active", - "price": to_bigint(new_price), + new_price = int_conversion(new_price * to_ton(1) / to_usdt(1)) + update_alarm_dict = { + alarm_id: { + "address": "is Mine", + "state": "active", + "price": round(new_price, 9), + } } - with open(PATH_TO_ALARM_JSON, "w") as f: - json.dump(alarm_dict, f, indent=4) + await update_alarm_to_db(update_alarm_dict) return wind_result, alarm_id @@ -255,12 +285,14 @@ async def ring( oracle, alarm_id, ): + logger.info("Ring") + logger.info(f"alarm_id: {alarm_id}") client = TonCenterTonClient(API_KEY) seqno = await client.run_get_method(watchmaker.address.to_string(), "seqno", []) body = ( begin_cell() .store_uint(0xC3510A29, 32) - .store_uint(0, 257) + .store_uint(alarm_id, 257) .store_uint(alarm_id, 257) .end_cell() ) @@ -272,25 +304,26 @@ async def ring( ) boc = query["message"].to_boc(False) - with open(PATH_TO_ALARM_JSON, "r") as f: - alarm_dict = json.load(f) - alarm_dict[str(alarm_id)]["state"] = "uninitialied" - with open(PATH_TO_ALARM_JSON, "w") as f: - json.dump(alarm_dict, f, indent=4) + update_alarm_dict = { + alarm_id: {"address": "is Mine", "state": "uninitialied", "price": -1} + } + result = await client.send_boc(boc) + + await update_alarm_to_db(update_alarm_dict) - return await client.send_boc(boc) + return result async def main(): - # print( - # await tick( - # watchmaker=WALLET, - # oracle=ORACLE, - # quote_asset_to_transfer=2, - # base_asset_to_transfer=1, - # ) - # ) - print(await ring(WALLET, ORACLE, 10)) + print( + await tick( + watchmaker=WALLET, + oracle=ORACLE, + quote_asset_to_transfer=4, + base_asset_to_transfer=1, + ) + ) + # print(await ring(WALLET, ORACLE, 25)) # print( # await wind( # timekeeper=WALLET, @@ -303,7 +336,7 @@ async def main(): # ) # ) # print(await get_total_amount()) - # print(await check_alarms([1])) + # print(await check_alarms([1, 2, 3])) # print(await get_alarm_info("EQAWIJ3mBo990Ui8kinaodH3AlMi6Q3aPuhUNoFySO08uhEP")) diff --git a/requirements.txt b/requirements.txt index 8332813..d280a5d 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,213 +1,36 @@ -absl-py==2.0.0 aiodns==3.1.1 aiohttp==3.9.1 aiosignal==1.3.1 -altair==5.1.2 -annotated-types==0.6.0 -anyio==3.6.2 -appnope==0.1.3 -argon2-cffi==21.3.0 -argon2-cffi-bindings==21.2.0 -asttokens==2.1.0 -astunparse==1.6.3 async-timeout==4.0.3 -attrs==22.1.0 -autopep8==2.0.0 -backcall==0.2.0 -beautifulsoup4==4.11.1 +attrs==23.2.0 bitarray==2.6.0 bitstring==4.0.2 -bleach==5.0.1 -blinker==1.7.0 -cachetools==5.3.2 -ccxt==4.2.13 -certifi==2022.9.24 -cffi==1.15.1 -charset-normalizer==2.1.1 -click==8.1.7 -coloredlogs==15.0.1 -configparser==5.3.0 -contourpy==1.2.0 -cramjam==2.6.2 -crc16==0.1.1 +ccxt==4.2.18 +certifi==2023.11.17 +cffi==1.16.0 +charset-normalizer==3.3.2 crc32c==2.3.post0 cryptography==41.0.7 -cycler==0.12.1 -debugpy==1.6.3 -decorator==5.1.1 -defusedxml==0.7.1 -dnspython==2.4.2 -entrypoints==0.4 -et-xmlfile==1.1.0 exceptiongroup==1.2.0 -executing==1.2.0 -fastjsonschema==2.16.2 -fastparquet==2023.7.0 -filelock==3.13.1 -flatbuffers==23.5.26 -fonttools==4.44.0 frozenlist==1.4.1 -fsspec==2023.6.0 -gast==0.5.4 -gitdb==4.0.11 -GitPython==3.1.40 -google-auth==2.23.4 -google-auth-oauthlib==1.1.0 -google-pasta==0.2.0 -graphql-core==3.2.3 -graphql_query==1.0.3 -grpcio==1.59.3 -h11==0.14.0 -h5py==3.10.0 -httpcore==1.0.2 -httpx==0.25.2 -huggingface-hub==0.19.4 -humanfriendly==10.0 -idna==3.4 -imbalanced-learn==0.11.0 -imblearn==0.0 -importlib-metadata==6.8.0 +idna==3.6 iniconfig==2.0.0 -ipykernel==6.17.1 -ipython==8.6.0 -ipython-genutils==0.2.0 -ipywidgets==8.0.2 -jedi==0.18.2 -Jinja2==3.1.2 -joblib==1.3.2 -jsonschema==4.17.1 -jupyter==1.0.0 -jupyter-console==6.4.4 -jupyter-server==1.23.3 -jupyter_client==7.4.7 -jupyter_core==5.0.0 -jupyterlab-pygments==0.2.2 -jupyterlab-widgets==3.0.3 -keras==2.15.0 -kiwisolver==1.4.5 -libclang==16.0.6 loguru==0.7.2 -Markdown==3.5.1 -markdown-it-py==3.0.0 -MarkupSafe==2.1.1 -matplotlib==3.8.1 -matplotlib-inline==0.1.6 -mdurl==0.1.2 -mistune==2.0.4 -ml-dtypes==0.2.0 -mlxtend==0.23.0 -mock==2.0.0 -mpmath==1.3.0 multidict==6.0.4 -nbclassic==0.4.8 -nbclient==0.7.0 -nbconvert==7.2.5 -nbformat==5.7.0 -nest-asyncio==1.5.6 -networkx==3.2.1 -notebook==6.5.2 -notebook_shim==0.2.2 -numpy==1.26.2 -oauthlib==3.2.2 -onnxruntime==1.16.3 -opencv-python==4.6.0.66 -openpyxl==3.1.2 -opt-einsum==3.3.0 -packaging==21.3 -pandas==1.5.2 -pandocfilters==1.5.0 -parso==0.8.3 -pbr==5.11.1 -pexpect==4.8.0 -pickleshare==0.7.5 -Pillow==9.4.0 -platformdirs==2.5.4 +mysql-connector-python==8.3.0 +packaging==23.2 pluggy==1.3.0 -prometheus-client==0.15.0 -prompt-toolkit==3.0.33 -protobuf==4.21.12 -psutil==5.9.4 -ptyprocess==0.7.0 -pure-eval==0.2.2 -pyarrow==12.0.1 -pyasn1==0.5.1 -pyasn1-modules==0.3.0 pycares==4.4.0 -pycodestyle==2.10.0 pycparser==2.21 -pydantic==1.10.13 -pydantic_core==2.14.5 -pydeck==0.8.1b0 -Pygments==2.13.0 -pymongo==4.6.1 -PyMySQL==1.0.2 PyNaCl==1.5.0 -pyparsing==3.0.9 -pyrsistent==0.19.2 -pytest==7.4.3 -python-dateutil==2.8.2 +pytest==7.4.4 python-dotenv==1.0.0 -python-telegram-bot==20.7 -pytonlib==0.0.53 -pytz==2022.6 -PyYAML==6.0.1 -pyzmq==24.0.1 -qtconsole==5.4.0 -QtPy==2.3.0 -regex==2023.10.3 -requests==2.28.1 -requests-oauthlib==1.3.1 -rich==13.6.0 -rsa==4.9 -safetensors==0.4.0 -scikit-learn==1.3.2 -scipy==1.11.3 -seaborn==0.13.0 -Send2Trash==1.8.0 -six==1.16.0 -sklearn==0.0.post11 -smmap==5.0.1 -sniffio==1.3.0 -soupsieve==2.3.2.post1 -stack-data==0.6.1 -streamlit==1.28.1 -sympy==1.12 -tenacity==8.2.3 -tensorboard==2.15.1 -tensorboard-data-server==0.7.2 -tensorflow==2.15.0 -tensorflow-estimator==2.15.0 -tensorflow-io-gcs-filesystem==0.34.0 -tensorflow-macos==2.15.0 -termcolor==2.3.0 -terminado==0.17.0 -threadpoolctl==3.2.0 -tinycss2==1.2.1 -tokenizers==0.15.0 -toml==0.10.2 +redis==5.0.1 +requests==2.31.0 tomli==2.0.1 -ton==0.26 -tonpy==0.0.0.1.2b0 +tonpy tonsdk==1.0.13 -TonTools==2.1.2 -toolz==0.12.0 -torch==2.1.1 -torchvision==0.16.1 -tornado==6.2 -tqdm==4.66.1 -traitlets==5.5.0 -transformers==4.35.2 -tvm-valuetypes==0.0.10 -typing_extensions==4.8.0 -tzlocal==5.2 -Unidecode==1.3.7 -urllib3==1.26.13 -validators==0.22.0 -wcwidth==0.2.5 -webencodings==0.5.1 -websocket-client==1.4.2 -Werkzeug==3.0.1 -widgetsnbextension==4.0.3 -wrapt==1.14.1 +tvm-valuetypes==0.0.12 +typing_extensions==4.9.0 +urllib3==2.1.0 yarl==1.9.4 -zipp==3.17.0 diff --git a/run.sh b/run.sh new file mode 100755 index 0000000..2d1c3cc --- /dev/null +++ b/run.sh @@ -0,0 +1,9 @@ +#!/bin/sh + +# init database +python ./mariadb_connector.py + +# run market price and bot +python ./market_price.py & +python ./bot.py & +wait diff --git a/ton_center_client.py b/ton_center_client.py index a87c19d..dd782a5 100644 --- a/ton_center_client.py +++ b/ton_center_client.py @@ -67,7 +67,7 @@ async def send_boc(self, boc): return await self._run(self.provider.raw_send_message(boc)) async def _run(self, to_run): - timeout = aiohttp.ClientTimeout(total=5) + timeout = aiohttp.ClientTimeout(total=10) async with aiohttp.ClientSession(timeout=timeout) as session: func = to_run["func"] args = to_run["args"] diff --git a/utils.py b/utils.py index 0666302..3e45e76 100644 --- a/utils.py +++ b/utils.py @@ -9,7 +9,7 @@ def float_conversion(value, precision=64): def int_conversion(value): - return Decimal(value) // (Decimal(2) ** 64) + return Decimal(value) / (Decimal(2) ** 64) def to_token(value, decimals):