diff --git a/.gitignore b/.gitignore
index bf9387f..e4c5ae6 100644
--- a/.gitignore
+++ b/.gitignore
@@ -2,3 +2,5 @@
src/__pycache__
src/test.py
src/credentials.py
+exampleData.py
+data.json
diff --git a/src/arbus.py b/src/arbus.py
new file mode 100644
index 0000000..eaa16bd
--- /dev/null
+++ b/src/arbus.py
@@ -0,0 +1,78 @@
+import json
+
+import options
+
+
+async def parseParams(params):
+ paramsList = params.split()
+ paramsDict = {}
+ for i in range(len(options.paramsLines)):
+ paramsDict[options.paramsLines[i]] = paramsList[i]
+ return paramsDict
+
+async def toDictPairParams(pair: str, params):
+ paramsList = params.split()
+
+ if len(paramsList) != len(options.paramsLines):
+ return -1
+
+ paramsDict = {pair: {}}
+ for i in range(len(options.paramsLines)):
+ paramsDict[pair][options.paramsLines[i]] = paramsList[i]
+ return paramsDict
+
+async def checkPair(pair: str):
+ currentData = {}
+ try:
+ with open('data.json', 'r') as f:
+ currentData = json.load(f)
+ except json.decoder.JSONDecodeError as e:
+ print('WARNING: JSON file is empty! Ignore if your installation is fresh.')
+
+ if pair in currentData:
+ print(pair, ' exists in data file.')
+ return 1
+ else:
+ print(pair, ' not found in data file.')
+ return 0
+
+async def deletePair(pair: str):
+ currentData = {}
+ try:
+ with open('data.json', 'r') as f:
+ currentData = json.load(f)
+ except json.decoder.JSONDecodeError as e:
+ print('WARNING: JSON file is empty! Ignore if your installation is fresh.')
+
+ if pair in currentData:
+ print(pair, ' exists in data file.')
+ del currentData[pair]
+ with open('data.json', 'w', encoding = 'utf-8') as f:
+ json.dump(currentData, f, ensure_ascii = False, indent = 4)
+ return 0
+ else:
+ print(pair, ' not found in data file.')
+ return -1
+
+async def mainWrapper(pair: str, params):
+ newData = await toDictPairParams(pair, params)
+
+ if newData == -1:
+ return -1
+
+ currentData = {}
+ try:
+ with open('data.json', 'r') as f:
+ currentData = json.load(f)
+ except json.decoder.JSONDecodeError as e:
+ print('WARNING: JSON file is empty! Ignore if your installation is fresh.')
+
+ if pair in currentData:
+ print(pair, ' already exists.')
+ return -2
+ else:
+ with open('data.json', 'w', encoding = 'utf-8') as f:
+ currentData.update(newData)
+ json.dump(currentData, f, ensure_ascii = False, indent = 4)
+ print(pair, ' was added!')
+ return 0
diff --git a/src/bybit.py b/src/bybit.py
new file mode 100644
index 0000000..9494ea1
--- /dev/null
+++ b/src/bybit.py
@@ -0,0 +1,75 @@
+import time
+import asyncio
+
+from pybit.unified_trading import HTTP
+
+import options
+import credentials
+
+import arbus
+
+
+async def getClient(apiKey, apiSecret, testnet):
+ if testnet:
+ print('Using testnet API.')
+ else:
+ print('Using real API.')
+
+ client = HTTP(
+ testnet = testnet,
+ api_key = apiKey,
+ api_secret = apiSecret,
+ )
+ try:
+ response = client.get_account_info()
+ print('Auth succesful!')
+ print('Account info:', response.get('retMsg'))
+ return client
+ except Exception as e:
+ print('Auth failed! Check API key!')
+ print('Error:', e)
+ return -1
+
+
+async def strategy(client: HTTP, pair: str, params):
+ startTime = time.time()
+ print('Starting strategy with ', pair)
+ paramsDict = await arbus.parseParams(params)
+
+ i = 0
+ t = await arbus.checkPair(pair)
+ while t:
+ t = await arbus.checkPair(pair)
+ if t != 1:
+ break
+
+ # client = getClient(credentials.api_key, credentials.api_secret, options.testnet)
+
+ r1 = client.get_order_history(
+ category=options.category,
+ symbol=pair
+ )
+ print(r1, '\n')
+
+ r2 = client.place_order(
+ category = options.category,
+ symbol = pair,
+ side = 'BUY',
+ orderType = 'Market',
+ qty = paramsDict['orderSize'],
+ marketUnit = 'quoteCoin'
+ )
+
+
+ print(r2, '\n')
+ if r2['retMsg'] == 'OK':
+ print('Order placed succesfully!')
+
+
+
+ await asyncio.sleep(20)
+ i += 1
+
+ print('Ending strategy with ', pair)
+ print('Ended on the iteration number ', i)
+ return i
diff --git a/src/main.py b/src/main.py
index 2a6fefa..d819fbc 100644
--- a/src/main.py
+++ b/src/main.py
@@ -1,4 +1,3 @@
-import time
import asyncio
from aiogram import Bot, Dispatcher
@@ -6,126 +5,158 @@ from aiogram.filters import Command
from aiogram.types import Message
from aiogram.client.default import DefaultBotProperties
from aiogram.enums import ParseMode
+from aiogram.types import BotCommand, BotCommandScopeDefault
from aiogram.fsm.context import FSMContext
from aiogram import Router, F
from aiogram.fsm.state import State, StatesGroup
from aiogram.fsm.storage.memory import MemoryStorage
-from aiogram.utils.chat_action import ChatActionSender
+
+import bybit
+import arbus
import credentials
-import options
import strings
+import options
-# Custom types and vars
-
-paramsLines = ['highEnd',
- 'lowEnd',
- 'highBreak',
- 'lowBreak',
- 'netLevelsAmount',
- 'netStep',
- 'takeDelta',
- 'stopDelta',
- 'orderSize'
- ]
-
-
-# Custom
-
-async def parseParams(params: str):
- paramsList = params.split()
- paramsDict = {}
- for i in range(len(paramsLines)):
- paramsDict(paramsLines[i]) = paramsList[i]
- return paramsDict
-
-
-# Bybit
-
-async def strategy(pair, params):
- print(params)
- await time.sleep(5)
- print('Test succes')
- result = pair + '\n' + params
- return result
-
-
-# Telegram
-
# Входные поля для трейдинг-бота:
# Верхняя граница ордеров
# Нижняя граница ордеров
# Верхняя граница для брейка
# Нижняя граница для брейка
# Количество уровней сетки
-# Шаг сетки
# Дельта для тейка
# Дельта для стопа
# Размер позиции на каждом уровне
-class Form(StatesGroup):
+
+async def set_commands():
+ commands = [BotCommand(command='start', description='Старт'),
+ BotCommand(command='help', description='Инструкция'),
+ BotCommand(command='strategy', description='Запустить стратегию'),
+ BotCommand(command='stop', description='Остановить стратегию')
+ ]
+ await bot.set_my_commands(commands, BotCommandScopeDefault())
+
+
+class startForm(StatesGroup):
pair = State()
params = State()
+class stopForm(StatesGroup):
+ pair = State()
+
dp = Dispatcher(storage=MemoryStorage())
bot = Bot(
token=credentials.bot_token,
default=DefaultBotProperties(parse_mode=ParseMode.HTML),
)
+
strategy_router = Router()
+stop_router = Router()
@dp.message(Command("start"))
async def commandStart(message: Message) -> None:
+ print("Called function commandStart")
await message.answer(strings.startCommand)
@dp.message(Command("help"))
async def commandHelp(message: Message) -> None:
+ print("Called function commandHelp")
await message.answer(strings.helpCommand)
@strategy_router.message(Command("strategy"))
async def commandStrategy(message: Message, state: FSMContext):
- print("F command strategy + capture pair")
- async with ChatActionSender.typing(bot=bot, chat_id=message.chat.id):
- await asyncio.sleep(1)
- await message.answer(strings.strategyCommand + '\n' + strings.askPair)
- await state.set_state(Form.pair)
+ print("Called function commandStrategy")
+ await message.answer(strings.strategyCommand + '\n' + strings.askPair)
+ await state.set_state(startForm.pair)
-@strategy_router.message(F.text, Form.pair)
-async def capture_pair(message: Message, state: FSMContext):
- print("F capture pair + capture params")
+@strategy_router.message(F.text, startForm.pair)
+async def capture_start_pair(message: Message, state: FSMContext):
+ print("Called function capture_start_pair")
await state.update_data(pair=message.text)
- async with ChatActionSender.typing(bot=bot, chat_id=message.chat.id):
- await asyncio.sleep(1)
- await message.answer(strings.askParams)
- await state.set_state(Form.params)
+ data = await state.get_data()
-@strategy_router.message(F.text, Form.params)
+ t = 0
+ if await arbus.checkPair(data.get("pair")) == 1:
+ msg_text = (f'Стратегия на паре {data.get("pair")} уже запущена.\nПожалуйста остановите стратегию либо введите другую пару.')
+ t = 1
+ else:
+ msg_text = strings.askParams
+
+ await message.answer(msg_text)
+ if t == 1:
+ print('Clearing state!')
+ await state.clear()
+ else:
+ await state.set_state(startForm.params)
+
+@strategy_router.message(F.text, startForm.params)
async def capture_params(message: Message, state: FSMContext):
- print("F capture params + end")
+ print("Called function capture_params")
await state.update_data(params=message.text)
data = await state.get_data()
- t = await strategy(pair=data.get("pair"), params=data.get("params"))
- msg_text = (f'Вы запускаете стратегию на паре {data.get("pair")} с данными параметрами\n{data.get("params")}\n')
+ t = await arbus.mainWrapper(pair=data.get("pair"), params=data.get("params"))
+ if t == 0:
+ client = await bybit.getClient(credentials.api_key, credentials.api_secret, options.testnet)
+ if client == -1:
+ msg_text = (f'Аутентификация не удалась, сообщите администратору если увидете данное сообщение.')
+ else:
+ asyncio.create_task(bybit.strategy(client, data.get("pair"), data.get("params")))
+ msg_text = (f'Вы запустили стратегию на паре {data.get("pair")} с данными параметрами:\n{data.get("params")}\n')
+ elif t == -1:
+ msg_text = (f'Параметры введены в неверном формате, пожалуйста начните заново.')
+ elif t == -2:
+ msg_text = (f'Стратегия на паре {data.get("pair")} уже запущена.')
+ else:
+ msg_text = (f'Возникла непредвиденная ошибка. =(')
await message.answer(msg_text)
await state.clear()
-@dp.message(Command("stop"))
-async def commandStop(message: Message) -> None:
- await message.answer(strings.stopCommand + '\n' + strings.stopStrategy)
+
+@stop_router.message(Command("stop"))
+async def commandStop(message: Message, state: FSMContext):
+ print("Called function commandStop")
+ await message.answer(strings.stopCommand + '\n' + strings.askPair)
+ await state.set_state(stopForm.pair)
+
+@stop_router.message(F.text, stopForm.pair)
+async def capture_stop_pair(message: Message, state: FSMContext):
+ print("Called function capture_stop_pair")
+ await state.update_data(pair=message.text)
+ data = await state.get_data()
+
+ if await arbus.checkPair(data.get("pair")) == 1:
+ t = await arbus.deletePair(data.get("pair"))
+ if t == 0:
+ print('Deleted pair succesfuly')
+ else:
+ print('Error with deleting pair')
+ msg_text = strings.stopStrategy
+ else:
+ msg_text = strings.pairNotFound
+
+ await message.answer(msg_text)
+ await state.clear()
+
+
+async def start_bot():
+ await set_commands()
+
+async def main() -> None:
+ dp.include_router(strategy_router)
+ dp.include_router(stop_router)
+ dp.startup.register(start_bot)
+ await dp.start_polling(bot)
# Main
-async def main() -> None:
- dp.include_router(strategy_router)
- await dp.start_polling(bot)
-
if __name__ == "__main__":
print('Started bot!')
asyncio.run(main())
diff --git a/src/options.py b/src/options.py
index ca359a8..525dbed 100644
--- a/src/options.py
+++ b/src/options.py
@@ -4,8 +4,20 @@ testnet = True # Use testnet or not
pairSymbol = 'ETHUSDT' # Trading pair
mainSymbol = 'USDT' # Balance asset
timeScape = '15m' # Candle length
+category = 'spot'
leverage = 1 # Leverage
notification = 1 # Telegram notifications
loopSleepTime = 2 # Time passing between loops/checks
+
+paramsLines = ['highEnd',
+ 'lowEnd',
+ 'highBreak',
+ 'lowBreak',
+ 'netLevelsAmount',
+ 'takeDelta',
+ 'stopDelta',
+ 'orderSize'
+ ]
+
diff --git a/src/strings.py b/src/strings.py
index 952f506..ea6bd20 100644
--- a/src/strings.py
+++ b/src/strings.py
@@ -1,3 +1,7 @@
+from aiogram.types import Message
+from aiogram.client.default import DefaultBotProperties
+from aiogram.enums import ParseMode
+
# Strategy
startStrategy = "Стратегия запущена!"
@@ -16,18 +20,17 @@ stopBot = "Бот остановлен!"
startCommand = "Привет! Это приватный бот для полуавтоматической торговли криптовалютой. Хороших позиций!"
-stopCommand = "Стратегия остановлена!"
+stopCommand = "Вы собираетесь остановить стратегию."
-helpCommand = "При старте стратегии требуется задать параметры в следующем формате:\n" \
+helpCommand = (f"При старте стратегии требуется задать параметры в следующем формате:\n" \
"Верхняя граница ордеров\n" \
"Нижняя граница ордеров\n" \
"Верхняя граница для брейка\n" \
"Нижняя граница для брейка\n" \
"Количество уровней сетки\n" \
-"Шаг сетки\n" \
"Дельта для тейка\n" \
"Дельта для стопа\n" \
-"Размер позиции на каждом уровне"
+"Размер позиции на каждом уровне")
strategyCommand = "Вы собираетесь запустить стратегию."
@@ -39,3 +42,5 @@ askPair = "Введите монетную пару:"
askParams = "Введите параметры:"
gotParams = "Параметры заданы!"
+
+pairNotFound = "Стратегия на данную монетную пару не найдена."