bitunix-automated-crypto-trading 2.6.0__py3-none-any.whl → 2.6.1__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- bitunix_automated_crypto_trading/AsyncThreadRunner.py +81 -0
- bitunix_automated_crypto_trading/BitunixApi.py +278 -0
- bitunix_automated_crypto_trading/BitunixSignal.py +1099 -0
- bitunix_automated_crypto_trading/BitunixWebSocket.py +254 -0
- bitunix_automated_crypto_trading/DataFrameHtmlRenderer.py +74 -0
- bitunix_automated_crypto_trading/NotificationManager.py +23 -0
- bitunix_automated_crypto_trading/ResourceManager.py +35 -0
- bitunix_automated_crypto_trading/ThreadManager.py +69 -0
- bitunix_automated_crypto_trading/TickerManager.py +636 -0
- bitunix_automated_crypto_trading/__init__.py +1 -0
- bitunix_automated_crypto_trading/bitunix.py +593 -0
- bitunix_automated_crypto_trading/clearenv.py +8 -0
- bitunix_automated_crypto_trading/config.py +90 -0
- bitunix_automated_crypto_trading/config.txt +60 -0
- bitunix_automated_crypto_trading/logger.py +85 -0
- bitunix_automated_crypto_trading/sampleenv.txt +5 -0
- bitunix_automated_crypto_trading/static/chart.css +28 -0
- bitunix_automated_crypto_trading/static/chart.js +362 -0
- bitunix_automated_crypto_trading/static/modal.css +68 -0
- bitunix_automated_crypto_trading/static/modal.js +147 -0
- bitunix_automated_crypto_trading/static/script.js +166 -0
- bitunix_automated_crypto_trading/static/styles.css +118 -0
- bitunix_automated_crypto_trading/templates/charts.html +98 -0
- bitunix_automated_crypto_trading/templates/login.html +19 -0
- bitunix_automated_crypto_trading/templates/main.html +551 -0
- bitunix_automated_crypto_trading/templates/modal-chart.html +26 -0
- bitunix_automated_crypto_trading/templates/modal-config.html +34 -0
- bitunix_automated_crypto_trading/templates/modal-logs.html +15 -0
- {bitunix_automated_crypto_trading-2.6.0.dist-info → bitunix_automated_crypto_trading-2.6.1.dist-info}/METADATA +1 -1
- bitunix_automated_crypto_trading-2.6.1.dist-info/RECORD +33 -0
- bitunix_automated_crypto_trading-2.6.1.dist-info/top_level.txt +1 -0
- bitunix_automated_crypto_trading-2.6.0.dist-info/RECORD +0 -5
- bitunix_automated_crypto_trading-2.6.0.dist-info/top_level.txt +0 -1
- {bitunix_automated_crypto_trading-2.6.0.dist-info → bitunix_automated_crypto_trading-2.6.1.dist-info}/WHEEL +0 -0
- {bitunix_automated_crypto_trading-2.6.0.dist-info → bitunix_automated_crypto_trading-2.6.1.dist-info}/entry_points.txt +0 -0
@@ -0,0 +1,593 @@
|
|
1
|
+
import asyncio
|
2
|
+
import os
|
3
|
+
import uvicorn
|
4
|
+
import numpy as np
|
5
|
+
import pandas as pd
|
6
|
+
import json
|
7
|
+
import time
|
8
|
+
from datetime import datetime
|
9
|
+
import pytz
|
10
|
+
from ThreadManager import ThreadManager
|
11
|
+
from BitunixApi import BitunixApi
|
12
|
+
from BitunixSignal import BitunixSignal
|
13
|
+
from NotificationManager import NotificationManager
|
14
|
+
|
15
|
+
from logger import Logger
|
16
|
+
logger = Logger(__name__).get_logger()
|
17
|
+
|
18
|
+
from fastapi import FastAPI, Request, Form, WebSocket, WebSocketDisconnect, Depends, Query
|
19
|
+
from fastapi.responses import HTMLResponse , JSONResponse, RedirectResponse, StreamingResponse
|
20
|
+
from fastapi.templating import Jinja2Templates
|
21
|
+
|
22
|
+
from fastapi.security import OAuth2PasswordRequestForm
|
23
|
+
from fastapi_login import LoginManager
|
24
|
+
from fastapi_login.exceptions import InvalidCredentialsException
|
25
|
+
from DataFrameHtmlRenderer import DataFrameHtmlRenderer
|
26
|
+
from fastapi.middleware.cors import CORSMiddleware
|
27
|
+
from fastapi.staticfiles import StaticFiles
|
28
|
+
|
29
|
+
from dotenv import load_dotenv, dotenv_values, set_key
|
30
|
+
from config import Settings
|
31
|
+
from pydantic import ValidationError
|
32
|
+
|
33
|
+
ENV_FILE = ".env"
|
34
|
+
CONFIG_FILE = "bitunix_automated_crypto_trading/config.txt"
|
35
|
+
LOG_FILE = "app.log"
|
36
|
+
|
37
|
+
#load environment variables
|
38
|
+
load_dotenv(ENV_FILE)
|
39
|
+
API_KEY = os.getenv('API_KEY')
|
40
|
+
SECRET_KEY = os.getenv('SECRET_KEY')
|
41
|
+
SECRET = os.getenv('SECRET')
|
42
|
+
PASSWORD = os.getenv('PASSWORD')
|
43
|
+
HOST = os.getenv('HOST')
|
44
|
+
|
45
|
+
#load config variables using setting class in config.py validating using pydantic
|
46
|
+
settings = Settings()
|
47
|
+
|
48
|
+
class bitunix():
|
49
|
+
def __init__(self, password, api_key, secret_key, settings):
|
50
|
+
self.screen_refresh_interval =settings.SCREEN_REFRESH_INTERVAL
|
51
|
+
|
52
|
+
self.autoTrade=settings.AUTOTRADE
|
53
|
+
|
54
|
+
self.threadManager = ThreadManager()
|
55
|
+
self.notifications = NotificationManager()
|
56
|
+
self.bitunixApi = BitunixApi(api_key, secret_key, settings)
|
57
|
+
self.bitunixSignal = BitunixSignal(api_key, secret_key, settings, self.threadManager, self.notifications, self.bitunixApi)
|
58
|
+
|
59
|
+
self.websocket_connections = set()
|
60
|
+
self.DB = {"admin": {"password": password}}
|
61
|
+
|
62
|
+
async def update_settings(self, settings):
|
63
|
+
self.settings = settings
|
64
|
+
await self.bitunixSignal.update_settings(settings)
|
65
|
+
await self.bitunixApi.update_settings(settings)
|
66
|
+
|
67
|
+
async def start(self):
|
68
|
+
await asyncio.create_task(self.bitunixSignal.load_tickers())
|
69
|
+
await asyncio.create_task(self.bitunixSignal.start_jobs())
|
70
|
+
|
71
|
+
async def restart(self):
|
72
|
+
await asyncio.create_task(self.bitunixSignal.restart_jobs())
|
73
|
+
|
74
|
+
async def send_message_to_websocket(self,message):
|
75
|
+
async def send_to_all():
|
76
|
+
for ws in self.bitunixSignal.websocket_connections:
|
77
|
+
await ws.send_text(message)
|
78
|
+
asyncio.run(send_to_all())
|
79
|
+
|
80
|
+
async def send_async_message_to_websocket(self,message):
|
81
|
+
for ws in self.bitunixSignal.websocket_connections:
|
82
|
+
await ws.send_text(message)
|
83
|
+
|
84
|
+
app = FastAPI()
|
85
|
+
app.mount("/static", StaticFiles(directory="bitunix_automated_crypto_trading/static"), name="static")
|
86
|
+
app.add_middleware(
|
87
|
+
CORSMiddleware,
|
88
|
+
allow_origins=["*"], # Adjust this for production
|
89
|
+
allow_credentials=True,
|
90
|
+
allow_methods=["*"],
|
91
|
+
allow_headers=["*"],
|
92
|
+
)
|
93
|
+
templates = Jinja2Templates(directory="bitunix_automated_crypto_trading/templates")
|
94
|
+
SECRET=os.getenv('SECRET')
|
95
|
+
login_manager = LoginManager(SECRET, token_url="/auth/login", use_cookie=True)
|
96
|
+
login_manager.cookie_name = "auth_token"
|
97
|
+
|
98
|
+
|
99
|
+
@app.post("/auth/login")
|
100
|
+
async def login(data: OAuth2PasswordRequestForm = Depends()):
|
101
|
+
start=time.time()
|
102
|
+
username = data.username
|
103
|
+
password = data.password
|
104
|
+
|
105
|
+
user = load_user(username, some_callable_object)
|
106
|
+
if not user or password != user["password"]:
|
107
|
+
raise InvalidCredentialsException
|
108
|
+
access_token = login_manager.create_access_token(data={"sub": username})
|
109
|
+
response = RedirectResponse(url="/main", status_code=302)
|
110
|
+
login_manager.set_cookie(response, access_token)
|
111
|
+
logger.info(f"/auth/login: elapsed time {time.time()-start}")
|
112
|
+
return response
|
113
|
+
|
114
|
+
def some_callable_object():
|
115
|
+
logger.info("called")
|
116
|
+
|
117
|
+
@login_manager.user_loader(some_callable=some_callable_object)
|
118
|
+
def load_user(username, some_callable=some_callable_object):
|
119
|
+
user = bitunix.DB.get(username)
|
120
|
+
return user
|
121
|
+
|
122
|
+
@app.get("/private")
|
123
|
+
async def get_private_endpoint(user=Depends(login_manager)):
|
124
|
+
return "You are an authenticated user"
|
125
|
+
|
126
|
+
@app.on_event("startup")
|
127
|
+
async def startup_event():
|
128
|
+
await asyncio.create_task(get_server_states(app, settings))
|
129
|
+
await bitunix.start()
|
130
|
+
|
131
|
+
#load inital states for html
|
132
|
+
async def get_server_states(app, settings):
|
133
|
+
app.state.element_states = {}
|
134
|
+
app.state.element_states['autoTrade']=settings.AUTOTRADE
|
135
|
+
app.state.element_states['optionMovingAverage']=settings.OPTION_MOVING_AVERAGE
|
136
|
+
app.state.element_states['maxAutoTrades']=settings.MAX_AUTO_TRADES
|
137
|
+
|
138
|
+
@app.post("/reload")
|
139
|
+
async def refresh_detected():
|
140
|
+
bitunix.bitunixSignal.notifications.add_notification("Reload detected!")
|
141
|
+
await asyncio.create_task(bitunix.restart())
|
142
|
+
|
143
|
+
@app.post("/save_states")
|
144
|
+
async def save_states(states: dict):
|
145
|
+
app.state.element_states.update(states)
|
146
|
+
return {"message": "States saved"}
|
147
|
+
|
148
|
+
@app.post("/get_states")
|
149
|
+
async def get_states(payload: dict):
|
150
|
+
element_ids = payload.get("element_ids", [])
|
151
|
+
states = {element_id: app.state.element_states.get(element_id, "No state found") for element_id in element_ids}
|
152
|
+
return {"states": states}
|
153
|
+
|
154
|
+
async def set_server_states():
|
155
|
+
settings.AUTOTRADE = True if app.state.element_states['autoTrade']=='true' else False
|
156
|
+
|
157
|
+
if settings.AUTOTRADE:
|
158
|
+
settings.OPTION_MOVING_AVERAGE = app.state.element_states['optionMovingAverage']
|
159
|
+
settings.notifications.add_notification(f"optionMovingAverage: {settings.OPTION_MOVING_AVERAGE}")
|
160
|
+
|
161
|
+
settings.PROFIT_AMOUNT = app.state.element_states['profitAmount']
|
162
|
+
settings.notifications.add_notification(f"profitAmount: {settings.PROFIT_AMOUNT}")
|
163
|
+
|
164
|
+
settings.LOSS_AMOUNT = app.state.element_states['lossAmount']
|
165
|
+
settings.notifications.add_notification(f"lossAmount: {settings.LOSS_AMOUNT}")
|
166
|
+
|
167
|
+
settings.MAX_AUTO_TRADES = app.state.element_states['maxAutoTrades']
|
168
|
+
settings.notifications.add_notification(f"maxAutoTrades: {settings.MAX_AUTO_TRADES}")
|
169
|
+
|
170
|
+
settings.notifications.add_notification(" AutoTrade activated" if settings.AUTOTRADE else "AutoTrade de-activated")
|
171
|
+
|
172
|
+
|
173
|
+
@app.post("/autotrade")
|
174
|
+
async def handle_autotrade():
|
175
|
+
await asyncio.create_task(set_server_states())
|
176
|
+
return {"status":settings.AUTOTRADE}
|
177
|
+
|
178
|
+
@app.get("/", response_class=HTMLResponse)
|
179
|
+
async def read_root(request: Request):
|
180
|
+
return templates.TemplateResponse({"request": request}, "login.html")
|
181
|
+
|
182
|
+
#when main page requested
|
183
|
+
@app.get("/main", response_class=HTMLResponse)
|
184
|
+
async def main_page(request: Request, user=Depends(login_manager)):
|
185
|
+
return templates.TemplateResponse({"request": request, "user": user}, "main.html")
|
186
|
+
|
187
|
+
#when main page opened
|
188
|
+
@app.websocket("/wsmain")
|
189
|
+
async def websocket_endpoint(websocket: WebSocket):
|
190
|
+
await asyncio.create_task(wsmain(websocket))
|
191
|
+
|
192
|
+
async def wsmain(websocket):
|
193
|
+
query_params = websocket.query_params
|
194
|
+
|
195
|
+
try:
|
196
|
+
logger.info("local main page WebSocket connection opened")
|
197
|
+
|
198
|
+
await websocket.accept()
|
199
|
+
bitunix.websocket_connections.add(websocket)
|
200
|
+
|
201
|
+
queue = asyncio.Queue()
|
202
|
+
queueTask = asyncio.create_task(send_data_queue(websocket, queue))
|
203
|
+
while True:
|
204
|
+
stime=time.time()
|
205
|
+
data={}
|
206
|
+
try:
|
207
|
+
|
208
|
+
# Handle incoming ping messages
|
209
|
+
await asyncio.create_task(send_pong(websocket,queue))
|
210
|
+
|
211
|
+
#combined data
|
212
|
+
dataframes={
|
213
|
+
"portfolio" : bitunix.bitunixSignal.portfoliodfStyle,
|
214
|
+
"positions" : bitunix.bitunixSignal.positiondfStyle,
|
215
|
+
"orders" : bitunix.bitunixSignal.orderdfStyle,
|
216
|
+
"signals" : bitunix.bitunixSignal.signaldfStyle,
|
217
|
+
"study" : bitunix.bitunixSignal.allsignaldfStyle,
|
218
|
+
"positionHistory" : bitunix.bitunixSignal.positionHistorydfStyle
|
219
|
+
}
|
220
|
+
notifications=bitunix.bitunixSignal.notifications.get_notifications()
|
221
|
+
|
222
|
+
utc_time = datetime.fromtimestamp(bitunix.bitunixSignal.lastAutoTradeTime, tz=pytz.UTC)
|
223
|
+
atctime = utc_time.astimezone(pytz.timezone('US/Central')).strftime('%Y-%m-%d %H:%M:%S')
|
224
|
+
utc_time = datetime.fromtimestamp(bitunix.bitunixSignal.lastTickerDataTime, tz=pytz.UTC)
|
225
|
+
tdctime = utc_time.astimezone(pytz.timezone('US/Central')).strftime('%Y-%m-%d %H:%M:%S')
|
226
|
+
|
227
|
+
data = {
|
228
|
+
"dataframes": dataframes,
|
229
|
+
"profit" : bitunix.bitunixSignal.profit,
|
230
|
+
"atctime": atctime,
|
231
|
+
"tdctime": tdctime,
|
232
|
+
"status_messages": [] if len(notifications)==0 else notifications
|
233
|
+
}
|
234
|
+
|
235
|
+
await queue.put(json.dumps(data))
|
236
|
+
|
237
|
+
except WebSocketDisconnect:
|
238
|
+
bitunix.websocket_connections.remove(websocket)
|
239
|
+
logger.info("local main page WebSocket connection closed")
|
240
|
+
break
|
241
|
+
except Exception as e:
|
242
|
+
logger.info(f"local main page websocket unexpected error1: {e}")
|
243
|
+
break
|
244
|
+
|
245
|
+
elapsed_time = time.time() - stime
|
246
|
+
|
247
|
+
if settings.VERBOSE_LOGGING:
|
248
|
+
logger.info(f"wsmain: elapsed time {elapsed_time}")
|
249
|
+
time_to_wait = max(0.01, bitunix.screen_refresh_interval - elapsed_time)
|
250
|
+
|
251
|
+
await asyncio.sleep(time_to_wait)
|
252
|
+
|
253
|
+
except Exception as e:
|
254
|
+
logger.info(f"local main page websocket unexpected error2: {e}")
|
255
|
+
finally:
|
256
|
+
queueTask.cancel()
|
257
|
+
try:
|
258
|
+
await queueTask
|
259
|
+
except asyncio.CancelledError:
|
260
|
+
pass
|
261
|
+
|
262
|
+
async def send_pong(websocket, queue):
|
263
|
+
# Handle incoming ping messages
|
264
|
+
try:
|
265
|
+
message = await asyncio.wait_for(websocket.receive_text(), timeout=0.01)
|
266
|
+
if message == "ping":
|
267
|
+
await queue.put("pong")
|
268
|
+
except asyncio.TimeoutError:
|
269
|
+
pass
|
270
|
+
|
271
|
+
async def send_data_queue(websocket, queue):
|
272
|
+
while True:
|
273
|
+
try:
|
274
|
+
data = await queue.get()
|
275
|
+
await websocket.send_text(data)
|
276
|
+
except Exception as e:
|
277
|
+
pass
|
278
|
+
|
279
|
+
@app.get("/send-message/{msg}")
|
280
|
+
async def send_message(msg: str):
|
281
|
+
await asyncio.create_task(bitunix.send_message_to_websocket(msg))
|
282
|
+
return {"message": f"Sent: {msg}"}
|
283
|
+
|
284
|
+
|
285
|
+
@app.post("/handle_bitunix_click")
|
286
|
+
async def handle_bitunix_click(symbol: str = Form(...)):
|
287
|
+
# Handle the row click event here
|
288
|
+
message = f"https://www.bitunix.com/contract-trade/{symbol}"
|
289
|
+
return {"message": message}
|
290
|
+
|
291
|
+
@app.post("/handle_order_close_click")
|
292
|
+
async def handle_order_close_click(symbol: str = Form(...), orderId: str = Form(...)):
|
293
|
+
datajs = await bitunix.bitunixApi.CancelOrder(symbol, orderId)
|
294
|
+
bitunix.bitunixSignal.notifications.add_notification(f'closing pending order for {symbol} {datajs["msg"]}')
|
295
|
+
|
296
|
+
@app.post("/handle_close_click")
|
297
|
+
async def handle_close_click(symbol: str = Form(...), positionId: str = Form(...), qty: str = Form(...), unrealizedPNL: str = Form(...), realizedPNL: str = Form(...)):
|
298
|
+
datajs = await bitunix.bitunixApi.FlashClose(positionId)
|
299
|
+
pnl=float(unrealizedPNL)+float(realizedPNL)
|
300
|
+
bitunix.bitunixSignal.notifications.add_notification(f'closing {qty} {symbol} with a profit/loss of {pnl} ({datajs["code"]} {datajs["msg"]}')
|
301
|
+
|
302
|
+
@app.post("/handle_buy_click")
|
303
|
+
async def handle_buy_click(symbol: str = Form(...), close: str = Form(...)):
|
304
|
+
balance = bitunix.bitunixSignal.get_portfolio_tradable_balance()
|
305
|
+
qty= str(balance * (float(settings.ORDER_AMOUNT_PERCENTAGE) / 100) / float(close) * int(settings.LEVERAGE))
|
306
|
+
datajs = await bitunix.bitunixApi.PlaceOrder(symbol,qty,close,'BUY')
|
307
|
+
bitunix.bitunixSignal.notifications.add_notification(f'Buying {qty} {symbol} @ {close} ({datajs["code"]} {datajs["msg"]})')
|
308
|
+
|
309
|
+
@app.post("/handle_add_click")
|
310
|
+
async def handle_add_click(symbol: str = Form(...), close: str = Form(...)):
|
311
|
+
row = bitunix.bitunixSignal.positiondf.loc[bitunix.bitunixSignal.positiondf['symbol'] == symbol]
|
312
|
+
if not row.empty:
|
313
|
+
balance = float(bitunix.bitunixSignal.portfoliodf["available"])+float(bitunix.bitunixSignal.portfoliodf["crossUnrealizedPNL"])
|
314
|
+
qty= str(balance * (float(settings.ORDER_AMOUNT_PERCENTAGE) / 100) / float(close) * int(settings.LEVERAGE))
|
315
|
+
datajs = await bitunix.bitunixApi.PlaceOrder(symbol,qty,close,row['side'].values[0])
|
316
|
+
bitunix.bitunixSignal.notifications.add_notification(f'adding {row["side"].values[0]} {qty} {symbol} @ {close} ({datajs["code"]} {datajs["msg"]})')
|
317
|
+
|
318
|
+
@app.post("/handle_sell_click")
|
319
|
+
async def handle_sell_click(symbol: str = Form(...), close: str = Form(...)):
|
320
|
+
balance = bitunix.bitunixSignal.get_portfolio_tradable_balance()
|
321
|
+
qty= str(balance * (float(settings.ORDER_AMOUNT_PERCENTAGE) / 100) / float(close) * int(settings.LEVERAGE))
|
322
|
+
datajs = await bitunix.bitunixApi.PlaceOrder(symbol,qty,close,'SELL')
|
323
|
+
bitunix.bitunixSignal.notifications.add_notification(f'Selling {qty} {symbol} @ {close} ({datajs["code"]} {datajs["msg"]})')
|
324
|
+
|
325
|
+
@app.post("/handle_reduce_click")
|
326
|
+
async def handle_reduce_click(symbol: str = Form(...), positionId: str = Form(...), qty: str = Form(...), close: str = Form(...)):
|
327
|
+
row = bitunix.bitunixSignal.positiondf.loc[bitunix.bitunixSignal.positiondf['symbol'] == symbol]
|
328
|
+
if not row.empty:
|
329
|
+
balance = bitunix.bitunixSignal.get_portfolio_tradable_balance()
|
330
|
+
qty= str(float(qty) / 2)
|
331
|
+
datajs = await bitunix.bitunixApi.PlaceOrder(symbol,qty,close,'BUY' if row['side'].values[0]=='SELL' else 'SELL',positionId=positionId, reduceOnly=True)
|
332
|
+
bitunix.bitunixSignal.notifications.add_notification(f'reducing {row["side"]} {qty} {symbol} @ {close} ({datajs["code"]} {datajs["msg"]})')
|
333
|
+
|
334
|
+
@app.post("/handle_charts_click")
|
335
|
+
async def handle_charts_click(symbol: str = Form(...)):
|
336
|
+
chart_url = f"/charts?symbol={symbol}"
|
337
|
+
return JSONResponse(content={"message": chart_url})
|
338
|
+
|
339
|
+
# when charts page requested (multiple period)
|
340
|
+
@app.get("/charts", response_class=HTMLResponse)
|
341
|
+
async def show_charts(request: Request, symbol: str):
|
342
|
+
return templates.TemplateResponse({"request": request, "data": symbol}, "charts.html")
|
343
|
+
|
344
|
+
# when charts page opened (multiple period)
|
345
|
+
@app.websocket("/wscharts")
|
346
|
+
async def websocket_endpoint(websocket: WebSocket):
|
347
|
+
await asyncio.create_task(wscharts(websocket))
|
348
|
+
|
349
|
+
async def wscharts(websocket):
|
350
|
+
query_params = websocket.query_params
|
351
|
+
ticker = query_params.get("ticker")
|
352
|
+
|
353
|
+
try:
|
354
|
+
logger.info("local charts page WebSocket connection opened")
|
355
|
+
|
356
|
+
await websocket.accept()
|
357
|
+
bitunix.websocket_connections.add(websocket)
|
358
|
+
|
359
|
+
queue = asyncio.Queue()
|
360
|
+
queueTask = asyncio.create_task(send_data_queue(websocket, queue))
|
361
|
+
|
362
|
+
while True:
|
363
|
+
stime=time.time()
|
364
|
+
try:
|
365
|
+
|
366
|
+
# Handle incoming ping messages
|
367
|
+
await asyncio.create_task(send_pong(websocket,queue))
|
368
|
+
|
369
|
+
if ticker in bitunix.bitunixSignal.tickerObjects.symbols():
|
370
|
+
bars=settings.BARS
|
371
|
+
chart1m=list(bitunix.bitunixSignal.tickerObjects.get(ticker).get_interval_ticks('1m').get_data()[-bars:])
|
372
|
+
chart5m=list(bitunix.bitunixSignal.tickerObjects.get(ticker).get_interval_ticks('5m').get_data()[-bars:])
|
373
|
+
chart15m=list(bitunix.bitunixSignal.tickerObjects.get(ticker).get_interval_ticks('15m').get_data()[-bars:])
|
374
|
+
chart1h=list(bitunix.bitunixSignal.tickerObjects.get(ticker).get_interval_ticks('1h').get_data()[-bars:])
|
375
|
+
chart1d=list(bitunix.bitunixSignal.tickerObjects.get(ticker).get_interval_ticks('1d').get_data()[-bars:])
|
376
|
+
buysell=list(bitunix.bitunixSignal.tickerObjects.get(ticker).trades)
|
377
|
+
close=bitunix.bitunixSignal.tickerObjects.get(ticker).get_last()
|
378
|
+
|
379
|
+
data = {
|
380
|
+
"symbol": ticker,
|
381
|
+
"close":close,
|
382
|
+
"chart1m":chart1m,
|
383
|
+
"chart5m":chart5m,
|
384
|
+
"chart15m":chart15m,
|
385
|
+
"chart1h":chart1h,
|
386
|
+
"chart1d":chart1d,
|
387
|
+
"buysell": buysell,
|
388
|
+
"ema_study": settings.EMA_STUDY,
|
389
|
+
"ema_chart": settings.EMA_CHART,
|
390
|
+
"macd_study": settings.MACD_STUDY,
|
391
|
+
"macd_chart": settings.MACD_CHART,
|
392
|
+
"bbm_study": settings.BBM_STUDY,
|
393
|
+
"bbm_chart": settings.BBM_CHART,
|
394
|
+
"rsi_study": settings.RSI_STUDY,
|
395
|
+
"rsi_chart": settings.RSI_CHART,
|
396
|
+
}
|
397
|
+
|
398
|
+
await queue.put(json.dumps(data))
|
399
|
+
|
400
|
+
except WebSocketDisconnect:
|
401
|
+
bitunix.websocket_connections.remove(websocket)
|
402
|
+
logger.info("local charts page WebSocket connection closed")
|
403
|
+
break
|
404
|
+
except Exception as e:
|
405
|
+
logger.info(f"local charts page websocket unexpected error1: {e}")
|
406
|
+
break
|
407
|
+
|
408
|
+
|
409
|
+
elapsed_time = time.time() - stime
|
410
|
+
if settings.VERBOSE_LOGGING:
|
411
|
+
logger.info(f"wscharts: elapsed time {elapsed_time}")
|
412
|
+
time_to_wait = max(0.01, bitunix.screen_refresh_interval - elapsed_time)
|
413
|
+
await asyncio.sleep(time_to_wait)
|
414
|
+
except Exception as e:
|
415
|
+
logger.info(f"local charts page websocket unexpected error2: {e}")
|
416
|
+
finally:
|
417
|
+
queueTask.cancel()
|
418
|
+
try:
|
419
|
+
await queueTask
|
420
|
+
except asyncio.CancelledError:
|
421
|
+
pass
|
422
|
+
|
423
|
+
# when modal chart page requested (current period)
|
424
|
+
@app.get("/chart", response_class=HTMLResponse)
|
425
|
+
async def chart_page(request: Request):
|
426
|
+
return templates.TemplateResponse("modal-chart.html", {"request": request})
|
427
|
+
|
428
|
+
# when chart page opened (current period)
|
429
|
+
@app.websocket("/wschart")
|
430
|
+
async def websocket_endpoint(websocket: WebSocket):
|
431
|
+
await asyncio.create_task(wschart(websocket))
|
432
|
+
|
433
|
+
async def wschart(websocket):
|
434
|
+
query_params = websocket.query_params
|
435
|
+
ticker = query_params.get("ticker")
|
436
|
+
|
437
|
+
try:
|
438
|
+
logger.info("local chart page WebSocket connection opened")
|
439
|
+
|
440
|
+
await websocket.accept()
|
441
|
+
bitunix.websocket_connections.add(websocket)
|
442
|
+
|
443
|
+
queue = asyncio.Queue()
|
444
|
+
queueTask = asyncio.create_task(send_data_queue(websocket, queue))
|
445
|
+
|
446
|
+
while True:
|
447
|
+
stime=time.time()
|
448
|
+
try:
|
449
|
+
|
450
|
+
# Handle incoming ping messages
|
451
|
+
await asyncio.create_task(send_pong(websocket,queue))
|
452
|
+
|
453
|
+
if ticker in bitunix.bitunixSignal.tickerObjects.symbols():
|
454
|
+
period=settings.OPTION_MOVING_AVERAGE
|
455
|
+
bars=settings.BARS
|
456
|
+
chart=list(bitunix.bitunixSignal.tickerObjects.get(ticker).get_interval_ticks(period).get_data()[-bars:])
|
457
|
+
buysell=list(bitunix.bitunixSignal.tickerObjects.get(ticker).trades)
|
458
|
+
close=bitunix.bitunixSignal.tickerObjects.get(ticker).get_last()
|
459
|
+
data = {
|
460
|
+
"symbol": ticker,
|
461
|
+
"close":close,
|
462
|
+
"chart":chart,
|
463
|
+
"buysell": buysell,
|
464
|
+
"period": period,
|
465
|
+
"ema_study": settings.EMA_STUDY,
|
466
|
+
"ema_chart": settings.EMA_CHART,
|
467
|
+
"macd_study": settings.MACD_STUDY,
|
468
|
+
"macd_chart": settings.MACD_CHART,
|
469
|
+
"bbm_study": settings.BBM_STUDY,
|
470
|
+
"bbm_chart": settings.BBM_CHART,
|
471
|
+
"rsi_study": settings.RSI_STUDY,
|
472
|
+
"rsi_chart": settings.RSI_CHART,
|
473
|
+
}
|
474
|
+
|
475
|
+
await queue.put(json.dumps(data))
|
476
|
+
|
477
|
+
except WebSocketDisconnect:
|
478
|
+
bitunix.websocket_connections.remove(websocket)
|
479
|
+
logger.info("local chart page WebSocket connection closed")
|
480
|
+
break
|
481
|
+
except Exception as e:
|
482
|
+
logger.info(f"local chart page websocket unexpected error1: {e}")
|
483
|
+
break
|
484
|
+
|
485
|
+
|
486
|
+
elapsed_time = time.time() - stime
|
487
|
+
if settings.VERBOSE_LOGGING:
|
488
|
+
logger.info(f"wschart: elapsed time {elapsed_time}")
|
489
|
+
time_to_wait = max(0.01, bitunix.screen_refresh_interval - elapsed_time)
|
490
|
+
await asyncio.sleep(time_to_wait)
|
491
|
+
except Exception as e:
|
492
|
+
logger.info(f"local chart page websocket unexpected error2: {e}")
|
493
|
+
finally:
|
494
|
+
queueTask.cancel()
|
495
|
+
try:
|
496
|
+
await queueTask
|
497
|
+
except asyncio.CancelledError:
|
498
|
+
pass
|
499
|
+
|
500
|
+
@app.get("/config", response_class=HTMLResponse)
|
501
|
+
async def config_page(request: Request):
|
502
|
+
return templates.TemplateResponse("modal-config.html", {"request": request})
|
503
|
+
|
504
|
+
@app.get("/get-config")
|
505
|
+
async def get_env_variables():
|
506
|
+
config = read_config(CONFIG_FILE) # Load .env variables
|
507
|
+
return JSONResponse(content=config)
|
508
|
+
|
509
|
+
# Save updated environment variables
|
510
|
+
@app.post("/save-config")
|
511
|
+
async def save_env_variable(key: str = Form(...), value: str = Form(...)):
|
512
|
+
global settings
|
513
|
+
# Read the existing configuration
|
514
|
+
config = read_config(CONFIG_FILE)
|
515
|
+
# Temporarily update the config dictionary for validation
|
516
|
+
config[key] = value
|
517
|
+
try:
|
518
|
+
# Test the new configuration using the Settings class
|
519
|
+
temp_settings = Settings(**config)
|
520
|
+
# Write the valid configuration back to the file
|
521
|
+
write_config(CONFIG_FILE, config)
|
522
|
+
settings = temp_settings
|
523
|
+
await bitunix.update_settings(settings)
|
524
|
+
await asyncio.create_task(get_server_states(app, settings))
|
525
|
+
bitunix.bitunixSignal.notifications.add_notification(f"Updated {key} = {value} successfully")
|
526
|
+
return {"message": f"Updated {key} = {value} successfully"}
|
527
|
+
except ValidationError as e:
|
528
|
+
bitunix.bitunixSignal.notifications.add_notification(f"{key} = {value} validation failed {e.errors()}")
|
529
|
+
return {"error": f"Validation failed, details: {e.errors()}"}
|
530
|
+
|
531
|
+
def read_config(file_path):
|
532
|
+
"""Read the config file into a dictionary."""
|
533
|
+
config = {}
|
534
|
+
if os.path.exists(file_path):
|
535
|
+
with open(file_path, "r") as file:
|
536
|
+
for line in file:
|
537
|
+
if "=" in line:
|
538
|
+
key, value = line.strip().split("=", 1)
|
539
|
+
config[key] = value
|
540
|
+
return config
|
541
|
+
|
542
|
+
def write_config(file_path, config):
|
543
|
+
"""Write the dictionary back to the config file."""
|
544
|
+
with open(file_path, "w") as file:
|
545
|
+
for key, value in config.items():
|
546
|
+
file.write(f"{key}={value}\n")
|
547
|
+
|
548
|
+
@app.get("/logs", response_class=HTMLResponse)
|
549
|
+
async def log_page(request: Request):
|
550
|
+
return templates.TemplateResponse("modal-logs.html", {"request": request})
|
551
|
+
|
552
|
+
@app.websocket("/wslogs")
|
553
|
+
async def websocket_endpoint(websocket: WebSocket):
|
554
|
+
await websocket.accept() # Accept the WebSocket connection
|
555
|
+
await stream_log_file(websocket)
|
556
|
+
|
557
|
+
async def stream_log_file(websocket: WebSocket):
|
558
|
+
try:
|
559
|
+
with open(LOG_FILE, "r") as log_file:
|
560
|
+
# Read all lines into memory
|
561
|
+
lines = log_file.readlines()
|
562
|
+
# Determine starting point: offset_from_end
|
563
|
+
start_index = max(len(lines) - 100, 0)
|
564
|
+
for line in lines[start_index:]: # Yield existing lines from offset
|
565
|
+
await websocket.send_text(line)
|
566
|
+
# Stream new lines added to the file
|
567
|
+
log_file.seek(0, 2) # Go to the end of the file
|
568
|
+
while True:
|
569
|
+
line = log_file.readline()
|
570
|
+
if not line:
|
571
|
+
await asyncio.sleep(0.1) # Wait briefly if no new line is added
|
572
|
+
continue
|
573
|
+
await websocket.send_text(line)
|
574
|
+
except WebSocketDisconnect:
|
575
|
+
print("log Client disconnected. Stopping the log stream.")
|
576
|
+
|
577
|
+
|
578
|
+
def main():
|
579
|
+
global bitunix
|
580
|
+
bitunix = bitunix(PASSWORD, API_KEY, SECRET_KEY, settings)
|
581
|
+
bitunix.bitunixSignal.notifications.add_notification(f"Starting....................")
|
582
|
+
import uvicorn
|
583
|
+
if settings.VERBOSE_LOGGING:
|
584
|
+
llevel = "debug"
|
585
|
+
else:
|
586
|
+
llevel = "error"
|
587
|
+
config1 = uvicorn.Config(app, host=HOST, port=8000, log_level=llevel, reload=False)
|
588
|
+
server = uvicorn.Server(config1)
|
589
|
+
server.run()
|
590
|
+
|
591
|
+
if __name__ == '__main__':
|
592
|
+
main()
|
593
|
+
|
@@ -0,0 +1,90 @@
|
|
1
|
+
from pydantic import Field, SecretStr, validator
|
2
|
+
from pydantic_settings import BaseSettings
|
3
|
+
import os
|
4
|
+
|
5
|
+
class Settings(BaseSettings):
|
6
|
+
# Start autotrading on start
|
7
|
+
AUTOTRADE: bool = Field(default=False)
|
8
|
+
|
9
|
+
# Trading Parameters
|
10
|
+
LEVERAGE: int = Field(default=20, ge=1, le=100)
|
11
|
+
THRESHOLD: float = Field(default=5.0, ge=0.0)
|
12
|
+
MIN_VOLUME: int = Field(default=10_000_000, ge=0)
|
13
|
+
ORDER_AMOUNT_PERCENTAGE: float = Field(default=0.01, ge=0.0, le=100)
|
14
|
+
MAX_AUTO_TRADES: int = Field(default=10, ge=0)
|
15
|
+
PROFIT_AMOUNT: float = Field(default=0.25, ge=0.0)
|
16
|
+
LOSS_AMOUNT: float = Field(default=5.0, ge=0.0)
|
17
|
+
OPTION_MOVING_AVERAGE: str = Field(default="1h")
|
18
|
+
BARS: int = Field(default=100, ge=1)
|
19
|
+
|
20
|
+
# Technical Indicators Parameters
|
21
|
+
MA_FAST: int = Field(default=12, ge=1)
|
22
|
+
MA_MEDIUM: int = Field(default=26, ge=1)
|
23
|
+
MA_SLOW: int = Field(default=50, ge=1)
|
24
|
+
RSI_FAST: int = Field(default=6, ge=1)
|
25
|
+
RSI_SLOW: int = Field(default=24, ge=1)
|
26
|
+
BBM_PERIOD: int = Field(default=20, ge=1)
|
27
|
+
BBM_STD: int = Field(default=2, ge=1)
|
28
|
+
MACD_PERIOD: int = Field(default=9, ge=1)
|
29
|
+
MACD_SHORT: int = Field(default=12, ge=1)
|
30
|
+
MACD_LONG: int = Field(default=26, ge=1)
|
31
|
+
ADX_PERIOD: int = Field(default=14, ge=1)
|
32
|
+
|
33
|
+
# Technical Indicators
|
34
|
+
OPEN_ON_ANY_SIGNAL: bool = Field(default=True)
|
35
|
+
|
36
|
+
EMA_CHART: bool = Field(default=True)
|
37
|
+
EMA_STUDY: bool = Field(default=True)
|
38
|
+
EMA_CROSSING: bool = Field(default=False)
|
39
|
+
EMA_CHECK_ON_OPEN: bool = Field(default=True)
|
40
|
+
EMA_CHECK_ON_CLOSE: bool = Field(default=True)
|
41
|
+
|
42
|
+
MACD_CHART: bool = Field(default=False)
|
43
|
+
MACD_STUDY: bool = Field(default=True)
|
44
|
+
MACD_CROSSING: bool = Field(default=False)
|
45
|
+
MACD_CHECK_ON_OPEN: bool = Field(default=False)
|
46
|
+
MACD_CHECK_ON_CLOSE: bool = Field(default=False)
|
47
|
+
|
48
|
+
BBM_CHART: bool = Field(default=False)
|
49
|
+
BBM_STUDY: bool = Field(default=True)
|
50
|
+
BBM_CROSSING: bool = Field(default=False)
|
51
|
+
BBM_CHECK_ON_OPEN: bool = Field(default=False)
|
52
|
+
BBM_CHECK_ON_CLOSE: bool = Field(default=False)
|
53
|
+
|
54
|
+
RSI_CHART: bool = Field(default=False)
|
55
|
+
RSI_STUDY: bool = Field(default=True)
|
56
|
+
RSI_CROSSING: bool = Field(default=False)
|
57
|
+
RSI_CHECK_ON_OPEN: bool = Field(default=False)
|
58
|
+
RSI_CHECK_ON_CLOSE: bool = Field(default=False)
|
59
|
+
|
60
|
+
ADX_STUDY: bool = Field(default=True)
|
61
|
+
ADX_CHECK_ON_OPEN: bool = Field(default=False)
|
62
|
+
ADX_CHECK_ON_CLOSE: bool = Field(default=False)
|
63
|
+
|
64
|
+
CANDLE_TREND_STUDY: bool = Field(default=True)
|
65
|
+
CANDLE_TREND_CHECK_ON_OPEN: bool = Field(default=False)
|
66
|
+
CANDLE_TREND_CHECK_ON_CLOSE: bool = Field(default=False)
|
67
|
+
|
68
|
+
# Time Intervals
|
69
|
+
SCREEN_REFRESH_INTERVAL: int = Field(default=1, ge=1)
|
70
|
+
SIGNAL_CHECK_INTERVAL: int = Field(default=15, ge=1)
|
71
|
+
PORTFOLIO_API_INTERVAL: int = Field(default=3, ge=1)
|
72
|
+
PENDING_POSITIONS_API_INTERVAL: int = Field(default=3, ge=1)
|
73
|
+
PENDING_ORDERS_API_INTERVAL: int = Field(default=3, ge=1)
|
74
|
+
TRADE_HISTORY_API_INTERVAL: int = Field(default=3, ge=1)
|
75
|
+
POSITION_HISTORY_API_INTERVAL: int = Field(default=3, ge=1)
|
76
|
+
TICKER_DATA_API_INTERVAL: int = Field(default=30, ge=1)
|
77
|
+
PUBLIC_WEBSOCKET_RESTART_INTERVAL: int = Field(default=10_800, ge=1)
|
78
|
+
|
79
|
+
# Use websocket or API
|
80
|
+
USE_PUBLIC_WEBSOCKET: bool = Field(default=False) # If there is lagging issue then use API
|
81
|
+
|
82
|
+
# Logger
|
83
|
+
VERBOSE_LOGGING: bool = Field(default=False)
|
84
|
+
|
85
|
+
# Benchmark
|
86
|
+
BENCHMARK: bool = Field(default=False)
|
87
|
+
|
88
|
+
class Config:
|
89
|
+
# Specify the file name for loading environment variables
|
90
|
+
env_file = "bitunix_automated_crypto_trading/config.txt"
|