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.
Files changed (35) hide show
  1. bitunix_automated_crypto_trading/AsyncThreadRunner.py +81 -0
  2. bitunix_automated_crypto_trading/BitunixApi.py +278 -0
  3. bitunix_automated_crypto_trading/BitunixSignal.py +1099 -0
  4. bitunix_automated_crypto_trading/BitunixWebSocket.py +254 -0
  5. bitunix_automated_crypto_trading/DataFrameHtmlRenderer.py +74 -0
  6. bitunix_automated_crypto_trading/NotificationManager.py +23 -0
  7. bitunix_automated_crypto_trading/ResourceManager.py +35 -0
  8. bitunix_automated_crypto_trading/ThreadManager.py +69 -0
  9. bitunix_automated_crypto_trading/TickerManager.py +636 -0
  10. bitunix_automated_crypto_trading/__init__.py +1 -0
  11. bitunix_automated_crypto_trading/bitunix.py +593 -0
  12. bitunix_automated_crypto_trading/clearenv.py +8 -0
  13. bitunix_automated_crypto_trading/config.py +90 -0
  14. bitunix_automated_crypto_trading/config.txt +60 -0
  15. bitunix_automated_crypto_trading/logger.py +85 -0
  16. bitunix_automated_crypto_trading/sampleenv.txt +5 -0
  17. bitunix_automated_crypto_trading/static/chart.css +28 -0
  18. bitunix_automated_crypto_trading/static/chart.js +362 -0
  19. bitunix_automated_crypto_trading/static/modal.css +68 -0
  20. bitunix_automated_crypto_trading/static/modal.js +147 -0
  21. bitunix_automated_crypto_trading/static/script.js +166 -0
  22. bitunix_automated_crypto_trading/static/styles.css +118 -0
  23. bitunix_automated_crypto_trading/templates/charts.html +98 -0
  24. bitunix_automated_crypto_trading/templates/login.html +19 -0
  25. bitunix_automated_crypto_trading/templates/main.html +551 -0
  26. bitunix_automated_crypto_trading/templates/modal-chart.html +26 -0
  27. bitunix_automated_crypto_trading/templates/modal-config.html +34 -0
  28. bitunix_automated_crypto_trading/templates/modal-logs.html +15 -0
  29. {bitunix_automated_crypto_trading-2.6.0.dist-info → bitunix_automated_crypto_trading-2.6.1.dist-info}/METADATA +1 -1
  30. bitunix_automated_crypto_trading-2.6.1.dist-info/RECORD +33 -0
  31. bitunix_automated_crypto_trading-2.6.1.dist-info/top_level.txt +1 -0
  32. bitunix_automated_crypto_trading-2.6.0.dist-info/RECORD +0 -5
  33. bitunix_automated_crypto_trading-2.6.0.dist-info/top_level.txt +0 -1
  34. {bitunix_automated_crypto_trading-2.6.0.dist-info → bitunix_automated_crypto_trading-2.6.1.dist-info}/WHEEL +0 -0
  35. {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,8 @@
1
+ import os
2
+
3
+ # Clear all environment variables
4
+ for key in list(os.environ.keys()):
5
+ del os.environ[key]
6
+
7
+ # Verify that all environment variables are cleared
8
+ print(os.environ)
@@ -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"