Dead4an commited on
Commit
3e1cbc7
·
verified ·
1 Parent(s): 969a1fe

Upload 8 files

Browse files
Files changed (8) hide show
  1. .gitignore +194 -0
  2. app.py +16 -0
  3. crypto_lstm.onnx +3 -0
  4. nav/__init__.py +0 -0
  5. nav/analysis.py +48 -0
  6. nav/dashboard.py +112 -0
  7. nav/main_page.py +239 -0
  8. requirements.txt +44 -3
.gitignore ADDED
@@ -0,0 +1,194 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Byte-compiled / optimized / DLL files
2
+ __pycache__/
3
+ *.py[cod]
4
+ *$py.class
5
+
6
+ # C extensions
7
+ *.so
8
+
9
+ # Distribution / packaging
10
+ .Python
11
+ build/
12
+ develop-eggs/
13
+ dist/
14
+ downloads/
15
+ eggs/
16
+ .eggs/
17
+ lib/
18
+ lib64/
19
+ parts/
20
+ sdist/
21
+ var/
22
+ wheels/
23
+ share/python-wheels/
24
+ *.egg-info/
25
+ .installed.cfg
26
+ *.egg
27
+ MANIFEST
28
+
29
+ # PyInstaller
30
+ # Usually these files are written by a python script from a template
31
+ # before PyInstaller builds the exe, so as to inject date/other infos into it.
32
+ *.manifest
33
+ *.spec
34
+
35
+ # Installer logs
36
+ pip-log.txt
37
+ pip-delete-this-directory.txt
38
+
39
+ # Unit test / coverage reports
40
+ htmlcov/
41
+ .tox/
42
+ .nox/
43
+ .coverage
44
+ .coverage.*
45
+ .cache
46
+ nosetests.xml
47
+ coverage.xml
48
+ *.cover
49
+ *.py,cover
50
+ .hypothesis/
51
+ .pytest_cache/
52
+ cover/
53
+
54
+ # Translations
55
+ *.mo
56
+ *.pot
57
+
58
+ # Django stuff:
59
+ *.log
60
+ local_settings.py
61
+ db.sqlite3
62
+ db.sqlite3-journal
63
+
64
+ # Flask stuff:
65
+ instance/
66
+ .webassets-cache
67
+
68
+ # Scrapy stuff:
69
+ .scrapy
70
+
71
+ # Sphinx documentation
72
+ docs/_build/
73
+
74
+ # PyBuilder
75
+ .pybuilder/
76
+ target/
77
+
78
+ # Jupyter Notebook
79
+ .ipynb_checkpoints
80
+
81
+ # IPython
82
+ profile_default/
83
+ ipython_config.py
84
+
85
+ # pyenv
86
+ # For a library or package, you might want to ignore these files since the code is
87
+ # intended to run in multiple environments; otherwise, check them in:
88
+ # .python-version
89
+
90
+ # pipenv
91
+ # According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control.
92
+ # However, in case of collaboration, if having platform-specific dependencies or dependencies
93
+ # having no cross-platform support, pipenv may install dependencies that don't work, or not
94
+ # install all needed dependencies.
95
+ #Pipfile.lock
96
+
97
+ # UV
98
+ # Similar to Pipfile.lock, it is generally recommended to include uv.lock in version control.
99
+ # This is especially recommended for binary packages to ensure reproducibility, and is more
100
+ # commonly ignored for libraries.
101
+ #uv.lock
102
+
103
+ # poetry
104
+ # Similar to Pipfile.lock, it is generally recommended to include poetry.lock in version control.
105
+ # This is especially recommended for binary packages to ensure reproducibility, and is more
106
+ # commonly ignored for libraries.
107
+ # https://python-poetry.org/docs/basic-usage/#commit-your-poetrylock-file-to-version-control
108
+ #poetry.lock
109
+
110
+ # pdm
111
+ # Similar to Pipfile.lock, it is generally recommended to include pdm.lock in version control.
112
+ #pdm.lock
113
+ # pdm stores project-wide configurations in .pdm.toml, but it is recommended to not include it
114
+ # in version control.
115
+ # https://pdm.fming.dev/latest/usage/project/#working-with-version-control
116
+ .pdm.toml
117
+ .pdm-python
118
+ .pdm-build/
119
+
120
+ # PEP 582; used by e.g. github.com/David-OConnor/pyflow and github.com/pdm-project/pdm
121
+ __pypackages__/
122
+
123
+ # Celery stuff
124
+ celerybeat-schedule
125
+ celerybeat.pid
126
+
127
+ # SageMath parsed files
128
+ *.sage.py
129
+
130
+ # Environments
131
+ .env
132
+ .venv
133
+ env/
134
+ venv/
135
+ ENV/
136
+ env.bak/
137
+ venv.bak/
138
+
139
+ # Spyder project settings
140
+ .spyderproject
141
+ .spyproject
142
+
143
+ # Rope project settings
144
+ .ropeproject
145
+
146
+ # mkdocs documentation
147
+ /site
148
+
149
+ # mypy
150
+ .mypy_cache/
151
+ .dmypy.json
152
+ dmypy.json
153
+
154
+ # Pyre type checker
155
+ .pyre/
156
+
157
+ # pytype static type analyzer
158
+ .pytype/
159
+
160
+ # Cython debug symbols
161
+ cython_debug/
162
+
163
+ # PyCharm
164
+ # JetBrains specific template is maintained in a separate JetBrains.gitignore that can
165
+ # be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore
166
+ # and can be added to the global gitignore or merged into this file. For a more nuclear
167
+ # option (not recommended) you can uncomment the following to ignore the entire idea folder.
168
+ #.idea/
169
+
170
+ # Abstra
171
+ # Abstra is an AI-powered process automation framework.
172
+ # Ignore directories containing user credentials, local state, and settings.
173
+ # Learn more at https://abstra.io/docs
174
+ .abstra/
175
+
176
+ # Visual Studio Code
177
+ # Visual Studio Code specific template is maintained in a separate VisualStudioCode.gitignore
178
+ # that can be found at https://github.com/github/gitignore/blob/main/Global/VisualStudioCode.gitignore
179
+ # and can be added to the global gitignore or merged into this file. However, if you prefer,
180
+ # you could uncomment the following to ignore the enitre vscode folder
181
+ # .vscode/
182
+
183
+ # Ruff stuff:
184
+ .ruff_cache/
185
+
186
+ # PyPI configuration file
187
+ .pypirc
188
+
189
+ # Cursor
190
+ # Cursor is an AI-powered code editor. `.cursorignore` specifies files/directories to
191
+ # exclude from AI features like autocomplete and code analysis. Recommended for sensitive data
192
+ # refer to https://docs.cursor.com/context/ignore-files
193
+ .cursorignore
194
+ .cursorindexingignore
app.py ADDED
@@ -0,0 +1,16 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """Точка входа в приложение."""
2
+ import streamlit as st
3
+ from nav import analysis, main_page, dashboard
4
+
5
+
6
+ st.set_page_config(page_title="Home", layout="wide")
7
+
8
+ pages = [
9
+ st.Page("./nav/main_page.py", title="Главная"),
10
+ st.Page("./nav/dashboard.py", title="Дашборд"),
11
+ st.Page("./nav/analysis.py", title="Анализ")
12
+ ]
13
+
14
+ if __name__ == "__main__":
15
+ selected_page = st.navigation(pages, position="sidebar")
16
+ selected_page.run()
crypto_lstm.onnx ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:278fc646348c3b4219a6c4669f2a8c54ba8f1c4e6526f3f527d3a8c5d53a252b
3
+ size 3403625
nav/__init__.py ADDED
File without changes
nav/analysis.py ADDED
@@ -0,0 +1,48 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """Страница анализа с получением прогноза модели."""
2
+ import streamlit as st
3
+ import onnxruntime as ort
4
+ import numpy as np
5
+ from nav.dashboard import get_coin_klines, plot_klines
6
+ from nav.main_page import get_ticker_info
7
+
8
+
9
+ def init_page():
10
+ """Инициализирует страницу."""
11
+ st.header("Анализ")
12
+
13
+ # данные берутся за последние 72 часа с интервалом 1 час
14
+ klines_df = get_coin_klines("BTC", interval=60, limit=72)
15
+ plot_klines("BTC", klines_df)
16
+ last_price = get_ticker_info("BTCUSDT")["last_price"]
17
+ st.metric("Текущая цена", f"{last_price:.2f}$", border=True)
18
+
19
+ # отбрасываем временные метки и добавляем +1 размерность к данным
20
+ # (это особенность работы модели)
21
+ klines_df = klines_df.drop("Timestamp", axis=1)
22
+ klines_df = klines_df.to_numpy(dtype=np.float32)
23
+ klines_df = np.expand_dims(klines_df, axis=0)
24
+
25
+ # получение прогноза от модели и вывод
26
+ model = load_model()
27
+ predict = model.run(["output"], {"input": klines_df})
28
+ predict = inverse_scale(predict[0][0])
29
+
30
+ st.metric("Прогноз цены открытия на следующий час", f"{predict:.2f}$",
31
+ delta=f"{(predict - last_price):.2f}$", border=True)
32
+
33
+ def load_model():
34
+ """Загружает прудобученную LSTM-модель."""
35
+ sess = ort.InferenceSession("crypto_lstm.onnx")
36
+ return sess
37
+
38
+ def inverse_scale(data):
39
+ """Приводит прогноз модели к ненормализованному виду.
40
+ Необходимо, т.к. изначально модель обучалась на нормализованных данных
41
+ (min-max нормализация в диапазоне 0-1). Глобальный минимум и максимум взят
42
+ на основе исторических данных."""
43
+ data = data * (111805.0 - 15613.0) + 15613.0
44
+ return data
45
+
46
+
47
+ if __name__ == "__main__":
48
+ init_page()
nav/dashboard.py ADDED
@@ -0,0 +1,112 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """Страница дашборда с графиками."""
2
+ import requests
3
+ import streamlit as st
4
+ import plotly.graph_objects as go
5
+ import pandas as pd
6
+
7
+
8
+ KLINE_URL = "https://api.bybit.com/v5/market/kline"
9
+ COINS = ["BTC", "ETH"]
10
+ INTERVALS = {"День": "D", "Неделя": "W", "Месяц": "M"}
11
+
12
+
13
+ def init_page():
14
+ """Инициализирует страницу."""
15
+ st.header("Дашборд")
16
+ coin = st.sidebar.selectbox("Монета", options=COINS)
17
+ interval = st.sidebar.selectbox("Интервал", options=INTERVALS, index=1)
18
+ interval = INTERVALS[interval]
19
+
20
+ klines_df = get_coin_klines(coin, interval)
21
+
22
+ # свечной график
23
+ plot_klines(coin, klines_df)
24
+
25
+ # линейный график
26
+ plot_klines_linear(coin, klines_df)
27
+
28
+ @st.cache_data
29
+ def get_coin_klines(coin, interval, limit=1000):
30
+ """Возвращает датафрейм со свечами для указанного тикера."""
31
+ params = {
32
+ "category": "spot",
33
+ "symbol": f"{coin.upper()}USDT",
34
+ "interval": interval,
35
+ "limit": limit,
36
+ }
37
+ response = requests.get(KLINE_URL, params=params).json()
38
+ klines_df = pd.DataFrame(response["result"]["list"], columns=["Timestamp", "Open", "High", "Low", "Close", "Volume", "Turnover"])
39
+ klines_df["Timestamp"] = pd.to_datetime(klines_df["Timestamp"], unit="ms")
40
+ klines_df.drop(["Volume", "Turnover"], axis=1, inplace=True)
41
+
42
+ return klines_df
43
+
44
+ def plot_klines(coin_name, klines_df):
45
+ """Строит свечной график на основе свеч."""
46
+ kline_plot = go.Figure(data=go.Candlestick(
47
+ x=klines_df["Timestamp"],
48
+ open=klines_df["Open"],
49
+ high=klines_df["High"],
50
+ low=klines_df["Low"],
51
+ close=klines_df["Close"]
52
+ ))
53
+ kline_plot.update_layout(
54
+ title=f"Свечной график {coin_name}/USDT",
55
+ xaxis_title="Дата",
56
+ yaxis_title="Цена ($)",
57
+ yaxis={"autorange": True, "fixedrange": False},
58
+ height=800,
59
+ plot_bgcolor='rgba(0,0,0,0.15)',
60
+ xaxis=dict(
61
+ showgrid=True,
62
+ showticklabels=True
63
+ ),
64
+ hovermode='x unified',
65
+ hoverlabel=dict(
66
+ bgcolor='rgba(0,0,0,0.8)',
67
+ font_size=12
68
+ )
69
+ )
70
+
71
+ with st.container(border=True):
72
+ st.plotly_chart(kline_plot, use_container_width=True)
73
+
74
+ def plot_klines_linear(coin_name, klines_df):
75
+ """Строит линейный график на основе свеч."""
76
+ line_plot = go.Figure(data=go.Scatter(
77
+ x=klines_df["Timestamp"],
78
+ y=klines_df["High"],
79
+ line=dict(
80
+ shape='spline',
81
+ smoothing=1,
82
+ width=2,
83
+ )
84
+ ))
85
+ line_plot.update_layout(
86
+ title=f"Линейный график {coin_name}/USDT",
87
+ xaxis_title="Дата",
88
+ yaxis_title="Цена ($)",
89
+ height=600,
90
+ plot_bgcolor='rgba(0,0,0,0.15)',
91
+ xaxis=dict(
92
+ showgrid=True,
93
+ showticklabels=True
94
+ ),
95
+ yaxis=dict(
96
+ showgrid=True,
97
+ gridcolor='rgba(200,200,200,0.2)',
98
+ gridwidth=0.5,
99
+ tickprefix='$'
100
+ ),
101
+ hovermode='x unified',
102
+ hoverlabel=dict(
103
+ bgcolor='rgba(0,0,0,0.8)',
104
+ font_size=12
105
+ )
106
+ )
107
+
108
+ with st.container(border=True):
109
+ st.plotly_chart(line_plot, use_container_width=True)
110
+
111
+ if __name__ == "__main__":
112
+ init_page()
nav/main_page.py ADDED
@@ -0,0 +1,239 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """Главная страница с общей информацией."""
2
+ import requests
3
+ import streamlit as st
4
+ import pandas as pd
5
+ import plotly.graph_objects as go
6
+
7
+ from nav.dashboard import get_coin_klines
8
+
9
+
10
+ GLOBAL_MARKET_INFO_URL = "https://api.coinlore.net/api/global/"
11
+ TICKER_URL = "https://api.bybit.com/v5/market/tickers"
12
+ ORDER_BOOK_URL = "https://api.binance.com/api/v3/depth"
13
+
14
+
15
+ def init_page():
16
+ """Инициализирует страницу."""
17
+ st.header("Главная")
18
+ market_info = get_market_info()
19
+ display_market_info(market_info)
20
+
21
+ def display_market_info(market_info):
22
+ """Разметка страницы и вывод показателей."""
23
+ global_container = st.container()
24
+ common_stat_col, order_book_col, exchange_rate_col = global_container.columns([0.2, 0.4, 0.4])
25
+
26
+ # общая статистика
27
+ with common_stat_col:
28
+ st.subheader("📌 Общая статистика")
29
+ with st.container(border=True):
30
+ st.metric("Монет в обращении", market_info["coins_count"])
31
+ st.divider()
32
+ st.metric("Активных торговых пар", market_info["active_markets"])
33
+ st.divider()
34
+ st.metric("Общая капитализация", f"${market_info['total_mcap'] / 1e9:.2f} млрд")
35
+ st.divider()
36
+ st.metric("Суточный объем торгов", f"${market_info['total_volume'] / 1e9:.2f} млрд")
37
+
38
+ # курсы
39
+ with exchange_rate_col:
40
+ st.subheader("💲 Курсы криптовалют")
41
+ btc_info = get_ticker_info("BTCUSDT")
42
+ eth_info = get_ticker_info("ETHUSDT")
43
+
44
+ btc_col, eth_col = st.columns(2)
45
+ btc_price_delta = round((btc_info["last_price"] - btc_info["prev_price"]), 2)
46
+ eth_price_delta = round((eth_info["last_price"] - eth_info["prev_price"]), 2)
47
+ # BTC метрики
48
+ with btc_col:
49
+ with st.container(border=True, height=500):
50
+ st.metric("BTC/USDT", f"{btc_info['last_price']}$", delta=f"{btc_price_delta}$")
51
+ st.metric("Изменение BTC/USDT", " ", delta=f"{btc_info['price_change']}%")
52
+ st.metric("Доминирование Bitcoin (BTC)", f"{market_info['btc_d']}%")
53
+ st.divider()
54
+ plot_order_book("BTC")
55
+
56
+ # ETH метрики
57
+ with eth_col:
58
+ with st.container(border=True, height=500):
59
+ st.metric("ETH/USDT", f"{eth_info['last_price']}$", delta=f"{eth_price_delta}$")
60
+ st.metric("Изменение ETH/USDT", " ", delta=f"{eth_info['price_change']}%")
61
+ st.metric("Доминирование Ethereum (ETH)", f"{market_info['eth_d']}%")
62
+ st.divider()
63
+ plot_order_book("ETH")
64
+
65
+ # мини-графики с курсами
66
+ with order_book_col:
67
+ st.subheader("⚖️ Курсы")
68
+ plot_klines_mini("BTC", get_coin_klines("BTC", "1", 60), dynamic=btc_price_delta)
69
+ plot_klines_mini("ETH", get_coin_klines("ETH", "1", 60), dynamic=eth_price_delta)
70
+
71
+ st.divider()
72
+
73
+ change_col, max_col = st.columns(2)
74
+ with change_col:
75
+ # изменения за 24ч
76
+ st.subheader("📈 Динамика за 24 часа")
77
+ st.metric("Изменение капитализации", " ", delta=f"{market_info['mcap_change']}%", border=True)
78
+ st.metric("Изменение объема", " ", delta=f"{market_info['volume_change']}%", border=True)
79
+ st.metric("Среднее изменение цен", " ", delta=f"{market_info['avg_change_percent']}%", border=True)
80
+
81
+ # исторические максимумы
82
+ with max_col:
83
+ st.subheader("🚀 Популярные биржи")
84
+ # st.container(border=True)
85
+ st.link_button("Binance", "https://www.binance.com/ru", use_container_width=True)
86
+ st.link_button("Bybit", "https://www.bybit.com/en/", use_container_width=True)
87
+ st.link_button("OKX", "https://www.okx.com/", use_container_width=True)
88
+ st.link_button("Bitget", "https://www.bitget.com/", use_container_width=True)
89
+ st.link_button("MEXC", "https://www.mexc.com/", use_container_width=True)
90
+ st.link_button("Upbit", "https://upbit.com/", use_container_width=True)
91
+
92
+ # увеличение размера текста для дельт
93
+ st.markdown("""
94
+ <style>
95
+ [data-testid="stMetricLabel"],
96
+ [data-testid="stMetricDelta"] {
97
+ font-size: 26px !important;
98
+ }
99
+ </style>
100
+ """, unsafe_allow_html=True)
101
+
102
+ get_ticker_info("BTCUSDT")
103
+
104
+ def get_market_info():
105
+ """Возвращает общие данные о рынке криптовалют."""
106
+ response = requests.get(GLOBAL_MARKET_INFO_URL)
107
+ if response.status_code != 200:
108
+ st.write("Не удалось получить глобальные данные")
109
+
110
+ market_info = response.json()[0]
111
+ return market_info
112
+
113
+ @st.cache_data
114
+ def get_ticker_info(symbol):
115
+ """Возвращает данные о конкретном тикере."""
116
+ params = {"category": "spot", "symbol": symbol}
117
+ response = requests.get(TICKER_URL, params=params)
118
+ if response.status_code != 200:
119
+ st.write("Не удалось получить данные тикера")
120
+
121
+ response = response.json()["result"]["list"][0]
122
+ ticker_info = {
123
+ "last_price": float(response["lastPrice"]), # последняя цена продажи
124
+ "prev_price": float(response["prevPrice24h"]), # цена продажи 24ч назад
125
+ "price_change": round(float(response["price24hPcnt"]) * 100, 2), # изменение цены за 24ч (проценты)
126
+ "volume": float(response["volume24h"]) # объем торгов за 24ч
127
+ }
128
+
129
+ return ticker_info
130
+
131
+ # @st.cache_data
132
+ def get_order_book_info(symbol):
133
+ """Возвращает датафреймы спроса и предложения из книги ордеров."""
134
+ params = {"symbol": symbol, "limit": 100}
135
+ response = requests.get(ORDER_BOOK_URL, params=params)
136
+ if response.status_code != 200:
137
+ st.write("Не удалось получить данные о спросе/предложении")
138
+
139
+ response = response.json()
140
+ bids = pd.DataFrame(data=response["bids"], columns=["price", "volume"], dtype=float)
141
+ asks = pd.DataFrame(data=response["asks"], columns=["price", "volume"], dtype=float)
142
+
143
+ return bids, asks
144
+
145
+ def plot_order_book(symbol):
146
+ """Строит график баланса спроса/предложения"""
147
+ # получение данных
148
+ bids, asks = get_order_book_info(f"{symbol}USDT")
149
+ bids.sort_values("price", ascending=False, inplace=True)
150
+ asks.sort_values("price", ascending=True, inplace=True)
151
+
152
+ # кумулятивные суммы объемов
153
+ bids["volume_cumsum"] = bids["volume"].cumsum()
154
+ asks["volume_cumsum"] = asks["volume"].cumsum()
155
+
156
+ # макс. объем для нормализации
157
+ max_volume = max(bids["volume_cumsum"].max(), asks["volume_cumsum"].max())
158
+
159
+ # построение графика
160
+ fig = go.Figure()
161
+ fig.add_trace(go.Bar(
162
+ x=[bids["volume_cumsum"].iloc[-1]],
163
+ y=[""],
164
+ name="Покупка",
165
+ hoverinfo="name",
166
+ orientation="h",
167
+ marker_color="rgb(61, 213, 109)",
168
+ ))
169
+ fig.add_trace(go.Bar(
170
+ x=[asks["volume_cumsum"].iloc[-1]],
171
+ y=[""],
172
+ name="Продажа",
173
+ hoverinfo="name",
174
+ orientation="h",
175
+ marker_color="rgb(255, 75, 75)",
176
+ base=bids["volume_cumsum"].iloc[-1]
177
+ ))
178
+ fig.update_layout(
179
+ barmode="stack",
180
+ title=f"Баланс ордеров {symbol}",
181
+ height=100,
182
+ xaxis={"range": [0, max_volume * 1.1]},
183
+ showlegend=False
184
+ )
185
+ fig.update_xaxes(showticklabels=False)
186
+
187
+ st.plotly_chart(fig, use_container_width=True, config={"displayModeBar": False})
188
+
189
+ def plot_klines_mini(coin_name, klines_df, dynamic=None):
190
+ """Строит компактный гладкий линейный график для главной страницы."""
191
+ # цвет линии под динамику курса за последние сутки
192
+ if not dynamic:
193
+ dynamic = "#4b8bff"
194
+ else:
195
+ dynamic = "rgb(61, 213, 109)" if dynamic > 0 else "rgb(255, 75, 75)"
196
+
197
+ mini_plot = go.Figure(data=go.Scatter(
198
+ x=klines_df["Timestamp"],
199
+ y=klines_df["High"],
200
+ line=dict(
201
+ shape='spline', # Сглаживание линий
202
+ smoothing=1,
203
+ width=2,
204
+ color=dynamic
205
+ ),
206
+ hoverinfo='x+y',
207
+ hovertemplate='<b>%{x|%d %b %H:%M}</b><br>%{y:$.2f}<extra></extra>'
208
+ ))
209
+
210
+ mini_plot.update_layout(
211
+ title=f"{coin_name}/USDT",
212
+ margin=dict(l=20, r=20, t=40, b=20),
213
+ height=250,
214
+ plot_bgcolor='rgba(0,0,0,0)',
215
+ xaxis=dict(
216
+ showgrid=True,
217
+ showticklabels=True,
218
+ tickformat='%H:%M',
219
+ spikecolor="rgb(255, 255, 255)"
220
+ ),
221
+ yaxis=dict(
222
+ showgrid=True,
223
+ gridcolor='rgba(200,200,200,0.2)',
224
+ gridwidth=0.5,
225
+ tickprefix='$'
226
+ ),
227
+ hovermode='x unified',
228
+ hoverlabel=dict(
229
+ bgcolor='rgba(0,0,0,0.8)',
230
+ font_size=12,
231
+ )
232
+ )
233
+
234
+ with st.container(border=True):
235
+ st.plotly_chart(mini_plot, use_container_width=True, config={'displayModeBar': False})
236
+
237
+
238
+ if __name__ == "__main__":
239
+ init_page()
requirements.txt CHANGED
@@ -1,3 +1,44 @@
1
- altair
2
- pandas
3
- streamlit
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ altair==5.5.0
2
+ attrs==25.3.0
3
+ blinker==1.9.0
4
+ cachetools==6.1.0
5
+ certifi==2025.6.15
6
+ charset-normalizer==3.4.2
7
+ click==8.2.1
8
+ coloredlogs==15.0.1
9
+ flatbuffers==25.2.10
10
+ gitdb==4.0.12
11
+ GitPython==3.1.44
12
+ humanfriendly==10.0
13
+ idna==3.10
14
+ Jinja2==3.1.6
15
+ jsonschema==4.24.0
16
+ jsonschema-specifications==2025.4.1
17
+ MarkupSafe==3.0.2
18
+ mpmath==1.3.0
19
+ narwhals==1.42.1
20
+ numpy==2.3.0
21
+ onnxruntime==1.22.0
22
+ packaging==25.0
23
+ pandas==2.3.0
24
+ pillow==11.2.1
25
+ plotly==6.1.2
26
+ protobuf==6.31.1
27
+ pyarrow==20.0.0
28
+ pydeck==0.9.1
29
+ python-dateutil==2.9.0.post0
30
+ pytz==2025.2
31
+ referencing==0.36.2
32
+ requests==2.32.4
33
+ rpds-py==0.25.1
34
+ six==1.17.0
35
+ smmap==5.0.2
36
+ streamlit==1.46.0
37
+ sympy==1.14.0
38
+ tenacity==9.1.2
39
+ toml==0.10.2
40
+ tornado==6.5.1
41
+ typing_extensions==4.14.0
42
+ tzdata==2025.2
43
+ urllib3==2.5.0
44
+ watchdog==6.0.0