Skip to content

Commit f870933

Browse files
authored
Merge pull request #222 from galacticcouncil/eur_usd_stableswap
Eur usd stableswap
2 parents d81da10 + d50eb07 commit f870933

38 files changed

Lines changed: 7600 additions & 765 deletions

.github/workflows/python-app.yml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,10 +16,10 @@ jobs:
1616

1717
steps:
1818
- uses: actions/checkout@v4
19-
- name: Set up Python 3.10
19+
- name: Set up Python 3.13.5
2020
uses: actions/setup-python@v5
2121
with:
22-
python-version: "3.10"
22+
python-version: "3.13.5"
2323
- name: Install dependencies
2424
run: |
2525
python -m pip install --upgrade pip

.gitignore

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,3 +16,11 @@
1616
*.env
1717
/hydradx/apps/fees/data
1818
/hydradx/apps/money_market/archive
19+
/hydradx/apps/fees/acct_swaps_5_0x7279fcf9694718e1234d102825dccaf332f0ea36edf1ca7c0358c4b68260d24b.json
20+
/hydradx/apps/omnipool/cached data/all_trades.txt
21+
/hydradx/other tests/cached data/all_trades.txt
22+
/hydradx/apps/omnipool/cached data/lrna_sells.txt
23+
/hydradx/other tests/cached data/lrna_sells.txt
24+
/hydradx/tests/money_market_save_test.json
25+
**/cached\ data/**
26+
/.streamlit/secrets.toml

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
The main HydraDX model can be found in "hydradx".
44

55
Installation:
6-
* Install Python 3.9. Not 3.8, it doesn't support all the features used in this code. Not 3.10, there is a compatibility issue with one of the libraries that you don't want to deal with. Link: https://www.python.org/downloads/release/python-3912/
6+
* Install Python 3.10 or higher
77
* Clone the repository and navigate to the root folder, HydraDx-simulations
88
* In terminal, enter 'pip install -r requirements.txt'
99
* Alternatively, open the project folder in PyCharm, and you'll be prompted to create a virtual environment for this project.

hydradx/apps/everything_is_collateral/other_tests.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@
1212
from hydradx.model.amm.omnipool_amm import OmnipoolState
1313
from hydradx.model.amm.agents import Agent
1414
from hydradx.model.plot_utils import color_gradient
15-
from hydradx.model.indexer_utils import get_current_omnipool, get_omnipool_trades, get_current_omnipool_assets, \
15+
from hydradx.model.indexer_utils import get_current_omnipool, get_omnipool_trades, get_current_omnipool_asset_ids, \
1616
get_asset_info_by_ids
1717

1818
st.markdown("""
Lines changed: 82 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,82 @@
1+
import random
2+
3+
from IPython.core.pylabtools import figsize
4+
from matplotlib import pyplot as plt
5+
import sys, os
6+
import streamlit as st
7+
import copy
8+
9+
from matplotlib.lines import lineStyles
10+
from streamlit import session_state
11+
12+
project_root = os.path.abspath(os.path.join(os.path.dirname(__file__), "../../../"))
13+
sys.path.append(project_root)
14+
15+
from hydradx.model import production_settings
16+
from hydradx.model.indexer_utils import get_current_omnipool_router
17+
18+
st.markdown("""
19+
<style>
20+
.stNumberInput button {
21+
display: none;
22+
}
23+
</style>
24+
""", unsafe_allow_html=True)
25+
26+
@st.cache_data(show_spinner=True)
27+
def load_omnipool_router():
28+
router = get_current_omnipool_router()
29+
return router
30+
31+
def run_app():
32+
st.session_state.router = load_omnipool_router()
33+
st.session_state.omnipool = st.session_state.router.exchanges['omnipool']
34+
omnipool = st.session_state.omnipool
35+
omnipool.asset_fee = 0
36+
omnipool.lrna_fee = 0
37+
omnipool.max_lrna_fee = 1
38+
omnipool.max_asset_fee = 1
39+
col1, col2, col3 = st.columns(3)
40+
with col1:
41+
st.session_state.tkn_buy = st.selectbox("Select token to buy:", options=omnipool.asset_list, index=omnipool.asset_list.index('HDX'))
42+
with col2:
43+
st.session_state.tkn_sell = st.selectbox("Select token to sell:", options=omnipool.asset_list, index=omnipool.asset_list.index('DOT'))
44+
with col3:
45+
omnipool.slip_factor = st.number_input("Slip factor:", min_value=0.0, max_value=10.0, value=1.0)
46+
plot_trade_sizes(st.session_state.tkn_buy, st.session_state.tkn_sell, st.session_state.router, st.session_state.omnipool)
47+
48+
def plot_trade_sizes(tkn_buy, tkn_sell, router, omnipool):
49+
trade_sizes = [10 ** (i / 5) for i in range(0, 26)]
50+
fees = []
51+
for trade_size in trade_sizes:
52+
sell_quantity = trade_size * router.price('Tether', tkn_sell)
53+
outputs = omnipool.calculate_out_given_in(tkn_buy=tkn_buy, tkn_sell=tkn_sell, sell_quantity=sell_quantity)
54+
buy_quantity, delta_qi, delta_qj, asset_fee_total, lrna_fee_total, slip_fee_buy, slip_fee_sell = outputs
55+
slip_fee_total = slip_fee_buy + slip_fee_sell
56+
slip_fee_percent = slip_fee_total / -delta_qi
57+
print(f"${trade_size:.2f} worth of {tkn_sell} sold for {tkn_buy} = {slip_fee_percent * 100:.4f}% slip fee")
58+
fees.append(slip_fee_percent)
59+
60+
fig, ax = plt.subplots(figsize=(10, 6))
61+
ax.plot(trade_sizes, [fee * 100 for fee in fees], label='Slip Fee %', color='orange')
62+
ax.set_xscale('log')
63+
ax.set_yscale('log')
64+
ax.set_xlabel(f'Value of {tkn_sell} sold for {tkn_buy} (USD)')
65+
ax.set_xticks([1, 10, 100, 1000, 10000, 100000], ['$1', '$10', '$100', '$1000', '$10k', '$100k'])
66+
ax.set_ylabel('Slip Fee (%)')
67+
ax.set_yticks(
68+
[0.0001, 0.001, 0.01, 0.1, 1] + ([10] if max(fees) * 100 > 1 else []),
69+
['0.0001%', '0.001%', '0.01%', '0.1%', '1%'] + (['10%'] if max(fees) * 100 > 1 else [])
70+
)
71+
# label the slip fee values at each dollar-value tick
72+
for i, trade_size in enumerate(trade_sizes):
73+
if trade_size in [1, 10, 100, 1000, 10000, 100000]:
74+
ax.text(trade_size, fees[i] * 100, f"{fees[i] * 100:.4f}%", fontsize=8, ha='center', va='bottom')
75+
ax.set_title('Total Slip Fee')
76+
ax.grid(True, which="both", ls="--", linewidth=0.5, color="gray")
77+
ax.legend()
78+
st.pyplot(fig)
79+
80+
st.set_page_config(layout="wide")
81+
st.title("Omnipool Slip Fees Chart")
82+
run_app()
Lines changed: 123 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,123 @@
1+
from hydradx.model.indexer_utils import query_indexer, get_blocks_at_timestamps, get_asset_info_by_ids
2+
import datetime
3+
from matplotlib import pyplot as plt
4+
from pathlib import Path
5+
import streamlit as st
6+
import json
7+
import math
8+
9+
LIQUIDITY_GRAPH_TARGET_POINTS = 1000
10+
11+
12+
def _sort_key(value):
13+
try:
14+
return int(value)
15+
except (TypeError, ValueError):
16+
return value
17+
18+
19+
def compress_liquidity_series(liquidity_series: dict, target_points: int) -> dict:
20+
if target_points <= 0:
21+
raise ValueError("target_points must be > 0")
22+
if len(liquidity_series) <= target_points:
23+
return dict(liquidity_series)
24+
25+
items = sorted(liquidity_series.items(), key=lambda item: _sort_key(item[0]))
26+
total = len(items)
27+
batch_size = max(1, math.ceil(total / target_points))
28+
compressed = {}
29+
30+
for idx in range(0, total, batch_size):
31+
batch = items[idx: idx + batch_size]
32+
first_key = batch[0][0]
33+
avg_value = sum(value for _, value in batch) / len(batch)
34+
compressed[first_key] = avg_value
35+
36+
last_key, last_value = items[-1]
37+
compressed[last_key] = last_value
38+
return compressed
39+
40+
41+
def _to_int_keyed(series: dict) -> dict:
42+
return {int(k): v for k, v in series.items()}
43+
44+
45+
def get_liquidity_over_time():
46+
dates = [
47+
datetime.datetime(2025, 11, day=i + 1) for i in range(30)
48+
] + [
49+
datetime.datetime(year=2025, month=12, day=i + 1) for i in range(31)
50+
]
51+
block_map = get_blocks_at_timestamps(dates)
52+
ordered_blocks = sorted(block_map.items(), key=lambda item: item[0])
53+
ordered_dates = [item[0] for item in ordered_blocks]
54+
block_numbers = [item[1] for item in ordered_blocks]
55+
liquidity = {}
56+
57+
if not Path.exists(Path(__file__).parent / 'cached data' / 'liquidity.json'):
58+
for i, start_block in enumerate(block_numbers[:-1]):
59+
date = dates[i]
60+
print(f"scanning {date}")
61+
blocks_per_query = 1000
62+
end_block = block_numbers[1 + 1]
63+
for block in range(start_block, end_block, blocks_per_query):
64+
query_start = block
65+
query_end = min(block + blocks_per_query, end_block)
66+
query = f"""
67+
query AssetBalancesByBlockHeight {{
68+
omnipoolAssetHistoricalData(
69+
filter: {{paraBlockHeight: {{greaterThanOrEqualTo: {query_start}, lessThan: {query_end}}}}}
70+
) {{
71+
nodes
72+
{{
73+
freeBalance
74+
assetId
75+
paraBlockHeight
76+
}}
77+
}}
78+
}}
79+
"""
80+
81+
results = query_indexer("https://galacticcouncil.squids.live/hydration-pools:unified-prod/api/graphql", query)
82+
for result in results["data"]["omnipoolAssetHistoricalData"]["nodes"]:
83+
asset_id = result["assetId"]
84+
free_balance = int(result["freeBalance"])
85+
result_block = int(result["paraBlockHeight"])
86+
if asset_id not in liquidity:
87+
liquidity[asset_id] = {}
88+
liquidity[asset_id][result_block] = free_balance
89+
90+
with open (Path(__file__).parent / 'cached data' / 'liquidity.json', 'w') as f:
91+
json.dump(liquidity, f)
92+
else:
93+
with open (Path(__file__).parent / 'cached data' / 'liquidity.json', 'r') as f:
94+
liquidity = json.load(f)
95+
96+
asset_names = {tkn.id: tkn.unique_id for tkn in get_asset_info_by_ids(list(liquidity.keys())).values()}
97+
graph_liquidity = {}
98+
for tkn in liquidity:
99+
raw_series = _to_int_keyed(liquidity[tkn])
100+
if len(raw_series) > LIQUIDITY_GRAPH_TARGET_POINTS:
101+
graph_liquidity[tkn] = compress_liquidity_series(
102+
raw_series,
103+
target_points=LIQUIDITY_GRAPH_TARGET_POINTS,
104+
)
105+
else:
106+
graph_liquidity[tkn] = dict(raw_series)
107+
graph_liquidity[tkn][block_numbers[0]] = list(raw_series.values())[0]
108+
graph_liquidity[tkn][block_numbers[-1]] = list(raw_series.values())[-1]
109+
sorted_items = sorted(graph_liquidity[tkn].items(), key=lambda item: _sort_key(item[0]))
110+
fig, ax = plt.subplots(figsize=(12, 4))
111+
ax.plot([item[0] for item in sorted_items], [item[1] for item in sorted_items])
112+
ax.set_title(f"Liquidity of {asset_names[tkn]} in Omnipool")
113+
ax.set_xlabel("Date")
114+
ax.set_ylabel("Free Balance")
115+
ax.set_xticks(block_numbers)
116+
ax.set_xticklabels([date.strftime('%Y-%m-%d') for date in ordered_dates])
117+
ax.tick_params(axis="x", labelsize=8)
118+
119+
plt.tight_layout()
120+
plt.show()
121+
st.pyplot(fig)
122+
pass
123+

0 commit comments

Comments
 (0)