rosacastillo
commited on
Commit
·
f9ef62b
1
Parent(s):
be3f31a
updating all files with new mech calls functions and better trader type description
Browse files- app.py +7 -5
- data/all_trades_profitability.parquet +2 -2
- data/daily_info.parquet +2 -2
- data/service_map.pkl +2 -2
- data/tools_accuracy.csv +1 -1
- scripts/daily_data.py +14 -2
- scripts/get_mech_info.py +13 -14
- scripts/markets.py +6 -4
- scripts/mech_request_utils.py +28 -18
- scripts/nr_mech_calls.py +198 -0
- scripts/profitability.py +50 -27
- scripts/pull_data.py +5 -6
- scripts/utils.py +16 -0
- scripts/web3_utils.py +6 -7
- tabs/metrics.py +39 -9
app.py
CHANGED
@@ -223,7 +223,7 @@ with demo:
|
|
223 |
trades_df=trades_df,
|
224 |
)
|
225 |
with gr.Column(scale=1):
|
226 |
-
trade_details_text = get_trade_metrics_text()
|
227 |
|
228 |
trade_details_selector.change(
|
229 |
update_trade_details,
|
@@ -251,7 +251,7 @@ with demo:
|
|
251 |
trader_filter="Olas",
|
252 |
)
|
253 |
with gr.Column(scale=1):
|
254 |
-
trade_details_text = get_trade_metrics_text()
|
255 |
|
256 |
def update_a_trade_details(trade_detail, trade_o_details_plot):
|
257 |
new_a_plot = plot_trade_metrics(
|
@@ -287,7 +287,7 @@ with demo:
|
|
287 |
trader_filter="non_Olas",
|
288 |
)
|
289 |
with gr.Column(scale=1):
|
290 |
-
trade_details_text = get_trade_metrics_text()
|
291 |
|
292 |
def update_na_trade_details(trade_detail, trade_details_plot):
|
293 |
new_no_plot = plot_trade_metrics(
|
@@ -306,7 +306,7 @@ with demo:
|
|
306 |
if len(unknown_trades) > 0:
|
307 |
with gr.Row():
|
308 |
gr.Markdown(
|
309 |
-
"# Weekly trading metrics for trades coming from
|
310 |
)
|
311 |
with gr.Row():
|
312 |
trade_u_details_selector = gr.Dropdown(
|
@@ -323,7 +323,9 @@ with demo:
|
|
323 |
trader_filter="all",
|
324 |
)
|
325 |
with gr.Column(scale=1):
|
326 |
-
trade_details_text = get_trade_metrics_text(
|
|
|
|
|
327 |
|
328 |
def update_na_trade_details(trade_detail, trade_u_details_plot):
|
329 |
new_u_plot = plot_trade_metrics(
|
|
|
223 |
trades_df=trades_df,
|
224 |
)
|
225 |
with gr.Column(scale=1):
|
226 |
+
trade_details_text = get_trade_metrics_text(trader_type=None)
|
227 |
|
228 |
trade_details_selector.change(
|
229 |
update_trade_details,
|
|
|
251 |
trader_filter="Olas",
|
252 |
)
|
253 |
with gr.Column(scale=1):
|
254 |
+
trade_details_text = get_trade_metrics_text(trader_type="Olas")
|
255 |
|
256 |
def update_a_trade_details(trade_detail, trade_o_details_plot):
|
257 |
new_a_plot = plot_trade_metrics(
|
|
|
287 |
trader_filter="non_Olas",
|
288 |
)
|
289 |
with gr.Column(scale=1):
|
290 |
+
trade_details_text = get_trade_metrics_text("non_Olas")
|
291 |
|
292 |
def update_na_trade_details(trade_detail, trade_details_plot):
|
293 |
new_no_plot = plot_trade_metrics(
|
|
|
306 |
if len(unknown_trades) > 0:
|
307 |
with gr.Row():
|
308 |
gr.Markdown(
|
309 |
+
"# Weekly trading metrics for trades coming from Unclassified traders"
|
310 |
)
|
311 |
with gr.Row():
|
312 |
trade_u_details_selector = gr.Dropdown(
|
|
|
323 |
trader_filter="all",
|
324 |
)
|
325 |
with gr.Column(scale=1):
|
326 |
+
trade_details_text = get_trade_metrics_text(
|
327 |
+
trader_type="unclassified"
|
328 |
+
)
|
329 |
|
330 |
def update_na_trade_details(trade_detail, trade_u_details_plot):
|
331 |
new_u_plot = plot_trade_metrics(
|
data/all_trades_profitability.parquet
CHANGED
@@ -1,3 +1,3 @@
|
|
1 |
version https://git-lfs.github.com/spec/v1
|
2 |
-
oid sha256:
|
3 |
-
size
|
|
|
1 |
version https://git-lfs.github.com/spec/v1
|
2 |
+
oid sha256:ec1f80e9de64d8981ac58b91de9e17f371c0620544c9012168519a6a789b512c
|
3 |
+
size 3537818
|
data/daily_info.parquet
CHANGED
@@ -1,3 +1,3 @@
|
|
1 |
version https://git-lfs.github.com/spec/v1
|
2 |
-
oid sha256:
|
3 |
-
size
|
|
|
1 |
version https://git-lfs.github.com/spec/v1
|
2 |
+
oid sha256:fed76273653048f900faca2d612b07f42be43d076238f0dac7f30e8882a1ec1b
|
3 |
+
size 374565
|
data/service_map.pkl
CHANGED
@@ -1,3 +1,3 @@
|
|
1 |
version https://git-lfs.github.com/spec/v1
|
2 |
-
oid sha256:
|
3 |
-
size
|
|
|
1 |
version https://git-lfs.github.com/spec/v1
|
2 |
+
oid sha256:6109116eb0c946088a55420b04cc85576985cb0bef7ec47c3b2be97ee85688e8
|
3 |
+
size 90766
|
data/tools_accuracy.csv
CHANGED
@@ -1,3 +1,3 @@
|
|
1 |
version https://git-lfs.github.com/spec/v1
|
2 |
-
oid sha256:
|
3 |
size 1101
|
|
|
1 |
version https://git-lfs.github.com/spec/v1
|
2 |
+
oid sha256:33bf015940a44f02ababb579398272ffc258a48d10e16be075179f18f4a2d578
|
3 |
size 1101
|
scripts/daily_data.py
CHANGED
@@ -5,7 +5,11 @@ from profitability import (
|
|
5 |
label_trades_by_staking,
|
6 |
)
|
7 |
import pandas as pd
|
8 |
-
from nr_mech_calls import
|
|
|
|
|
|
|
|
|
9 |
|
10 |
logging.basicConfig(level=logging.INFO)
|
11 |
|
@@ -16,8 +20,16 @@ def prepare_live_metrics(
|
|
16 |
):
|
17 |
fpmmTrades = pd.read_parquet(TMP_DIR / trades_filename)
|
18 |
tools = pd.read_parquet(TMP_DIR / tools_filename)
|
|
|
|
|
|
|
|
|
|
|
|
|
19 |
print("Analysing trades...")
|
20 |
-
all_trades_df = analyse_all_traders(
|
|
|
|
|
21 |
|
22 |
# staking label
|
23 |
label_trades_by_staking(all_trades_df)
|
|
|
5 |
label_trades_by_staking,
|
6 |
)
|
7 |
import pandas as pd
|
8 |
+
from nr_mech_calls import (
|
9 |
+
create_unknown_traders_df,
|
10 |
+
compute_daily_mech_calls,
|
11 |
+
transform_to_datetime,
|
12 |
+
)
|
13 |
|
14 |
logging.basicConfig(level=logging.INFO)
|
15 |
|
|
|
20 |
):
|
21 |
fpmmTrades = pd.read_parquet(TMP_DIR / trades_filename)
|
22 |
tools = pd.read_parquet(TMP_DIR / tools_filename)
|
23 |
+
|
24 |
+
fpmmTrades["creationTimestamp"] = fpmmTrades["creationTimestamp"].apply(
|
25 |
+
lambda x: transform_to_datetime(x)
|
26 |
+
)
|
27 |
+
print("Computing the estimated mech calls dataset")
|
28 |
+
trader_mech_calls = compute_daily_mech_calls(fpmmTrades=fpmmTrades, tools=tools)
|
29 |
print("Analysing trades...")
|
30 |
+
all_trades_df = analyse_all_traders(
|
31 |
+
fpmmTrades, tools, trader_mech_calls, daily_info=True
|
32 |
+
)
|
33 |
|
34 |
# staking label
|
35 |
label_trades_by_staking(all_trades_df)
|
scripts/get_mech_info.py
CHANGED
@@ -1,7 +1,13 @@
|
|
1 |
from string import Template
|
2 |
from typing import Any
|
3 |
from datetime import datetime, timedelta, UTC
|
4 |
-
from utils import
|
|
|
|
|
|
|
|
|
|
|
|
|
5 |
import requests
|
6 |
import pandas as pd
|
7 |
import numpy as np
|
@@ -16,14 +22,6 @@ from mech_request_utils import (
|
|
16 |
)
|
17 |
from web3_utils import updating_timestamps
|
18 |
|
19 |
-
OLD_MECH_SUBGRAPH_URL = (
|
20 |
-
"https://api.thegraph.com/subgraphs/name/stakewise/ethereum-gnosis"
|
21 |
-
)
|
22 |
-
|
23 |
-
NETWORK_SUBGRAPH_URL = Template(
|
24 |
-
"""https://gateway-arbitrum.network.thegraph.com/api/${subgraph_api_key}/subgraphs/id/FxV6YUix58SpYmLBwc9gEHkwjfkqwe1X5FJQjn8nKPyA"""
|
25 |
-
)
|
26 |
-
|
27 |
SUBGRAPH_HEADERS = {
|
28 |
"Accept": "application/json, multipart/mixed",
|
29 |
"Content-Type": "application/json",
|
@@ -321,18 +319,19 @@ def get_mech_events_since_last_run():
|
|
321 |
last_block_number = get_last_block_number()
|
322 |
|
323 |
# mech requests
|
324 |
-
requests_dict, duplicatedReqId = collect_all_mech_requests(
|
325 |
from_block=last_run_block_number,
|
326 |
to_block=last_block_number,
|
327 |
filename="new_mech_requests.json",
|
328 |
)
|
329 |
-
|
330 |
# mech delivers
|
331 |
-
delivers_dict, duplicatedIds = collect_all_mech_delivers(
|
332 |
from_block=last_run_block_number,
|
333 |
to_block=last_block_number,
|
334 |
filename="new_mech_delivers.json",
|
335 |
)
|
|
|
336 |
if delivers_dict is None:
|
337 |
return None
|
338 |
# clean delivers
|
@@ -357,14 +356,14 @@ def get_mech_events_last_60_days():
|
|
357 |
earliest_block_number = get_last_60_days_block_number()
|
358 |
last_block_number = get_last_block_number()
|
359 |
# mech requests
|
360 |
-
requests_dict, duplicatedReqId = collect_all_mech_requests(
|
361 |
from_block=earliest_block_number,
|
362 |
to_block=last_block_number,
|
363 |
filename="mech_requests.json",
|
364 |
)
|
365 |
|
366 |
# mech delivers
|
367 |
-
delivers_dict, duplicatedIds = collect_all_mech_delivers(
|
368 |
from_block=earliest_block_number,
|
369 |
to_block=last_block_number,
|
370 |
filename="mech_delivers.json",
|
|
|
1 |
from string import Template
|
2 |
from typing import Any
|
3 |
from datetime import datetime, timedelta, UTC
|
4 |
+
from utils import (
|
5 |
+
SUBGRAPH_API_KEY,
|
6 |
+
measure_execution_time,
|
7 |
+
DATA_DIR,
|
8 |
+
TMP_DIR,
|
9 |
+
NETWORK_SUBGRAPH_URL,
|
10 |
+
)
|
11 |
import requests
|
12 |
import pandas as pd
|
13 |
import numpy as np
|
|
|
22 |
)
|
23 |
from web3_utils import updating_timestamps
|
24 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
25 |
SUBGRAPH_HEADERS = {
|
26 |
"Accept": "application/json, multipart/mixed",
|
27 |
"Content-Type": "application/json",
|
|
|
319 |
last_block_number = get_last_block_number()
|
320 |
|
321 |
# mech requests
|
322 |
+
requests_dict, duplicatedReqId, nr_errors = collect_all_mech_requests(
|
323 |
from_block=last_run_block_number,
|
324 |
to_block=last_block_number,
|
325 |
filename="new_mech_requests.json",
|
326 |
)
|
327 |
+
print(f"NUMBER OF MECH REQUEST ERRORS={nr_errors}")
|
328 |
# mech delivers
|
329 |
+
delivers_dict, duplicatedIds, nr_errors = collect_all_mech_delivers(
|
330 |
from_block=last_run_block_number,
|
331 |
to_block=last_block_number,
|
332 |
filename="new_mech_delivers.json",
|
333 |
)
|
334 |
+
print(f"NUMBER OF MECH DELIVER ERRORS={nr_errors}")
|
335 |
if delivers_dict is None:
|
336 |
return None
|
337 |
# clean delivers
|
|
|
356 |
earliest_block_number = get_last_60_days_block_number()
|
357 |
last_block_number = get_last_block_number()
|
358 |
# mech requests
|
359 |
+
requests_dict, duplicatedReqId, nr_errors = collect_all_mech_requests(
|
360 |
from_block=earliest_block_number,
|
361 |
to_block=last_block_number,
|
362 |
filename="mech_requests.json",
|
363 |
)
|
364 |
|
365 |
# mech delivers
|
366 |
+
delivers_dict, duplicatedIds, nr_errors = collect_all_mech_delivers(
|
367 |
from_block=earliest_block_number,
|
368 |
to_block=last_block_number,
|
369 |
filename="mech_delivers.json",
|
scripts/markets.py
CHANGED
@@ -26,7 +26,12 @@ import requests
|
|
26 |
from tqdm import tqdm
|
27 |
from typing import List, Dict
|
28 |
from utils import SUBGRAPH_API_KEY, DATA_DIR, TMP_DIR
|
29 |
-
from web3_utils import
|
|
|
|
|
|
|
|
|
|
|
30 |
from queries import (
|
31 |
FPMMS_QUERY,
|
32 |
ID_FIELD,
|
@@ -45,9 +50,6 @@ SubgraphResponseType = Dict[str, ResponseItemType]
|
|
45 |
BATCH_SIZE = 1000
|
46 |
DEFAULT_TO_TIMESTAMP = 2147483647 # around year 2038
|
47 |
DEFAULT_FROM_TIMESTAMP = 0
|
48 |
-
OMEN_SUBGRAPH_URL = Template(
|
49 |
-
"""https://gateway-arbitrum.network.thegraph.com/api/${subgraph_api_key}/subgraphs/id/9fUVQpFwzpdWS9bq5WkAnmKbNNcoBwatMR4yZq81pbbz"""
|
50 |
-
)
|
51 |
|
52 |
MAX_UINT_HEX = "0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff"
|
53 |
DEFAULT_FILENAME = "fpmms.parquet"
|
|
|
26 |
from tqdm import tqdm
|
27 |
from typing import List, Dict
|
28 |
from utils import SUBGRAPH_API_KEY, DATA_DIR, TMP_DIR
|
29 |
+
from web3_utils import (
|
30 |
+
FPMM_QS_CREATOR,
|
31 |
+
FPMM_PEARL_CREATOR,
|
32 |
+
query_omen_xdai_subgraph,
|
33 |
+
OMEN_SUBGRAPH_URL,
|
34 |
+
)
|
35 |
from queries import (
|
36 |
FPMMS_QUERY,
|
37 |
ID_FIELD,
|
|
|
50 |
BATCH_SIZE = 1000
|
51 |
DEFAULT_TO_TIMESTAMP = 2147483647 # around year 2038
|
52 |
DEFAULT_FROM_TIMESTAMP = 0
|
|
|
|
|
|
|
53 |
|
54 |
MAX_UINT_HEX = "0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff"
|
55 |
DEFAULT_FILENAME = "fpmms.parquet"
|
scripts/mech_request_utils.py
CHANGED
@@ -36,7 +36,7 @@ from tools import (
|
|
36 |
from tqdm import tqdm
|
37 |
from web3_utils import FPMM_QS_CREATOR, FPMM_PEARL_CREATOR, IPFS_POLL_INTERVAL
|
38 |
from concurrent.futures import ThreadPoolExecutor, as_completed
|
39 |
-
from utils import DATA_DIR, JSON_DATA_DIR
|
40 |
|
41 |
NUM_WORKERS = 10
|
42 |
BLOCKS_CHUNK_SIZE = 10000
|
@@ -44,7 +44,6 @@ TEXT_ALIGNMENT = 30
|
|
44 |
MINIMUM_WRITE_FILE_DELAY_SECONDS = 20
|
45 |
MECH_FROM_BLOCK_RANGE = 50000
|
46 |
IPFS_ADDRESS = "https://gateway.autonolas.tech/ipfs/"
|
47 |
-
THEGRAPH_ENDPOINT = "https://api.studio.thegraph.com/query/57238/mech/0.0.2"
|
48 |
last_write_time = 0.0
|
49 |
|
50 |
REQUESTS_QUERY_FILTER = """
|
@@ -109,10 +108,12 @@ def collect_all_mech_requests(from_block: int, to_block: int, filename: str) ->
|
|
109 |
print(f"Fetching all mech requests from {from_block} to {to_block}")
|
110 |
mech_requests = {}
|
111 |
duplicated_reqIds = []
|
112 |
-
|
|
|
113 |
client = Client(transport=transport, fetch_schema_from_transport=True)
|
114 |
|
115 |
id_gt = "0x00"
|
|
|
116 |
while True:
|
117 |
variables = {
|
118 |
"sender_not_in": [FPMM_QS_CREATOR, FPMM_PEARL_CREATOR],
|
@@ -121,9 +122,6 @@ def collect_all_mech_requests(from_block: int, to_block: int, filename: str) ->
|
|
121 |
"blockNumber_lte": str(to_block), # str
|
122 |
}
|
123 |
try:
|
124 |
-
# response = client.execute(
|
125 |
-
# gql(REQUESTS_QUERY_FILTER), variable_values=variables
|
126 |
-
# )
|
127 |
response = fetch_with_retry(client, REQUESTS_QUERY_FILTER, variables)
|
128 |
|
129 |
items = response.get("requests", [])
|
@@ -137,6 +135,8 @@ def collect_all_mech_requests(from_block: int, to_block: int, filename: str) ->
|
|
137 |
else:
|
138 |
duplicated_reqIds.append(mech_request["id"])
|
139 |
except Exception as e:
|
|
|
|
|
140 |
print(f"Error while getting the response: {e}")
|
141 |
|
142 |
id_gt = items[-1]["id"]
|
@@ -149,7 +149,7 @@ def collect_all_mech_requests(from_block: int, to_block: int, filename: str) ->
|
|
149 |
print(f"Number of requests = {len(mech_requests)}")
|
150 |
print(f"Number of duplicated req Ids = {len(duplicated_reqIds)}")
|
151 |
save_json_file(mech_requests, filename)
|
152 |
-
return mech_requests, duplicated_reqIds
|
153 |
|
154 |
|
155 |
def fetch_with_retry(client, query, variables, max_retries=5):
|
@@ -169,12 +169,14 @@ def collect_all_mech_delivers(from_block: int, to_block: int, filename: str) ->
|
|
169 |
|
170 |
mech_delivers = {}
|
171 |
duplicated_requestIds = []
|
172 |
-
|
|
|
173 |
client = Client(transport=transport, fetch_schema_from_transport=True)
|
174 |
to_block = (
|
175 |
to_block + MECH_FROM_BLOCK_RANGE
|
176 |
) # there is a delay between deliver and request
|
177 |
id_gt = ""
|
|
|
178 |
while True:
|
179 |
variables = {
|
180 |
"id_gt": id_gt,
|
@@ -182,9 +184,6 @@ def collect_all_mech_delivers(from_block: int, to_block: int, filename: str) ->
|
|
182 |
"blockNumber_lte": str(to_block), # str
|
183 |
}
|
184 |
try:
|
185 |
-
# response = client.execute(
|
186 |
-
# gql(DELIVERS_QUERY_NO_FILTER), variable_values=variables
|
187 |
-
# )
|
188 |
response = fetch_with_retry(client, DELIVERS_QUERY_NO_FILTER, variables)
|
189 |
items = response.get("delivers", [])
|
190 |
|
@@ -198,8 +197,10 @@ def collect_all_mech_delivers(from_block: int, to_block: int, filename: str) ->
|
|
198 |
duplicated_requestIds.append(mech_deliver["requestId"])
|
199 |
# we will handle the duplicated later
|
200 |
except Exception as e:
|
|
|
|
|
201 |
print(f"Error while getting the response: {e}")
|
202 |
-
return None, None
|
203 |
|
204 |
id_gt = items[-1]["id"]
|
205 |
time.sleep(IPFS_POLL_INTERVAL)
|
@@ -210,7 +211,7 @@ def collect_all_mech_delivers(from_block: int, to_block: int, filename: str) ->
|
|
210 |
print(f"Number of delivers = {len(mech_delivers)}")
|
211 |
print(f"Number of duplicated request id = {len(duplicated_requestIds)}")
|
212 |
save_json_file(mech_delivers, filename)
|
213 |
-
return mech_delivers, duplicated_requestIds
|
214 |
|
215 |
|
216 |
def collect_missing_delivers(request_id: int, block_number: int) -> Dict[str, Any]:
|
@@ -219,7 +220,8 @@ def collect_missing_delivers(request_id: int, block_number: int) -> Dict[str, An
|
|
219 |
) # there is a delay between deliver and request
|
220 |
print(f"Fetching all missing delivers from {block_number} to {to_block}")
|
221 |
mech_delivers = {}
|
222 |
-
|
|
|
223 |
client = Client(transport=transport, fetch_schema_from_transport=True)
|
224 |
|
225 |
variables = {
|
@@ -277,7 +279,7 @@ def populate_requests_ipfs_contents(
|
|
277 |
updated_dict[k] = mech_request
|
278 |
time.sleep(IPFS_POLL_INTERVAL)
|
279 |
|
280 |
-
return updated_dict
|
281 |
|
282 |
|
283 |
def populate_delivers_ipfs_contents(
|
@@ -285,6 +287,7 @@ def populate_delivers_ipfs_contents(
|
|
285 |
) -> dict:
|
286 |
"""Function to complete the delivers content info from ipfs"""
|
287 |
updated_dict = {}
|
|
|
288 |
for k in tqdm(
|
289 |
keys_to_traverse,
|
290 |
desc="Fetching IPFS contents for delivers",
|
@@ -316,6 +319,7 @@ def populate_delivers_ipfs_contents(
|
|
316 |
tqdm.write(f"Skipping {mech_request} because of JSONDecodeError")
|
317 |
continue
|
318 |
except Exception:
|
|
|
319 |
tqdm.write(
|
320 |
f"Skipping {mech_request} because of error parsing the response"
|
321 |
)
|
@@ -323,7 +327,7 @@ def populate_delivers_ipfs_contents(
|
|
323 |
updated_dict[k] = mech_request
|
324 |
time.sleep(IPFS_POLL_INTERVAL)
|
325 |
|
326 |
-
return updated_dict
|
327 |
|
328 |
|
329 |
def write_mech_events_to_file(
|
@@ -500,6 +504,7 @@ def get_ipfs_data(input_filename: str, output_filename: str):
|
|
500 |
session = create_session()
|
501 |
print("UPDATING IPFS CONTENTS OF REQUESTS")
|
502 |
# requests
|
|
|
503 |
with ThreadPoolExecutor(max_workers=NUM_WORKERS) as executor:
|
504 |
futures = []
|
505 |
for i in range(0, len(mech_requests), GET_CONTENTS_BATCH_SIZE):
|
@@ -517,12 +522,15 @@ def get_ipfs_data(input_filename: str, output_filename: str):
|
|
517 |
total=len(futures),
|
518 |
desc=f"Fetching all ipfs contents from requests ",
|
519 |
):
|
520 |
-
partial_dict = future.result()
|
|
|
521 |
updated_mech_requests.update(partial_dict)
|
522 |
|
523 |
save_json_file(updated_mech_requests, output_filename)
|
|
|
524 |
|
525 |
# delivers
|
|
|
526 |
print("UPDATING IPFS CONTENTS OF DELIVERS")
|
527 |
total_keys_to_traverse = list(updated_mech_requests.keys())
|
528 |
final_tools_content = {}
|
@@ -543,10 +551,12 @@ def get_ipfs_data(input_filename: str, output_filename: str):
|
|
543 |
total=len(futures),
|
544 |
desc=f"Fetching all ipfs contents from delivers ",
|
545 |
):
|
546 |
-
partial_dict = future.result()
|
|
|
547 |
final_tools_content.update(partial_dict)
|
548 |
|
549 |
save_json_file(final_tools_content, output_filename)
|
|
|
550 |
|
551 |
|
552 |
def only_delivers_loop():
|
|
|
36 |
from tqdm import tqdm
|
37 |
from web3_utils import FPMM_QS_CREATOR, FPMM_PEARL_CREATOR, IPFS_POLL_INTERVAL
|
38 |
from concurrent.futures import ThreadPoolExecutor, as_completed
|
39 |
+
from utils import DATA_DIR, JSON_DATA_DIR, MECH_SUBGRAPH_URL, SUBGRAPH_API_KEY
|
40 |
|
41 |
NUM_WORKERS = 10
|
42 |
BLOCKS_CHUNK_SIZE = 10000
|
|
|
44 |
MINIMUM_WRITE_FILE_DELAY_SECONDS = 20
|
45 |
MECH_FROM_BLOCK_RANGE = 50000
|
46 |
IPFS_ADDRESS = "https://gateway.autonolas.tech/ipfs/"
|
|
|
47 |
last_write_time = 0.0
|
48 |
|
49 |
REQUESTS_QUERY_FILTER = """
|
|
|
108 |
print(f"Fetching all mech requests from {from_block} to {to_block}")
|
109 |
mech_requests = {}
|
110 |
duplicated_reqIds = []
|
111 |
+
mech_subgraph_url = MECH_SUBGRAPH_URL.substitute(subgraph_api_key=SUBGRAPH_API_KEY)
|
112 |
+
transport = RequestsHTTPTransport(url=mech_subgraph_url)
|
113 |
client = Client(transport=transport, fetch_schema_from_transport=True)
|
114 |
|
115 |
id_gt = "0x00"
|
116 |
+
nr_errors = 0
|
117 |
while True:
|
118 |
variables = {
|
119 |
"sender_not_in": [FPMM_QS_CREATOR, FPMM_PEARL_CREATOR],
|
|
|
122 |
"blockNumber_lte": str(to_block), # str
|
123 |
}
|
124 |
try:
|
|
|
|
|
|
|
125 |
response = fetch_with_retry(client, REQUESTS_QUERY_FILTER, variables)
|
126 |
|
127 |
items = response.get("requests", [])
|
|
|
135 |
else:
|
136 |
duplicated_reqIds.append(mech_request["id"])
|
137 |
except Exception as e:
|
138 |
+
# counter for errors
|
139 |
+
nr_errors += 1
|
140 |
print(f"Error while getting the response: {e}")
|
141 |
|
142 |
id_gt = items[-1]["id"]
|
|
|
149 |
print(f"Number of requests = {len(mech_requests)}")
|
150 |
print(f"Number of duplicated req Ids = {len(duplicated_reqIds)}")
|
151 |
save_json_file(mech_requests, filename)
|
152 |
+
return mech_requests, duplicated_reqIds, nr_errors
|
153 |
|
154 |
|
155 |
def fetch_with_retry(client, query, variables, max_retries=5):
|
|
|
169 |
|
170 |
mech_delivers = {}
|
171 |
duplicated_requestIds = []
|
172 |
+
mech_subgraph_url = MECH_SUBGRAPH_URL.substitute(subgraph_api_key=SUBGRAPH_API_KEY)
|
173 |
+
transport = RequestsHTTPTransport(url=mech_subgraph_url)
|
174 |
client = Client(transport=transport, fetch_schema_from_transport=True)
|
175 |
to_block = (
|
176 |
to_block + MECH_FROM_BLOCK_RANGE
|
177 |
) # there is a delay between deliver and request
|
178 |
id_gt = ""
|
179 |
+
nr_errors = 0
|
180 |
while True:
|
181 |
variables = {
|
182 |
"id_gt": id_gt,
|
|
|
184 |
"blockNumber_lte": str(to_block), # str
|
185 |
}
|
186 |
try:
|
|
|
|
|
|
|
187 |
response = fetch_with_retry(client, DELIVERS_QUERY_NO_FILTER, variables)
|
188 |
items = response.get("delivers", [])
|
189 |
|
|
|
197 |
duplicated_requestIds.append(mech_deliver["requestId"])
|
198 |
# we will handle the duplicated later
|
199 |
except Exception as e:
|
200 |
+
# counter for errors
|
201 |
+
nr_errors += 1
|
202 |
print(f"Error while getting the response: {e}")
|
203 |
+
# return None, None
|
204 |
|
205 |
id_gt = items[-1]["id"]
|
206 |
time.sleep(IPFS_POLL_INTERVAL)
|
|
|
211 |
print(f"Number of delivers = {len(mech_delivers)}")
|
212 |
print(f"Number of duplicated request id = {len(duplicated_requestIds)}")
|
213 |
save_json_file(mech_delivers, filename)
|
214 |
+
return mech_delivers, duplicated_requestIds, nr_errors
|
215 |
|
216 |
|
217 |
def collect_missing_delivers(request_id: int, block_number: int) -> Dict[str, Any]:
|
|
|
220 |
) # there is a delay between deliver and request
|
221 |
print(f"Fetching all missing delivers from {block_number} to {to_block}")
|
222 |
mech_delivers = {}
|
223 |
+
mech_subgraph_url = MECH_SUBGRAPH_URL.substitute(subgraph_api_key=SUBGRAPH_API_KEY)
|
224 |
+
transport = RequestsHTTPTransport(url=mech_subgraph_url)
|
225 |
client = Client(transport=transport, fetch_schema_from_transport=True)
|
226 |
|
227 |
variables = {
|
|
|
279 |
updated_dict[k] = mech_request
|
280 |
time.sleep(IPFS_POLL_INTERVAL)
|
281 |
|
282 |
+
return updated_dict, wrong_response_count
|
283 |
|
284 |
|
285 |
def populate_delivers_ipfs_contents(
|
|
|
287 |
) -> dict:
|
288 |
"""Function to complete the delivers content info from ipfs"""
|
289 |
updated_dict = {}
|
290 |
+
errors = 0
|
291 |
for k in tqdm(
|
292 |
keys_to_traverse,
|
293 |
desc="Fetching IPFS contents for delivers",
|
|
|
319 |
tqdm.write(f"Skipping {mech_request} because of JSONDecodeError")
|
320 |
continue
|
321 |
except Exception:
|
322 |
+
errors += 1
|
323 |
tqdm.write(
|
324 |
f"Skipping {mech_request} because of error parsing the response"
|
325 |
)
|
|
|
327 |
updated_dict[k] = mech_request
|
328 |
time.sleep(IPFS_POLL_INTERVAL)
|
329 |
|
330 |
+
return updated_dict, errors
|
331 |
|
332 |
|
333 |
def write_mech_events_to_file(
|
|
|
504 |
session = create_session()
|
505 |
print("UPDATING IPFS CONTENTS OF REQUESTS")
|
506 |
# requests
|
507 |
+
nr_errors = 0
|
508 |
with ThreadPoolExecutor(max_workers=NUM_WORKERS) as executor:
|
509 |
futures = []
|
510 |
for i in range(0, len(mech_requests), GET_CONTENTS_BATCH_SIZE):
|
|
|
522 |
total=len(futures),
|
523 |
desc=f"Fetching all ipfs contents from requests ",
|
524 |
):
|
525 |
+
partial_dict, error_counter = future.result()
|
526 |
+
nr_errors += error_counter
|
527 |
updated_mech_requests.update(partial_dict)
|
528 |
|
529 |
save_json_file(updated_mech_requests, output_filename)
|
530 |
+
print(f"NUMBER OF MECH REQUEST IPFS ERRORS={nr_errors}")
|
531 |
|
532 |
# delivers
|
533 |
+
nr_deliver_errors = 0
|
534 |
print("UPDATING IPFS CONTENTS OF DELIVERS")
|
535 |
total_keys_to_traverse = list(updated_mech_requests.keys())
|
536 |
final_tools_content = {}
|
|
|
551 |
total=len(futures),
|
552 |
desc=f"Fetching all ipfs contents from delivers ",
|
553 |
):
|
554 |
+
partial_dict, error_counter = future.result()
|
555 |
+
nr_deliver_errors += error_counter
|
556 |
final_tools_content.update(partial_dict)
|
557 |
|
558 |
save_json_file(final_tools_content, output_filename)
|
559 |
+
print(f"NUMBER OF MECH DELIVERS IPFS ERRORS={nr_deliver_errors}")
|
560 |
|
561 |
|
562 |
def only_delivers_loop():
|
scripts/nr_mech_calls.py
CHANGED
@@ -1,6 +1,15 @@
|
|
1 |
import pandas as pd
|
2 |
from utils import DATA_DIR, DEFAULT_MECH_FEE
|
3 |
from tqdm import tqdm
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
4 |
|
5 |
|
6 |
def update_roi(row: pd.DataFrame) -> float:
|
@@ -12,6 +21,38 @@ def update_roi(row: pd.DataFrame) -> float:
|
|
12 |
return new_value
|
13 |
|
14 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
15 |
def create_unknown_traders_df(trades_df: pd.DataFrame) -> pd.DataFrame:
|
16 |
"""filter trades coming from non-Olas traders that are placing no mech calls"""
|
17 |
no_mech_calls_mask = (trades_df["staking"] == "non_Olas") & (
|
@@ -66,6 +107,163 @@ def update_trade_nr_mech_calls(non_agents: bool = False):
|
|
66 |
# summary_df.to_parquet(DATA_DIR / "summary_profitability.parquet", index=False)
|
67 |
|
68 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
69 |
if __name__ == "__main__":
|
70 |
# update_trade_nr_mech_calls(non_agents=True)
|
71 |
trades_df = pd.read_parquet(DATA_DIR / "all_trades_profitability.parquet")
|
|
|
1 |
import pandas as pd
|
2 |
from utils import DATA_DIR, DEFAULT_MECH_FEE
|
3 |
from tqdm import tqdm
|
4 |
+
from datetime import datetime, timezone
|
5 |
+
from typing import Dict, Any
|
6 |
+
from collections import defaultdict
|
7 |
+
from tools import IRRELEVANT_TOOLS
|
8 |
+
import re
|
9 |
+
|
10 |
+
|
11 |
+
def transform_to_datetime(x):
|
12 |
+
return datetime.fromtimestamp(int(x), tz=timezone.utc)
|
13 |
|
14 |
|
15 |
def update_roi(row: pd.DataFrame) -> float:
|
|
|
21 |
return new_value
|
22 |
|
23 |
|
24 |
+
def get_mech_statistics(mech_requests: Dict[str, Any]) -> Dict[str, Dict[str, int]]:
|
25 |
+
"""Outputs a table with Mech statistics"""
|
26 |
+
|
27 |
+
mech_statistics: Dict[str, Dict[str, int]] = defaultdict(lambda: defaultdict(int))
|
28 |
+
|
29 |
+
for mech_request in mech_requests.values():
|
30 |
+
if (
|
31 |
+
"ipfs_contents" not in mech_request
|
32 |
+
or "tool" not in mech_request["ipfs_contents"]
|
33 |
+
or "prompt" not in mech_request["ipfs_contents"]
|
34 |
+
):
|
35 |
+
continue
|
36 |
+
|
37 |
+
if mech_request["ipfs_contents"]["tool"] in IRRELEVANT_TOOLS:
|
38 |
+
continue
|
39 |
+
|
40 |
+
prompt = mech_request["ipfs_contents"]["prompt"]
|
41 |
+
prompt = prompt.replace("\n", " ")
|
42 |
+
prompt = prompt.strip()
|
43 |
+
prompt = re.sub(r"\s+", " ", prompt)
|
44 |
+
prompt_match = re.search(r"\"(.*)\"", prompt)
|
45 |
+
if prompt_match:
|
46 |
+
question = prompt_match.group(1)
|
47 |
+
else:
|
48 |
+
question = prompt
|
49 |
+
|
50 |
+
mech_statistics[question]["count"] += 1
|
51 |
+
mech_statistics[question]["fees"] += mech_request["fee"]
|
52 |
+
|
53 |
+
return mech_statistics
|
54 |
+
|
55 |
+
|
56 |
def create_unknown_traders_df(trades_df: pd.DataFrame) -> pd.DataFrame:
|
57 |
"""filter trades coming from non-Olas traders that are placing no mech calls"""
|
58 |
no_mech_calls_mask = (trades_df["staking"] == "non_Olas") & (
|
|
|
107 |
# summary_df.to_parquet(DATA_DIR / "summary_profitability.parquet", index=False)
|
108 |
|
109 |
|
110 |
+
def get_daily_mech_calls_estimation(
|
111 |
+
daily_trades: pd.DataFrame, daily_tools: pd.DataFrame
|
112 |
+
) -> list:
|
113 |
+
# for each market
|
114 |
+
daily_markets = daily_trades.title.unique()
|
115 |
+
trader = daily_trades.iloc[0].trader_address
|
116 |
+
day = daily_trades.iloc[0].creation_date
|
117 |
+
estimations = []
|
118 |
+
for market in daily_markets:
|
119 |
+
estimation_dict = {}
|
120 |
+
estimation_dict["trader_address"] = trader
|
121 |
+
estimation_dict["trading_day"] = day
|
122 |
+
# tools usage of this market
|
123 |
+
market_requests = daily_tools.loc[daily_tools["title"] == market]
|
124 |
+
# trades done on this market
|
125 |
+
market_trades = daily_trades[daily_trades["title"] == market]
|
126 |
+
mech_calls_estimation = 0
|
127 |
+
total_trades = len(market_trades)
|
128 |
+
total_requests = 0
|
129 |
+
if len(market_requests) > 0:
|
130 |
+
total_requests = len(market_requests)
|
131 |
+
mech_calls_estimation = total_requests / total_trades
|
132 |
+
estimation_dict["total_trades"] = total_trades
|
133 |
+
estimation_dict["total_mech_requests"] = total_requests
|
134 |
+
estimation_dict["market"] = market
|
135 |
+
estimation_dict["mech_calls_per_trade"] = mech_calls_estimation
|
136 |
+
estimations.append(estimation_dict)
|
137 |
+
return estimations
|
138 |
+
|
139 |
+
|
140 |
+
def compute_daily_mech_calls(
|
141 |
+
fpmmTrades: pd.DataFrame, tools: pd.DataFrame
|
142 |
+
) -> pd.DataFrame:
|
143 |
+
"""Function to compute the daily mech calls at the trader and market level"""
|
144 |
+
nr_traders = len(fpmmTrades["trader_address"].unique())
|
145 |
+
fpmmTrades["creation_timestamp"] = pd.to_datetime(fpmmTrades["creationTimestamp"])
|
146 |
+
fpmmTrades["creation_date"] = fpmmTrades["creation_timestamp"].dt.date
|
147 |
+
trades_df = trades_df.sort_values(by="creation_timestamp", ascending=True)
|
148 |
+
tools["request_time"] = pd.to_datetime(tools["request_time"])
|
149 |
+
tools["request_date"] = tools["request_time"].dt.date
|
150 |
+
tools = tools.sort_values(by="request_time", ascending=True)
|
151 |
+
all_mech_calls = []
|
152 |
+
for trader in tqdm(
|
153 |
+
fpmmTrades["trader_address"].unique(),
|
154 |
+
total=nr_traders,
|
155 |
+
desc="creating mech calls estimation based on timestamps",
|
156 |
+
):
|
157 |
+
# compute the mech calls estimations for each trader
|
158 |
+
all_trades = fpmmTrades[fpmmTrades["trader_address"] == trader]
|
159 |
+
all_tools = tools[tools["trader_address"] == trader]
|
160 |
+
trading_days = all_trades.creation_date.unique()
|
161 |
+
for trading_day in trading_days:
|
162 |
+
daily_trades = all_trades.loc[all_trades["creation_date"] == trading_day]
|
163 |
+
daily_tools = all_tools.loc[all_tools["request_date"] == trading_day]
|
164 |
+
trader_entry = {}
|
165 |
+
trader_entry["trader_address"] = trader
|
166 |
+
trader_entry["total_trades"] = len(daily_trades)
|
167 |
+
trader_entry["trading_day"] = trading_day
|
168 |
+
trader_entry["total_mech_calls"] = len(daily_tools)
|
169 |
+
all_mech_calls.append(trader_entry)
|
170 |
+
return pd.DataFrame.from_dict(all_mech_calls, orient="columns")
|
171 |
+
|
172 |
+
|
173 |
+
def compute_mech_call_estimations(
|
174 |
+
fpmmTrades: pd.DataFrame, tools: pd.DataFrame
|
175 |
+
) -> pd.DataFrame:
|
176 |
+
"""Function to compute the estimated mech calls needed per trade at the trader and market level"""
|
177 |
+
nr_traders = len(fpmmTrades["trader_address"].unique())
|
178 |
+
fpmmTrades["creation_timestamp"] = pd.to_datetime(fpmmTrades["creationTimestamp"])
|
179 |
+
fpmmTrades["creation_date"] = fpmmTrades["creation_timestamp"].dt.date
|
180 |
+
tools["request_time"] = pd.to_datetime(tools["request_time"])
|
181 |
+
tools["request_date"] = tools["request_time"].dt.date
|
182 |
+
all_estimations = []
|
183 |
+
for trader in tqdm(
|
184 |
+
fpmmTrades["trader_address"].unique(),
|
185 |
+
total=nr_traders,
|
186 |
+
desc="creating mech calls estimation dataframe",
|
187 |
+
):
|
188 |
+
# compute the mech calls estimations for each trader
|
189 |
+
all_trades = fpmmTrades[fpmmTrades["trader_address"] == trader]
|
190 |
+
all_tools = tools[tools["trader_address"] == trader]
|
191 |
+
trading_days = all_trades.creation_date.unique()
|
192 |
+
for trading_day in trading_days:
|
193 |
+
daily_trades = all_trades.loc[all_trades["creation_date"] == trading_day]
|
194 |
+
daily_tools = all_tools.loc[all_tools["request_date"] == trading_day]
|
195 |
+
daily_estimations = get_daily_mech_calls_estimation(
|
196 |
+
daily_trades=daily_trades, daily_tools=daily_tools
|
197 |
+
)
|
198 |
+
all_estimations.extend(daily_estimations)
|
199 |
+
return pd.DataFrame.from_dict(all_estimations, orient="columns")
|
200 |
+
|
201 |
+
|
202 |
+
def compute_timestamp_mech_calls(
|
203 |
+
all_trades: pd.DataFrame, all_tools: pd.DataFrame
|
204 |
+
) -> list:
|
205 |
+
"""Function to compute the mech calls based on timestamps but without repeating mech calls"""
|
206 |
+
mech_calls_contents = []
|
207 |
+
request_timestamps_used = {}
|
208 |
+
# intialize the dict with all markets
|
209 |
+
all_markets = all_trades.title.unique()
|
210 |
+
for market in all_markets:
|
211 |
+
request_timestamps_used[market] = []
|
212 |
+
|
213 |
+
for i, trade in all_trades.iterrows():
|
214 |
+
trader = trade["trader_address"]
|
215 |
+
trade_id = trade["id"]
|
216 |
+
market = trade["title"]
|
217 |
+
trade_ts = trade["creation_timestamp"]
|
218 |
+
market_requests = all_tools.loc[
|
219 |
+
(all_tools["trader_address"] == trader) & (all_tools["title"] == market)
|
220 |
+
]
|
221 |
+
# traverse market requests
|
222 |
+
total_mech_calls = 0
|
223 |
+
for mech_request in market_requests:
|
224 |
+
# check timestamp (before the trade)
|
225 |
+
request_ts = mech_request.request_time
|
226 |
+
if request_ts < trade_ts:
|
227 |
+
# check the timestamp has not been used in a previous trade
|
228 |
+
used_timestamps = request_timestamps_used[market]
|
229 |
+
if request_ts not in used_timestamps:
|
230 |
+
request_timestamps_used[market].append(request_ts)
|
231 |
+
total_mech_calls += 1
|
232 |
+
# create enty for the dataframe
|
233 |
+
mech_call_entry = {}
|
234 |
+
mech_call_entry["trader_address"] = trader
|
235 |
+
mech_call_entry["market"] = market
|
236 |
+
mech_call_entry["trade_id"] = trade_id
|
237 |
+
mech_call_entry["total_mech_calls"] = total_mech_calls
|
238 |
+
mech_calls_contents.append(mech_call_entry)
|
239 |
+
return mech_calls_contents
|
240 |
+
|
241 |
+
|
242 |
+
def compute_mech_calls_based_on_timestamps(
|
243 |
+
fpmmTrades: pd.DataFrame, tools: pd.DataFrame
|
244 |
+
) -> pd.DataFrame:
|
245 |
+
"""Function to compute the mech calls needed per trade at the trader and market level using timestamps"""
|
246 |
+
nr_traders = len(fpmmTrades["trader_address"].unique())
|
247 |
+
fpmmTrades["creation_timestamp"] = pd.to_datetime(fpmmTrades["creationTimestamp"])
|
248 |
+
fpmmTrades["creation_date"] = fpmmTrades["creation_timestamp"].dt.date
|
249 |
+
trades_df = trades_df.sort_values(by="creation_timestamp", ascending=True)
|
250 |
+
tools["request_time"] = pd.to_datetime(tools["request_time"])
|
251 |
+
tools["request_date"] = tools["request_time"].dt.date
|
252 |
+
tools = tools.sort_values(by="request_time", ascending=True)
|
253 |
+
all_mech_calls = []
|
254 |
+
for trader in tqdm(
|
255 |
+
fpmmTrades["trader_address"].unique(),
|
256 |
+
total=nr_traders,
|
257 |
+
desc="creating mech calls estimation based on timestamps",
|
258 |
+
):
|
259 |
+
# compute the mech calls estimations for each trader
|
260 |
+
all_trades = fpmmTrades[fpmmTrades["trader_address"] == trader]
|
261 |
+
all_tools = tools[tools["trader_address"] == trader]
|
262 |
+
trader_mech_calls = compute_timestamp_mech_calls(all_trades, all_tools)
|
263 |
+
all_mech_calls.extend(trader_mech_calls)
|
264 |
+
return pd.DataFrame.from_dict(all_mech_calls, orient="columns")
|
265 |
+
|
266 |
+
|
267 |
if __name__ == "__main__":
|
268 |
# update_trade_nr_mech_calls(non_agents=True)
|
269 |
trades_df = pd.read_parquet(DATA_DIR / "all_trades_profitability.parquet")
|
scripts/profitability.py
CHANGED
@@ -40,7 +40,11 @@ from utils import (
|
|
40 |
DEFAULT_MECH_FEE,
|
41 |
)
|
42 |
from staking import label_trades_by_staking
|
43 |
-
from nr_mech_calls import
|
|
|
|
|
|
|
|
|
44 |
|
45 |
DUST_THRESHOLD = 10000000000000
|
46 |
INVALID_ANSWER = -1
|
@@ -215,12 +219,14 @@ def analyse_trader(
|
|
215 |
trader_address: str,
|
216 |
fpmmTrades: pd.DataFrame,
|
217 |
tools: pd.DataFrame,
|
|
|
218 |
daily_info: bool = False,
|
219 |
) -> pd.DataFrame:
|
220 |
"""Analyse a trader's trades"""
|
|
|
|
|
221 |
# Filter trades and tools for the given trader
|
222 |
trades = fpmmTrades[fpmmTrades["trader_address"] == trader_address]
|
223 |
-
tools_usage = tools[tools["trader_address"] == trader_address]
|
224 |
|
225 |
# Prepare the DataFrame
|
226 |
trades_df = pd.DataFrame(columns=ALL_TRADES_STATS_DF_COLS)
|
@@ -238,14 +244,12 @@ def analyse_trader(
|
|
238 |
for i, trade in tqdm(trades.iterrows(), total=len(trades), desc="Analysing trades"):
|
239 |
try:
|
240 |
market_answer = trade["fpmm.currentAnswer"]
|
|
|
|
|
241 |
if not daily_info and not market_answer:
|
242 |
print(f"Skipping trade {i} because currentAnswer is NaN")
|
243 |
continue
|
244 |
# Parsing and computing shared values
|
245 |
-
|
246 |
-
creation_timestamp_utc = datetime.datetime.fromtimestamp(
|
247 |
-
int(trade["creationTimestamp"]), tz=datetime.timezone.utc
|
248 |
-
)
|
249 |
collateral_amount = wei_to_unit(float(trade["collateralAmount"]))
|
250 |
fee_amount = wei_to_unit(float(trade["feeAmount"]))
|
251 |
outcome_tokens_traded = wei_to_unit(float(trade["outcomeTokensTraded"]))
|
@@ -280,25 +284,23 @@ def analyse_trader(
|
|
280 |
earnings = outcome_tokens_traded
|
281 |
winner_trade = True
|
282 |
|
283 |
-
# Compute mech calls
|
284 |
-
if
|
285 |
-
|
286 |
-
|
|
|
|
|
287 |
else:
|
288 |
-
|
289 |
-
|
290 |
-
|
291 |
-
|
292 |
-
|
293 |
-
)
|
294 |
-
except Exception:
|
295 |
-
print(f"Error while getting the number of mech calls")
|
296 |
-
num_mech_calls = 2 # Average value
|
297 |
|
298 |
net_earnings = (
|
299 |
earnings
|
300 |
- fee_amount
|
301 |
-
- (
|
302 |
- collateral_amount
|
303 |
)
|
304 |
|
@@ -308,7 +310,7 @@ def analyse_trader(
|
|
308 |
"market_creator": market_creator,
|
309 |
"trade_id": trade["id"],
|
310 |
"market_status": market_status.name,
|
311 |
-
"creation_timestamp":
|
312 |
"title": trade["title"],
|
313 |
"collateral_amount": collateral_amount,
|
314 |
"outcome_index": trade["outcomeIndex"],
|
@@ -320,11 +322,13 @@ def analyse_trader(
|
|
320 |
"earnings": earnings,
|
321 |
"redeemed": redemption,
|
322 |
"redeemed_amount": earnings if redemption else 0,
|
323 |
-
"num_mech_calls":
|
324 |
-
"mech_fee_amount":
|
325 |
"net_earnings": net_earnings,
|
326 |
"roi": net_earnings
|
327 |
-
/ (
|
|
|
|
|
328 |
}
|
329 |
|
330 |
except Exception as e:
|
@@ -336,16 +340,27 @@ def analyse_trader(
|
|
336 |
|
337 |
|
338 |
def analyse_all_traders(
|
339 |
-
trades: pd.DataFrame,
|
|
|
|
|
|
|
340 |
) -> pd.DataFrame:
|
341 |
"""Analyse all creators."""
|
|
|
342 |
all_traders = []
|
343 |
for trader in tqdm(
|
344 |
trades["trader_address"].unique(),
|
345 |
total=len(trades["trader_address"].unique()),
|
346 |
desc="Analysing creators",
|
347 |
):
|
348 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
349 |
|
350 |
# concat all creators
|
351 |
all_creators_df = pd.concat(all_traders)
|
@@ -414,8 +429,16 @@ def run_profitability_analysis(
|
|
414 |
update_tools_parquet(rpc, tools_filename)
|
415 |
tools = pd.read_parquet(DATA_DIR / "tools.parquet")
|
416 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
417 |
print("Analysing trades...")
|
418 |
-
all_trades_df = analyse_all_traders(fpmmTrades, tools)
|
419 |
|
420 |
# # merge previous files if requested
|
421 |
if merge:
|
|
|
40 |
DEFAULT_MECH_FEE,
|
41 |
)
|
42 |
from staking import label_trades_by_staking
|
43 |
+
from nr_mech_calls import (
|
44 |
+
create_unknown_traders_df,
|
45 |
+
transform_to_datetime,
|
46 |
+
compute_mech_calls_based_on_timestamps,
|
47 |
+
)
|
48 |
|
49 |
DUST_THRESHOLD = 10000000000000
|
50 |
INVALID_ANSWER = -1
|
|
|
219 |
trader_address: str,
|
220 |
fpmmTrades: pd.DataFrame,
|
221 |
tools: pd.DataFrame,
|
222 |
+
trader_estimated_mech_calls: pd.DataFrame,
|
223 |
daily_info: bool = False,
|
224 |
) -> pd.DataFrame:
|
225 |
"""Analyse a trader's trades"""
|
226 |
+
fpmmTrades["creation_timestamp"] = pd.to_datetime(fpmmTrades["creationTimestamp"])
|
227 |
+
fpmmTrades["creation_date"] = fpmmTrades["creation_timestamp"].dt.date
|
228 |
# Filter trades and tools for the given trader
|
229 |
trades = fpmmTrades[fpmmTrades["trader_address"] == trader_address]
|
|
|
230 |
|
231 |
# Prepare the DataFrame
|
232 |
trades_df = pd.DataFrame(columns=ALL_TRADES_STATS_DF_COLS)
|
|
|
244 |
for i, trade in tqdm(trades.iterrows(), total=len(trades), desc="Analysing trades"):
|
245 |
try:
|
246 |
market_answer = trade["fpmm.currentAnswer"]
|
247 |
+
trading_day = trade["creation_date"]
|
248 |
+
trade_id = trade["id"]
|
249 |
if not daily_info and not market_answer:
|
250 |
print(f"Skipping trade {i} because currentAnswer is NaN")
|
251 |
continue
|
252 |
# Parsing and computing shared values
|
|
|
|
|
|
|
|
|
253 |
collateral_amount = wei_to_unit(float(trade["collateralAmount"]))
|
254 |
fee_amount = wei_to_unit(float(trade["feeAmount"]))
|
255 |
outcome_tokens_traded = wei_to_unit(float(trade["outcomeTokensTraded"]))
|
|
|
284 |
earnings = outcome_tokens_traded
|
285 |
winner_trade = True
|
286 |
|
287 |
+
# Compute mech calls using the title, and trade id
|
288 |
+
if daily_info:
|
289 |
+
total_mech_calls = trader_estimated_mech_calls.loc[
|
290 |
+
(trader_estimated_mech_calls["trading_day"] == trading_day),
|
291 |
+
"total_mech_calls",
|
292 |
+
].iloc[0]
|
293 |
else:
|
294 |
+
total_mech_calls = trader_estimated_mech_calls.loc[
|
295 |
+
(trader_estimated_mech_calls["market"] == trade["title"])
|
296 |
+
& (trader_estimated_mech_calls["trade_id"] == trade_id),
|
297 |
+
"mech_calls_per_trade",
|
298 |
+
].iloc[0]
|
|
|
|
|
|
|
|
|
299 |
|
300 |
net_earnings = (
|
301 |
earnings
|
302 |
- fee_amount
|
303 |
+
- (total_mech_calls * DEFAULT_MECH_FEE)
|
304 |
- collateral_amount
|
305 |
)
|
306 |
|
|
|
310 |
"market_creator": market_creator,
|
311 |
"trade_id": trade["id"],
|
312 |
"market_status": market_status.name,
|
313 |
+
"creation_timestamp": trade["creationTimestamp"],
|
314 |
"title": trade["title"],
|
315 |
"collateral_amount": collateral_amount,
|
316 |
"outcome_index": trade["outcomeIndex"],
|
|
|
322 |
"earnings": earnings,
|
323 |
"redeemed": redemption,
|
324 |
"redeemed_amount": earnings if redemption else 0,
|
325 |
+
"num_mech_calls": total_mech_calls,
|
326 |
+
"mech_fee_amount": total_mech_calls * DEFAULT_MECH_FEE,
|
327 |
"net_earnings": net_earnings,
|
328 |
"roi": net_earnings
|
329 |
+
/ (
|
330 |
+
collateral_amount + fee_amount + total_mech_calls * DEFAULT_MECH_FEE
|
331 |
+
),
|
332 |
}
|
333 |
|
334 |
except Exception as e:
|
|
|
340 |
|
341 |
|
342 |
def analyse_all_traders(
|
343 |
+
trades: pd.DataFrame,
|
344 |
+
tools: pd.DataFrame,
|
345 |
+
estimated_mech_calls: pd.DataFrame,
|
346 |
+
daily_info: bool = False,
|
347 |
) -> pd.DataFrame:
|
348 |
"""Analyse all creators."""
|
349 |
+
|
350 |
all_traders = []
|
351 |
for trader in tqdm(
|
352 |
trades["trader_address"].unique(),
|
353 |
total=len(trades["trader_address"].unique()),
|
354 |
desc="Analysing creators",
|
355 |
):
|
356 |
+
trader_estimated_mech_calls = estimated_mech_calls.loc[
|
357 |
+
estimated_mech_calls["trader_address"] == trader
|
358 |
+
]
|
359 |
+
all_traders.append(
|
360 |
+
analyse_trader(
|
361 |
+
trader, trades, tools, trader_estimated_mech_calls, daily_info
|
362 |
+
)
|
363 |
+
)
|
364 |
|
365 |
# concat all creators
|
366 |
all_creators_df = pd.concat(all_traders)
|
|
|
429 |
update_tools_parquet(rpc, tools_filename)
|
430 |
tools = pd.read_parquet(DATA_DIR / "tools.parquet")
|
431 |
|
432 |
+
fpmmTrades["creationTimestamp"] = fpmmTrades["creationTimestamp"].apply(
|
433 |
+
lambda x: transform_to_datetime(x)
|
434 |
+
)
|
435 |
+
print("Computing the estimated mech calls dataset")
|
436 |
+
trade_mech_calls = compute_mech_calls_based_on_timestamps(
|
437 |
+
fpmmTrades=fpmmTrades, tools=tools
|
438 |
+
)
|
439 |
+
print(trade_mech_calls.total_mech_calls.describe())
|
440 |
print("Analysing trades...")
|
441 |
+
all_trades_df = analyse_all_traders(fpmmTrades, tools, trade_mech_calls)
|
442 |
|
443 |
# # merge previous files if requested
|
444 |
if merge:
|
scripts/pull_data.py
CHANGED
@@ -3,7 +3,7 @@ from datetime import datetime
|
|
3 |
import pandas as pd
|
4 |
from markets import etl as mkt_etl, DEFAULT_FILENAME as MARKETS_FILENAME, fpmmTrades_etl
|
5 |
from tools import DEFAULT_FILENAME as TOOLS_FILENAME, generate_tools_file
|
6 |
-
from profitability import run_profitability_analysis
|
7 |
from utils import (
|
8 |
get_question,
|
9 |
current_answer,
|
@@ -13,7 +13,6 @@ from utils import (
|
|
13 |
HIST_DIR,
|
14 |
)
|
15 |
from get_mech_info import (
|
16 |
-
get_mech_events_last_60_days,
|
17 |
get_mech_events_since_last_run,
|
18 |
update_json_files,
|
19 |
)
|
@@ -57,7 +56,7 @@ def save_historical_data():
|
|
57 |
filename = f"tools_{timestamp}.parquet"
|
58 |
tools.to_parquet(HIST_DIR / filename, index=False)
|
59 |
# save into cloud storage
|
60 |
-
|
61 |
except Exception as e:
|
62 |
print(f"Error saving tools file in the historical folder {e}")
|
63 |
|
@@ -66,7 +65,7 @@ def save_historical_data():
|
|
66 |
filename = f"all_trades_profitability_{timestamp}.parquet"
|
67 |
all_trades.to_parquet(HIST_DIR / filename, index=False)
|
68 |
# save into cloud storage
|
69 |
-
|
70 |
|
71 |
except Exception as e:
|
72 |
print(
|
@@ -80,7 +79,7 @@ def only_new_weekly_analysis():
|
|
80 |
rpc = RPC
|
81 |
# Run markets ETL
|
82 |
logging.info("Running markets ETL")
|
83 |
-
|
84 |
logging.info("Markets ETL completed")
|
85 |
|
86 |
# Mech events ETL
|
@@ -128,7 +127,7 @@ def only_new_weekly_analysis():
|
|
128 |
|
129 |
save_historical_data()
|
130 |
|
131 |
-
clean_old_data_from_parquet_files("2024-10-
|
132 |
|
133 |
compute_tools_accuracy()
|
134 |
|
|
|
3 |
import pandas as pd
|
4 |
from markets import etl as mkt_etl, DEFAULT_FILENAME as MARKETS_FILENAME, fpmmTrades_etl
|
5 |
from tools import DEFAULT_FILENAME as TOOLS_FILENAME, generate_tools_file
|
6 |
+
from profitability import run_profitability_analysis
|
7 |
from utils import (
|
8 |
get_question,
|
9 |
current_answer,
|
|
|
13 |
HIST_DIR,
|
14 |
)
|
15 |
from get_mech_info import (
|
|
|
16 |
get_mech_events_since_last_run,
|
17 |
update_json_files,
|
18 |
)
|
|
|
56 |
filename = f"tools_{timestamp}.parquet"
|
57 |
tools.to_parquet(HIST_DIR / filename, index=False)
|
58 |
# save into cloud storage
|
59 |
+
load_historical_file(filename)
|
60 |
except Exception as e:
|
61 |
print(f"Error saving tools file in the historical folder {e}")
|
62 |
|
|
|
65 |
filename = f"all_trades_profitability_{timestamp}.parquet"
|
66 |
all_trades.to_parquet(HIST_DIR / filename, index=False)
|
67 |
# save into cloud storage
|
68 |
+
load_historical_file(filename)
|
69 |
|
70 |
except Exception as e:
|
71 |
print(
|
|
|
79 |
rpc = RPC
|
80 |
# Run markets ETL
|
81 |
logging.info("Running markets ETL")
|
82 |
+
mkt_etl(MARKETS_FILENAME)
|
83 |
logging.info("Markets ETL completed")
|
84 |
|
85 |
# Mech events ETL
|
|
|
127 |
|
128 |
save_historical_data()
|
129 |
|
130 |
+
clean_old_data_from_parquet_files("2024-10-17")
|
131 |
|
132 |
compute_tools_accuracy()
|
133 |
|
scripts/utils.py
CHANGED
@@ -9,6 +9,7 @@ import re
|
|
9 |
from dataclasses import dataclass
|
10 |
from pathlib import Path
|
11 |
from enum import Enum
|
|
|
12 |
from json.decoder import JSONDecodeError
|
13 |
|
14 |
DEFAULT_MECH_FEE = 0.01
|
@@ -48,6 +49,21 @@ INC_TOOLS = [
|
|
48 |
"prediction-request-reasoning-claude",
|
49 |
"superforcaster",
|
50 |
]
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
51 |
|
52 |
SUBGRAPH_API_KEY = os.environ.get("SUBGRAPH_API_KEY", None)
|
53 |
RPC = os.environ.get("RPC", None)
|
|
|
9 |
from dataclasses import dataclass
|
10 |
from pathlib import Path
|
11 |
from enum import Enum
|
12 |
+
from string import Template
|
13 |
from json.decoder import JSONDecodeError
|
14 |
|
15 |
DEFAULT_MECH_FEE = 0.01
|
|
|
49 |
"prediction-request-reasoning-claude",
|
50 |
"superforcaster",
|
51 |
]
|
52 |
+
SUBGRAPH_URL = Template(
|
53 |
+
"""https://gateway-arbitrum.network.thegraph.com/api/${subgraph_api_key}/subgraphs/id/7s9rGBffUTL8kDZuxvvpuc46v44iuDarbrADBFw5uVp2"""
|
54 |
+
)
|
55 |
+
OMEN_SUBGRAPH_URL = Template(
|
56 |
+
"""https://gateway-arbitrum.network.thegraph.com/api/${subgraph_api_key}/subgraphs/id/9fUVQpFwzpdWS9bq5WkAnmKbNNcoBwatMR4yZq81pbbz"""
|
57 |
+
)
|
58 |
+
NETWORK_SUBGRAPH_URL = Template(
|
59 |
+
"""https://gateway-arbitrum.network.thegraph.com/api/${subgraph_api_key}/subgraphs/id/FxV6YUix58SpYmLBwc9gEHkwjfkqwe1X5FJQjn8nKPyA"""
|
60 |
+
)
|
61 |
+
# THEGRAPH_ENDPOINT = (
|
62 |
+
# "https://api.studio.thegraph.com/query/78829/mech-predict/version/latest"
|
63 |
+
# )
|
64 |
+
MECH_SUBGRAPH_URL = Template(
|
65 |
+
"""https://gateway.thegraph.com/api/${subgraph_api_key}/subgraphs/id/4YGoX3iXUni1NBhWJS5xyKcntrAzssfytJK7PQxxQk5g"""
|
66 |
+
)
|
67 |
|
68 |
SUBGRAPH_API_KEY = os.environ.get("SUBGRAPH_API_KEY", None)
|
69 |
RPC = os.environ.get("RPC", None)
|
scripts/web3_utils.py
CHANGED
@@ -12,7 +12,7 @@ from tqdm import tqdm
|
|
12 |
from web3 import Web3
|
13 |
from typing import Any, Optional
|
14 |
from web3.types import BlockParams
|
15 |
-
from utils import JSON_DATA_DIR, DATA_DIR, SUBGRAPH_API_KEY, to_content
|
16 |
from queries import conditional_tokens_gc_user_query, omen_xdai_trades_query
|
17 |
import pandas as pd
|
18 |
|
@@ -30,6 +30,9 @@ N_RPC_RETRIES = 100
|
|
30 |
RPC_POLL_INTERVAL = 0.05
|
31 |
# IPFS_POLL_INTERVAL = 0.05 # low speed
|
32 |
IPFS_POLL_INTERVAL = 0.2 # high speed
|
|
|
|
|
|
|
33 |
|
34 |
headers = {
|
35 |
"Accept": "application/json, multipart/mixed",
|
@@ -156,9 +159,7 @@ def updating_timestamps(rpc: str, tools_filename: str):
|
|
156 |
|
157 |
def query_conditional_tokens_gc_subgraph(creator: str) -> dict[str, Any]:
|
158 |
"""Query the subgraph."""
|
159 |
-
|
160 |
-
"""https://gateway-arbitrum.network.thegraph.com/api/${subgraph_api_key}/subgraphs/id/7s9rGBffUTL8kDZuxvvpuc46v44iuDarbrADBFw5uVp2"""
|
161 |
-
)
|
162 |
subgraph = SUBGRAPH_URL.substitute(subgraph_api_key=SUBGRAPH_API_KEY)
|
163 |
all_results: dict[str, Any] = {"data": {"user": {"userPositions": []}}}
|
164 |
userPositions_id_gt = ""
|
@@ -200,9 +201,7 @@ def query_omen_xdai_subgraph(
|
|
200 |
fpmm_to_timestamp: float,
|
201 |
) -> dict[str, Any]:
|
202 |
"""Query the subgraph."""
|
203 |
-
|
204 |
-
"""https://gateway-arbitrum.network.thegraph.com/api/${subgraph_api_key}/subgraphs/id/9fUVQpFwzpdWS9bq5WkAnmKbNNcoBwatMR4yZq81pbbz"""
|
205 |
-
)
|
206 |
omen_subgraph = OMEN_SUBGRAPH_URL.substitute(subgraph_api_key=SUBGRAPH_API_KEY)
|
207 |
print(f"omen_subgraph = {omen_subgraph}")
|
208 |
grouped_results = defaultdict(list)
|
|
|
12 |
from web3 import Web3
|
13 |
from typing import Any, Optional
|
14 |
from web3.types import BlockParams
|
15 |
+
from utils import JSON_DATA_DIR, DATA_DIR, SUBGRAPH_API_KEY, to_content, SUBGRAPH_URL
|
16 |
from queries import conditional_tokens_gc_user_query, omen_xdai_trades_query
|
17 |
import pandas as pd
|
18 |
|
|
|
30 |
RPC_POLL_INTERVAL = 0.05
|
31 |
# IPFS_POLL_INTERVAL = 0.05 # low speed
|
32 |
IPFS_POLL_INTERVAL = 0.2 # high speed
|
33 |
+
OMEN_SUBGRAPH_URL = Template(
|
34 |
+
"""https://gateway-arbitrum.network.thegraph.com/api/${subgraph_api_key}/subgraphs/id/9fUVQpFwzpdWS9bq5WkAnmKbNNcoBwatMR4yZq81pbbz"""
|
35 |
+
)
|
36 |
|
37 |
headers = {
|
38 |
"Accept": "application/json, multipart/mixed",
|
|
|
159 |
|
160 |
def query_conditional_tokens_gc_subgraph(creator: str) -> dict[str, Any]:
|
161 |
"""Query the subgraph."""
|
162 |
+
|
|
|
|
|
163 |
subgraph = SUBGRAPH_URL.substitute(subgraph_api_key=SUBGRAPH_API_KEY)
|
164 |
all_results: dict[str, Any] = {"data": {"user": {"userPositions": []}}}
|
165 |
userPositions_id_gt = ""
|
|
|
201 |
fpmm_to_timestamp: float,
|
202 |
) -> dict[str, Any]:
|
203 |
"""Query the subgraph."""
|
204 |
+
|
|
|
|
|
205 |
omen_subgraph = OMEN_SUBGRAPH_URL.substitute(subgraph_api_key=SUBGRAPH_API_KEY)
|
206 |
print(f"omen_subgraph = {omen_subgraph}")
|
207 |
grouped_results = defaultdict(list)
|
tabs/metrics.py
CHANGED
@@ -187,13 +187,43 @@ def plot_trade_metrics(
|
|
187 |
)
|
188 |
|
189 |
|
190 |
-
def get_trade_metrics_text() -> gr.Markdown:
|
191 |
-
|
192 |
-
|
193 |
-
|
194 |
-
|
195 |
-
|
196 |
-
|
197 |
-
|
198 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
199 |
return gr.Markdown(metric_text)
|
|
|
187 |
)
|
188 |
|
189 |
|
190 |
+
def get_trade_metrics_text(trader_type: str = None) -> gr.Markdown:
|
191 |
+
if trader_type is None:
|
192 |
+
metric_text = """
|
193 |
+
## Description of the graph
|
194 |
+
These metrics are computed weekly. The statistical measures are:
|
195 |
+
* min, max, 25th(q1), 50th(median) and 75th(q2) percentiles
|
196 |
+
* the upper and lower fences to delimit possible outliers
|
197 |
+
* the average values as the dotted lines
|
198 |
+
"""
|
199 |
+
elif trader_type == "Olas":
|
200 |
+
metric_text = """
|
201 |
+
## Definition of Olas trader
|
202 |
+
Agents using Mech, with a service ID and the corresponding safe in the registry
|
203 |
+
## Description of the graph
|
204 |
+
These metrics are computed weekly. The statistical measures are:
|
205 |
+
* min, max, 25th(q1), 50th(median) and 75th(q2) percentiles
|
206 |
+
* the upper and lower fences to delimit possible outliers
|
207 |
+
* the average values as the dotted lines
|
208 |
+
"""
|
209 |
+
elif trader_type == "non_Olas":
|
210 |
+
metric_text = """
|
211 |
+
## Definition of non-Olas trader
|
212 |
+
Agents using Mech, with no service ID
|
213 |
+
## Description of the graph
|
214 |
+
These metrics are computed weekly. The statistical measures are:
|
215 |
+
* min, max, 25th(q1), 50th(median) and 75th(q2) percentiles
|
216 |
+
* the upper and lower fences to delimit possible outliers
|
217 |
+
* the average values as the dotted lines
|
218 |
+
"""
|
219 |
+
else: # Unclassified
|
220 |
+
metric_text = """
|
221 |
+
## Definition of unclassified trader
|
222 |
+
Agents (safe/EOAs) not using Mechs
|
223 |
+
## Description of the graph
|
224 |
+
These metrics are computed weekly. The statistical measures are:
|
225 |
+
* min, max, 25th(q1), 50th(median) and 75th(q2) percentiles
|
226 |
+
* the upper and lower fences to delimit possible outliers
|
227 |
+
* the average values as the dotted lines
|
228 |
+
"""
|
229 |
return gr.Markdown(metric_text)
|