Compare commits
2 Commits
43d9c333ca
...
0db0405ea1
Author | SHA1 | Date | |
---|---|---|---|
0db0405ea1 | |||
f8fe2c089f |
25
.gitea/workflows/analyze.yaml
Normal file
25
.gitea/workflows/analyze.yaml
Normal file
@ -0,0 +1,25 @@
|
|||||||
|
on:
|
||||||
|
# Trigger analysis when pushing to your main branches, and when creating a pull request.
|
||||||
|
push:
|
||||||
|
branches:
|
||||||
|
- main
|
||||||
|
- master
|
||||||
|
- develop
|
||||||
|
- 'releases/**'
|
||||||
|
run-name: ${{ gitea.actor }} is testing out Gitea Actions 🚀
|
||||||
|
name: SonarQube Scan
|
||||||
|
jobs:
|
||||||
|
sonarqube:
|
||||||
|
name: SonarQube Trigger
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- name: Checking out
|
||||||
|
uses: actions/checkout@v4
|
||||||
|
with:
|
||||||
|
# Disabling shallow clone is recommended for improving relevancy of reporting
|
||||||
|
fetch-depth: 0
|
||||||
|
- name: SonarQube Scan
|
||||||
|
uses: kitabisa/sonarqube-action@v1.2.0
|
||||||
|
with:
|
||||||
|
host: https://sonar.ailaplacelab.com
|
||||||
|
login: sqa_a3988da69583c0ae606dae085f14e6ec901675f7
|
107
app.py
107
app.py
@ -1,67 +1,72 @@
|
|||||||
from mexc_sdk import Spot
|
from fastapi import FastAPI, Request
|
||||||
|
from fastapi.responses import HTMLResponse
|
||||||
|
from fastapi.templating import Jinja2Templates
|
||||||
|
from fastapi.staticfiles import StaticFiles
|
||||||
|
from pymongo import MongoClient
|
||||||
|
from datetime import datetime
|
||||||
|
import uvicorn
|
||||||
|
from urllib.parse import quote_plus
|
||||||
from dotenv import load_dotenv
|
from dotenv import load_dotenv
|
||||||
import logging
|
|
||||||
import os
|
import os
|
||||||
|
import yaml
|
||||||
|
|
||||||
|
with open("coin.yaml", "r") as file:
|
||||||
|
data = yaml.safe_load(file)
|
||||||
|
interest_coins = data["interest"]
|
||||||
|
|
||||||
# Set up logging
|
|
||||||
logging.basicConfig(
|
|
||||||
level=logging.INFO, format="%(asctime)s - %(levelname)s - %(message)s"
|
|
||||||
)
|
|
||||||
logger = logging.getLogger(__name__)
|
|
||||||
|
|
||||||
# Load the environment variables from the .env file
|
|
||||||
load_dotenv()
|
load_dotenv()
|
||||||
|
|
||||||
|
app = FastAPI()
|
||||||
|
|
||||||
class TradeBot(object):
|
|
||||||
def __init__(self, api_key, api_secret):
|
|
||||||
self.client = Spot(api_key=api_key, api_secret=api_secret)
|
|
||||||
self.symbol = "BTCUSDT"
|
|
||||||
|
|
||||||
def change_symbol(self, new_symbol):
|
# Setup templates directory
|
||||||
self.symbol = new_symbol
|
templates = Jinja2Templates(directory="templates")
|
||||||
|
|
||||||
def time(self):
|
MONGO_URI = f'mongodb://{quote_plus(os.getenv("DB_USER"))}:{quote_plus(os.getenv("DB_PWD"))}@{os.getenv("DB_HOST")}:{os.getenv("DB_PORT")}'
|
||||||
# Format is in dict
|
client = MongoClient(MONGO_URI)
|
||||||
return f"Server Time: {self.client.time()['serverTime']}"
|
db = client[os.getenv("DB_NAME")] # Replace with your database name
|
||||||
|
# collection = db["XRP"]
|
||||||
|
|
||||||
def get_market_price(self, symbol=None):
|
|
||||||
"""Get current market price for a symbol"""
|
|
||||||
try:
|
|
||||||
symbol = symbol or self.symbol
|
|
||||||
ticker = self.client.ticker_price(symbol)
|
|
||||||
price = float(ticker["price"])
|
|
||||||
logger.info(f"Current {symbol} price: {price}")
|
|
||||||
return price
|
|
||||||
except Exception as e:
|
|
||||||
logger.error(f"Error getting market price: {str(e)}")
|
|
||||||
return None
|
|
||||||
|
|
||||||
def get_account_balance(self):
|
@app.get("/", response_class=HTMLResponse)
|
||||||
"""Get account balance for all assets"""
|
async def read_data(request: Request):
|
||||||
try:
|
return templates.TemplateResponse(
|
||||||
account_info = self.client.account_info()
|
request=request, name="index.html", context={"coins": interest_coins}
|
||||||
balances = account_info["balances"]
|
)
|
||||||
|
|
||||||
logger.info("Account balances:")
|
|
||||||
for balance in balances:
|
|
||||||
if float(balance["free"]) > 0 or float(balance["locked"]) > 0:
|
|
||||||
logger.info(
|
|
||||||
f"{balance['asset']}: Free={balance['free']}, Locked={balance['locked']}"
|
|
||||||
)
|
|
||||||
|
|
||||||
return balances
|
@app.get("/coin/{coin}")
|
||||||
except Exception as e:
|
async def view_coin_graph(request: Request, coin: str):
|
||||||
logger.error(f"Error getting account balance: {str(e)}")
|
if coin in interest_coins:
|
||||||
return None
|
return templates.TemplateResponse(
|
||||||
|
request=request, name="coin.html", context={"coin": coin}
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
return {"coin": "not found"}
|
||||||
|
|
||||||
|
|
||||||
|
@app.get("/data/{coin}")
|
||||||
|
async def get_data(coin):
|
||||||
|
collection = db[coin]
|
||||||
|
# Get last 100 data points, sorted by time
|
||||||
|
cursor = (
|
||||||
|
collection.find({}, {"_id": 0, "timestamp": 1, "price": 1})
|
||||||
|
.sort("timestamp", -1)
|
||||||
|
.limit(100)
|
||||||
|
)
|
||||||
|
|
||||||
|
data = list(cursor)
|
||||||
|
|
||||||
|
# Format the data for Chart.js
|
||||||
|
times = []
|
||||||
|
prices = []
|
||||||
|
|
||||||
|
for item in reversed(data): # Reverse to show oldest to newest
|
||||||
|
times.append(str(item["timestamp"]))
|
||||||
|
prices.append(float(item["price"]))
|
||||||
|
|
||||||
|
return {"times": times, "prices": prices}
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
# Access the variables
|
uvicorn.run("app:app", host="0.0.0.0", port=8000, reload=True)
|
||||||
api_key = os.getenv("API_KEY")
|
|
||||||
api_secret = os.getenv("API_SECRET")
|
|
||||||
tb = TradeBot(api_key, api_secret)
|
|
||||||
print(tb.time())
|
|
||||||
tb.get_market_price()
|
|
||||||
tb.get_account_balance()
|
|
||||||
|
9
coin.yaml
Normal file
9
coin.yaml
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
interest :
|
||||||
|
- BTC
|
||||||
|
- XRP
|
||||||
|
- DOGE
|
||||||
|
- ETH
|
||||||
|
- SOL
|
||||||
|
|
||||||
|
base:
|
||||||
|
- USDT
|
@ -3,7 +3,7 @@ cattrs==24.1.2
|
|||||||
exceptiongroup==1.2.2
|
exceptiongroup==1.2.2
|
||||||
importlib_resources==6.4.5
|
importlib_resources==6.4.5
|
||||||
jsii==1.106.0
|
jsii==1.106.0
|
||||||
mexc-sdk @ file:///mnt/c/Users/dongho/Desktop/mexc/mexc-api-sdk/dist/python/mexc_sdk-1.0.0-py3-none-any.whl
|
mexc-sdk @ ./package/mexc_sdk-1.0.0-py3-none-any.whl
|
||||||
publication==0.0.3
|
publication==0.0.3
|
||||||
python-dateutil==2.9.0.post0
|
python-dateutil==2.9.0.post0
|
||||||
python-dotenv==1.0.1
|
python-dotenv==1.0.1
|
||||||
|
153
server.py
Normal file
153
server.py
Normal file
@ -0,0 +1,153 @@
|
|||||||
|
from mexc_sdk import Spot
|
||||||
|
from dotenv import load_dotenv
|
||||||
|
import yaml
|
||||||
|
from pymongo import MongoClient
|
||||||
|
from pymongo.errors import ConnectionFailure, OperationFailure
|
||||||
|
import pytz
|
||||||
|
|
||||||
|
import logging
|
||||||
|
import os
|
||||||
|
from datetime import datetime
|
||||||
|
import time
|
||||||
|
from urllib.parse import quote_plus
|
||||||
|
|
||||||
|
|
||||||
|
# Set up logging
|
||||||
|
logging.basicConfig(
|
||||||
|
level=logging.INFO, format="%(asctime)s - %(levelname)s - %(message)s"
|
||||||
|
)
|
||||||
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
# Load the environment variables from the .env file
|
||||||
|
load_dotenv()
|
||||||
|
|
||||||
|
|
||||||
|
class MongoDBConn(object):
|
||||||
|
def __init__(self):
|
||||||
|
"""Initialize MongoDB connection parameters"""
|
||||||
|
self.username = quote_plus(os.getenv("DB_USER"))
|
||||||
|
self.password = quote_plus(os.getenv("DB_PWD"))
|
||||||
|
self.host = os.getenv("DB_HOST")
|
||||||
|
self.port = os.getenv("DB_PORT")
|
||||||
|
self.db_name = os.getenv("DB_NAME")
|
||||||
|
self.client = None
|
||||||
|
self.db = None
|
||||||
|
|
||||||
|
def connect(self):
|
||||||
|
"""Establish connection to MongoDB"""
|
||||||
|
try:
|
||||||
|
# Create connection URI
|
||||||
|
uri = f"mongodb://{self.username}:{self.password}@{self.host}:{self.port}"
|
||||||
|
|
||||||
|
# Connect to MongoDB
|
||||||
|
self.client = MongoClient(uri)
|
||||||
|
|
||||||
|
# Test the connection
|
||||||
|
self.client.admin.command("ping")
|
||||||
|
|
||||||
|
# Get database reference
|
||||||
|
self.db = self.client[self.db_name]
|
||||||
|
|
||||||
|
logger.info("Successfully connected to MongoDB")
|
||||||
|
return True
|
||||||
|
|
||||||
|
except ConnectionFailure as e:
|
||||||
|
logger.error(f"Could not connect to MongoDB: {e}")
|
||||||
|
return False
|
||||||
|
except OperationFailure as e:
|
||||||
|
logger.error(f"Authentication failed: {e}")
|
||||||
|
return False
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"An error occurred: {e}")
|
||||||
|
return False
|
||||||
|
|
||||||
|
def close(self):
|
||||||
|
"""Close the MongoDB connection"""
|
||||||
|
if self.client:
|
||||||
|
self.client.close()
|
||||||
|
logger.info("MongoDB connection closed")
|
||||||
|
|
||||||
|
def insert_coin_price(self, collection_name, document):
|
||||||
|
"""Insert a single document into a collection"""
|
||||||
|
try:
|
||||||
|
collection = self.db[collection_name]
|
||||||
|
result = collection.insert_one(document)
|
||||||
|
logger.info(f"Document inserted with ID: {result.inserted_id}")
|
||||||
|
return result.inserted_id
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"Error inserting document: {e}")
|
||||||
|
return None
|
||||||
|
|
||||||
|
|
||||||
|
class TradeBot(object):
|
||||||
|
def __init__(self, api_key, api_secret, mdb: MongoDBConn):
|
||||||
|
self.client = Spot(api_key=api_key, api_secret=api_secret)
|
||||||
|
self._load_coin()
|
||||||
|
self.timezone = pytz.timezone("Asia/Seoul")
|
||||||
|
self.mdb = mdb
|
||||||
|
|
||||||
|
def ttime(self):
|
||||||
|
# Format is in dict
|
||||||
|
return f"Server Time: {self.client.time()['serverTime']}"
|
||||||
|
|
||||||
|
def _get_current_time(self):
|
||||||
|
return datetime.now(self.timezone)
|
||||||
|
|
||||||
|
def _get_market_price(self, symbol=None):
|
||||||
|
"""Get current market price for a symbol"""
|
||||||
|
try:
|
||||||
|
symbol = symbol or self.symbol
|
||||||
|
ticker = self.client.ticker_price(symbol)
|
||||||
|
price = float(ticker["price"])
|
||||||
|
logger.info(f"Current {symbol} price: {price}")
|
||||||
|
return price
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"Error getting market price: {str(e)}")
|
||||||
|
return None
|
||||||
|
|
||||||
|
def get_all_interset_market_price(self):
|
||||||
|
for coin in self.interest_coins:
|
||||||
|
data = {}
|
||||||
|
data["price"] = self._get_market_price(symbol=coin + self.base)
|
||||||
|
data["timestamp"] = self._get_current_time()
|
||||||
|
self.mdb.insert_coin_price(collection_name=coin, document=data)
|
||||||
|
|
||||||
|
def get_account_balance(self):
|
||||||
|
"""Get account balance for all assets"""
|
||||||
|
try:
|
||||||
|
account_info = self.client.account_info()
|
||||||
|
balances = account_info["balances"]
|
||||||
|
|
||||||
|
logger.info("Account balances:")
|
||||||
|
for balance in balances:
|
||||||
|
if float(balance["free"]) > 0 or float(balance["locked"]) > 0:
|
||||||
|
logger.info(
|
||||||
|
f"{balance['asset']}: Free={balance['free']}, Locked={balance['locked']}"
|
||||||
|
)
|
||||||
|
|
||||||
|
return balances
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"Error getting account balance: {str(e)}")
|
||||||
|
return None
|
||||||
|
|
||||||
|
def _load_coin(self):
|
||||||
|
data = None
|
||||||
|
with open("coin.yaml", "r") as file:
|
||||||
|
data = yaml.safe_load(file)
|
||||||
|
self.interest_coins = data["interest"]
|
||||||
|
self.base = data["base"][0]
|
||||||
|
|
||||||
|
|
||||||
|
def main(tb: TradeBot):
|
||||||
|
while True:
|
||||||
|
tb.get_all_interset_market_price()
|
||||||
|
time.sleep(5)
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
mdb = MongoDBConn()
|
||||||
|
mdb.connect()
|
||||||
|
api_key = os.getenv("API_KEY")
|
||||||
|
api_secret = os.getenv("API_SECRET")
|
||||||
|
tb = TradeBot(api_key, api_secret, mdb)
|
||||||
|
main(tb)
|
123
templates/coin.html
Normal file
123
templates/coin.html
Normal file
@ -0,0 +1,123 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<title>XRP Price Live Chart</title>
|
||||||
|
<script src="https://cdnjs.cloudflare.com/ajax/libs/Chart.js/3.7.0/chart.min.js"></script>
|
||||||
|
<style>
|
||||||
|
body {
|
||||||
|
font-family: Arial, sans-serif;
|
||||||
|
margin: 20px;
|
||||||
|
background-color: #f5f5f5;
|
||||||
|
}
|
||||||
|
.container {
|
||||||
|
max-width: 1200px;
|
||||||
|
margin: 0 auto;
|
||||||
|
background-color: white;
|
||||||
|
padding: 20px;
|
||||||
|
border-radius: 8px;
|
||||||
|
box-shadow: 0 2px 4px rgba(0,0,0,0.1);
|
||||||
|
}
|
||||||
|
h1 {
|
||||||
|
color: #333;
|
||||||
|
text-align: center;
|
||||||
|
margin-bottom: 20px;
|
||||||
|
}
|
||||||
|
.chart-container {
|
||||||
|
position: relative;
|
||||||
|
height: 60vh;
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<div class="container">
|
||||||
|
<h1>XRP Price Live Chart</h1>
|
||||||
|
<div class="chart-container">
|
||||||
|
<canvas id="priceChart"></canvas>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
let chart;
|
||||||
|
|
||||||
|
async function fetchData() {
|
||||||
|
try {
|
||||||
|
const response = await fetch('/data/{{coin}}');
|
||||||
|
const data = await response.json();
|
||||||
|
return data;
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error fetching data:', error);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function updateChart() {
|
||||||
|
const data = await fetchData();
|
||||||
|
if (!data) return;
|
||||||
|
|
||||||
|
if (chart) {
|
||||||
|
chart.data.labels = data.times;
|
||||||
|
chart.data.datasets[0].data = data.prices;
|
||||||
|
chart.update();
|
||||||
|
} else {
|
||||||
|
const ctx = document.getElementById('priceChart').getContext('2d');
|
||||||
|
chart = new Chart(ctx, {
|
||||||
|
type: 'line',
|
||||||
|
data: {
|
||||||
|
labels: data.times,
|
||||||
|
datasets: [{
|
||||||
|
label: 'XRP Price (USD)',
|
||||||
|
data: data.prices,
|
||||||
|
borderColor: 'rgb(75, 192, 192)',
|
||||||
|
borderWidth: 2,
|
||||||
|
fill: true,
|
||||||
|
backgroundColor: 'rgba(75, 192, 192, 0.1)',
|
||||||
|
tension: 0.1,
|
||||||
|
pointRadius: 1,
|
||||||
|
pointHoverRadius: 5
|
||||||
|
}]
|
||||||
|
},
|
||||||
|
options: {
|
||||||
|
responsive: true,
|
||||||
|
maintainAspectRatio: false,
|
||||||
|
plugins: {
|
||||||
|
legend: {
|
||||||
|
position: 'top',
|
||||||
|
},
|
||||||
|
title: {
|
||||||
|
display: true,
|
||||||
|
text: 'XRP Price History'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
scales: {
|
||||||
|
y: {
|
||||||
|
beginAtZero: false,
|
||||||
|
ticks: {
|
||||||
|
callback: function(value) {
|
||||||
|
return '$' + value.toFixed(4);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
x: {
|
||||||
|
ticks: {
|
||||||
|
maxTicksLimit: 10,
|
||||||
|
maxRotation: 45,
|
||||||
|
minRotation: 45
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
interaction: {
|
||||||
|
intersect: false,
|
||||||
|
mode: 'index'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update every 5 seconds
|
||||||
|
updateChart();
|
||||||
|
setInterval(updateChart, 5000);
|
||||||
|
</script>
|
||||||
|
</body>
|
||||||
|
</html>
|
41
templates/index.html
Normal file
41
templates/index.html
Normal file
@ -0,0 +1,41 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
|
<title>Billionaire yes</title>
|
||||||
|
<style>
|
||||||
|
/* Add your CSS here */
|
||||||
|
body {
|
||||||
|
margin: 0;
|
||||||
|
padding: 0;
|
||||||
|
font-family: Arial, sans-serif;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<!-- Add your content here -->
|
||||||
|
<header>
|
||||||
|
<h1>To Become Billionaire</h1>
|
||||||
|
</header>
|
||||||
|
|
||||||
|
<main>
|
||||||
|
<section>
|
||||||
|
<h2>Interested Coints Live Chart</h2>
|
||||||
|
<ul>
|
||||||
|
{% for coin in coins %}
|
||||||
|
<li><a href="/coin/{{coin}}">{{coin}}</a></li>
|
||||||
|
{% endfor %}
|
||||||
|
</ul>
|
||||||
|
</section>
|
||||||
|
</main>
|
||||||
|
|
||||||
|
<footer>
|
||||||
|
<p>© 2024 Dongho Kim</p>
|
||||||
|
</footer>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
// Add your JavaScript here
|
||||||
|
</script>
|
||||||
|
</body>
|
||||||
|
</html>
|
Reference in New Issue
Block a user