From 04044934e40d9e2d374cbe6118921cd45942c494 Mon Sep 17 00:00:00 2001 From: Norbert <37236152+KatunaNorbert@users.noreply.github.com> Date: Fri, 19 Jan 2024 07:05:58 +0200 Subject: [PATCH] Re-write get_cli_statistics (#463) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * First stab at porting various functions over to polars... lots to go * TOHLCV df initialization and type checking added. 2/8 pdutil tests passing * black formatted * Fixing initialization and improving test. datetime is not generated if no timestamp is present * Restructured pdutil a bit to reduce DRY and utilize schema more strictly. * test initializing the df and datetime * improve init test to show exception without timestamp * fixing test_concat such that it verifies that schemas must match, and how transform handles datetime * saving parquet enforces datetime and transform. updated test_load_append and test_load_filtered. * black formatted * data_eng tests are passing * initial data_eng tests are passing w/ black, mypy, and pylint. * _merge_parquet_dfs updated and create_xy test_1 is passing. all data_eng tests that are enabled are passing. * 2exch_2coins_2signals is passing * Added polars support for fill_nans, has_nans, and create_xy__handle_nan is passing. * Starting to deprecate references to pandas and csv in data_factory. * Black formatted * Deprecated csv logic in DataFactory and created tests around get_hist_df() to verify that its working as intended. I believe kraken data is returning null at the moment. * All tests should be passing. * Fix #370: YAML & CLI (#371) * Towards #232: Refactoring towards ppss.yaml part 3/3 * move everything in model_eng/ to data_eng/ * Fix #352: [SW eng] High DRY violation in test_predictoor_agent.py <> test_predictoor_agent3.py * Deprecate backend-dev.md (long obsolete), macos.md (obsolete due to vps), and envvars.md (obsolete because of ppss.yaml). * Rename BaseConfig to web3_pp.py and make it yaml-based * Move scripts into util/, incorporate them into pdr cli, some refactoring. * revamp READMEs for cli. And, tighten up text for getting OCEAN & ROSE * Deprecated ADDRESS_FILE and RPC_URL envvars. * deprecate Predictoor approach 2. Pita to maintain Co-authored-by: trizin <25263018+trizin@users.noreply.github.com> * Update CI to use pdr instead of scripts/ (#399) * Update check script CI * Update cron topup * Workflow dispatch * Nevermind, revert previous commit * Run on push to test * Pass ppss.web3_pp instead of web3_config * Don't run on push * Replace long try/except with _safe*() function; rename pdutil -> plutil; get linters to pass * Update entrypoint script to use pdr cli (#406) * Add main.py back (#404) * Add main.py back * Black * Linter * Linter * Remove "switch back to version v0.1.1" * Black * make black happy * small bug fix * many bug fixes. Still >=1 left * fix warning * Add support for polars where needed * tweak docstring * Fix #408: test_sim_engine failing in yaml-cli2, bc hist_df is s not ms. Proper testing and documentation was added, as part of the fix * BaseContract tests that Web3PP type is input * goes with previous commit * tweak - lowercase * Bug fix - fix failing tests * Remove unwanted file * (a) better organize ppss.yaml for usability (b) ensure user isn't annoyed by git with their copy of ppss.yaml being my_ppss.yaml * add a more precise test for modeling * make black happy * Small refactor: make transform_df() part of helper routine * Fix #414: Split data_factory into (1) CEX -> parquet -> df (2) df -> X,y for models * Fix #415: test_cli_do_dfbuyer.py is hanging #415 * test create_xy() even more. Clarify the order of timestamps * Add a model-building test, using data shaped like data from test_model_data_factory * Fix #416: [YAML branch] No Feeds Found - data_pp.py changes pair standards * For barge#391: update to *not* use barge's predictoor branch * Update vps.md: nicer order of operations * For #417, #418 in yaml-cli2 branch. publisher TUSD -> USDT * remove default_network from ppss.yaml (obsolete) * Fix #427 - time now * Fix #428: test_get_hist_df - FileNotFoundError. Includes lots of extra robustness checking * remove dependency that we don't need, which caused problems * Fix #421: Add cli + logic to calculate and plot traction metrics (PR #422) Also: mild cleanup of CLI. * bug fix: YAML_FILE * fix breaking test; clean it up too * add barge-calls.md * Fix #433. Calculate metrics and draw plots for epoch-based stats (PR #434) #433 : "Plot daily global (pair_timeframe x20) and , by sampling slots from each day." * Tweak barge-calls.md How: show origin of NETWORK_RPC_URL * Tweak barge-calls.md: more compactly show RPC_URL calc * update stake_token * bug fix * Update release-process.md: bug fix * Tweak barge-calls.md * Tune #405 (PR #406): Update entrypointsh script to use pdr CLI * Update vps.md: docker doesn't need to prompt to delete * Update vps.md: add docker-stop instrs * allow CLI to have NETWORK_OVERRIDE, for more flexiblity from barge * fix pylint issue * Update barge-calls.md: link to barge.md * Update release-process.md: fix typo * touch * Update vps.md: more instrs around waiting for barge to be ready * add unit tests for cli_module * Towards #437: [YAML] Publisher error 'You must set RPC_URL environment variable' * Bug fixes * refactor tweaks to predictoor and trader * Clean up some envvar stuff. Document ppss vars better. * publish_assets.py now supports barge-pytest and barge-predictoor-bot * bug fix * bug fix the previous 'bug fix' * Clean up how dfbuyer/predictoor/trader agents get feeds: web3_pp.query_feed_contracts() -> data_pp.filter_feeds(); no more filtering within subgraph querying; easier printing & logging. Add timeframestr.Timeframe. Add feed.mock_feed. All tests pass. * fix breaking subgraph tests. Still breakage in trader & dfbuyer (that's next) * Fix failing tests in tradder, dfbuyer. And greatly speed up the tests, via better mocking. * Fix bugs for failing tests of https://github.com/oceanprotocol/pdr-backend/actions/runs/7156603163/job/19486494815 * fix tmpdir bug * Fix (hopefully) failing unit test - restricted region in querying binance api * consolidate gas_price setting, make it consistent; set gas_price to 0 for development/barge * fix linter complaints * Fix remaining failing unit tests for predictoor_batcher * Finish the consolidation of gas pricing. All tests pass * Update vps.md: add debugging info - Where to find queries - Key docker debugging commands * add to/from wei utility. Copied from ocean.py * tweak docs in conftest_ganache * tweaks from black for wei * Make fixed_rate.py and its test easier to understand via better var naming & docs * Make predictoor_contract.py easier to understandn via better var anming & docs * test fixed_rate calcBaseInGivenOutDT * Refactor predictoor_contract: push utility methods out of the class, and into more appropriate utility modules. And, move to/from_wei() from wei.py to mathutil.py. Test it all. * Tweak docstrings for fixed_rate.py * Improve DX: show dev what the parameters are. Improve UX: print when done. * Improve DX & UX for predictoor_contract * Tweak UX (prints) * Update vps.md: export PATH * Logging for predictoor is way better: more calm yet more informative. Predictoors only do 1 feed now. * TraderAgent -> BaseTraderAgent * Rename parquet_dfs -> rawohlcv_dfs; hist_df -> mergedohlcv_df; update related * apply black to test_plutil.py * apply black to test_model_data_factory.py * apply black to ohlcv_data_factory.py * refactor test_ohlcv_data_factory: cleanup mocks; remove redundant test; pq_data_factory -> factory * Fix #443: [YAML] yaml timescale is 5m, yet predictoor logs s_per_epoch=3600 (1h) * Update feed str() to give full address; and order to be similar to predict_feeds_strs. Show all info used in filtering feeds. * Small bug fix: not printing properly * Tweak: logging in predictoor_contract.py * Tweak: logging in trueval_agent_single.py * Two bug fixes: pass in web3_pp not web3_config to PredictoorContract constructor * enhance typechecking * tweak payout.py: make args passed more obvious * fix broken unit test * make black happy * fix breaking unit test * Tweak predictoor_contract DX & UX * Improve trueval: Have fewer layers of try/except, better DX via docstrings and more, better UX via logs * Rename TruevalAgentBase -> BaseTruevalAgent * (a) Fix #445: merge 3 trueval agent files into 1. (b) Fix #448 contract.py::get_address() doesn't handle 'sapphire-testnet' etc #448. (c) test_contract.py doesn't test across all networks we use, and it's missing places where we have errors (d) clean up trueval agent testing (e) move test_contract.py into test_noganache since it doesn't use ganache * Fix #450: test_contract_main[barge-pytest] fails * renaming pq_data_factory to ohlcv_data_factory * Removing all TODOs * Fix #452: Add clean code guidelines README * removing dangling _ppss() inside predictoor_agent_runner.py * Fixing linter * Fix #454: Refactor: Rename MEXCOrder -> MexcOrder, ERC721Factory * Fix #455: Cyclic import issue * Fix #454 redux: the first commit introduced a bug, this one fixes the bug * Fix #436 - Implement GQL data factory (PR #438) * First pass on gql data factory Co-authored-by: trentmc * Fix #350: [Sim] Tweaks to plot title * make black happy * Fix #446: [YAML] Rename/move files & dirs for proper separation among lake, AI models, analytics (#458) * rename data_eng/ -> lake/ * rename model_factory -> aimodel_factory, model_data_factory -> aimodel_data_factory, model_ss -> aimodel_ss * for any module in util/ that should be in analytics/, move it * for any module in util/ that should be in lake/, move it. Including get_*_info.py * create dir subgraph/ and move all *subgraph*.py into it. Split apart subgraph.py into core_subgraph.py and more * apply mathutil.to_wei() and from_wei() everywhere * move contents of util/test_data.py (a bunch of sample predictions) into models/predictions.py. Fix DRY violations in related conftest.pys * note: there are 2 failing unit tests related to polars and "timestamp_right" column. However they were failing before. I just created a separate issue for that: #459 * Fix #459: In CI, polars error: col timestamp_right already exists (#460) Plus: * remove datetime from all DFs, it's too problematic, and unneeded * bug fix: wasn't mocking check_dfbuyer(), so CI was failing * Fix #397: Remove need to specify 'stake_token' in ppss.yaml (#461) * Docs fixes (#456) * transform data into polar data * refactored predictoor summary stats function to use polar operations * update feed summary function * Make Feeds objects instead of tuples. (#464) * Make Feeds objects instead of tuples. * Add namings for different feed objects. * Move signal at the end. * Move and rename utils (#467) * Move and rename utils * Objectify pairstr. (#470) * Objectify pairstr. * Add possibility for empty signal in feeds. * Move and add some timeframe functions. * Move exchangestr. * Towards #462: Separate lake and aimodel SS, lake command (#473) * Split aimodel and lake ss. * Split data ss tests. * Add aimodel ss into predictoor ss. * Remove stray data_ss. * Moves test_n to sim ss. * Trader ss to use own feed instead of data pp. * Remove data pp entirely. * Correct ohlcv data factory. * Add timeframe into arg feeds. * Refine and add tests for timeframe in arg feed. * Remove timeframe dependency in trader and predictoor. * Remove timeframe from lake ss keys. * Singleify trader agents. * Adds lake command, assert timeframe in lake (needed for columns). * Process all signals in lake. * group data by timeframe also * fix filtering and code formatting issues * run black to format code * removed duplicated imports * [Lake] integrate pdr_subscriptions into GQL Data Factory (#469) * first commit for subscriptions * hook up pdr_subscriptions to gql_factory * Tests passing, expanding tests to support multiple tables * Adding tests and improving handling of empty parquet files * Subscriptions test * Updating logic to use predictSubscriptions, take lastPriceValue, and to not query the subgraph more than needed. * Moving models from contract/ -> subgraph/ * Fixing pylint * fixing tests * adding @enforce_types * Improve DRY (#475) * Improve DRY in cli module. * Add common functionality to single and multifeed entries. * Remove trader pp and move necessary lines into trader ss. * Adds dfbuyer filtering. * Remove exchange dict from multifeed mixin. * Replace name of predict_feed. * Add base_ss tests. * Adds trueval filtering. * Add Code climate. (#484) * Adds manual trigger to pytest workflow. * fixed failing tests * fix mypy * use contract address inside id instead of pair * fix lines to long issues * issue483: move the logic from subgraph_slot.py (#489) * Add some test coverage (#488) * Adds a line of coverage to test. * Add coverage for csvs module. * Add coverage to check_network. * Add coverage to predictions and traction info. * Adds coverage to predictoor stats. * Adds full coverage to arg cli classes. * Adds cli arguments coverage and fix a wrong parameter in cli arguments. * Adds coverage to cli module and timeframe. * Some reformats and coverage in contract module. * Adds coverage and simplifications to contracts, except token. * Add some coverage to tokens to complete contract coverage work. * Fix #501: ModuleNotFoundError: No module named 'flask' (PR #504) * rename prediction address field and change prediction id format * Fix #509: Refactor test_update_rawohlcv_files (PR #508) * fix failing test * Fix #505: polars.exceptions.ComputeError: datatypes of join keys don't match (PR #510) * Refactor: new function clean_raw_ohlcv() that moves code from _update_rawohlcv_files_at_feed(). It has sub-functions with precise responsibilities. It has tests. * Add more tests for merge_raw_ohlcv_dfs, including one that replicates the original issue * Fix the core bug, now the new tests pass. The main fix is at the top of merge_df::_add_df_col() * Fix failing test due to network override. NOTE: this may have caused the remaining pytest error. Will fix that after this merge * Fix #517: aimodel_data_factory.py missing data: binance:BTC/USDT:None (PR #518) Fixes #517 Root cause: ppss.yaml's aimodel_ss feeds section didn't have eg "c" or "ohlcv"; it assumed that they didn't need to be specified. This was an incorrect assumption: aimodel_ss needs it. In fact aimodel_ss class supports these signals, but the yaml file didn't have it. What this PR does: - add a test to aimodel_ss class constructor that complains if not specified - do specify signals in the ppss.yaml file Note: the PR pytest failed, but for an unrelated reason. Just created #520 for follow-up. * Towards #494: Improve coverage 2 (#498) * Adds some coverage to dfbuyer agent. * Add dfbuyer and ppss coverage. * Adds predictoor and sim coverage. * Adds coverage to util. * Add some trueval coverage. * Adds coverage to trader agents. * Add coverage to portfolio. * Add coverage to subgraph consume_so_far and fix an infinite loop bug. * More subgraph coverage. * remove filtering and other fixes * Fix #519: aimodel_data_factory.py missing data col: binance:ETH/USDT:close (#524) Fix #519 Changes: - do check for dependencies among various ppss ss feeds - if any of those checks fails, give a user-friendly error message - greatly improved printing of ArgFeeds, including merging across pairs and signals. This was half the change of this PR - appropriate unit tests * moved filters and prints to analytics level * Replace `dftool` with `pdr` (#522) * Print texts: dftool -> pdrcli * pdrcli -> pdr * Fix #525: Plots pop up unwanted in tests. (PR #528) Fix by mocking plt.show(). * Issue 519 feed dependencies (#529) * Make missing attributes message more friendly and integrate ai ss part to multimixin. * Update to #519: remove do_verify, it's redundant (#532) * Fix #507: fix asyncio issues (PR #531) How fixed: use previous ascynio version. Calina: Asyncio has some known issues, per their changelog. Namely issues with fixture handling etc., which I believe causes the warnings and test skips in our runs. They recommend using the previous version until they are fixed. It is also why my setup didn't spew up any warnings, my asyncio version was 21.1. https://pytest-asyncio.readthedocs.io/en/latest/reference/changelog.html * #413 - YAML thorough system level tests (#527) * Fix web3_config.rpc_url in test_send_encrypted_tx * Add conftest.py for system tests * Add system test for get_traction_info * Add system test for get_predictions_info * Add system test for get_predictoors_info * Add "PDRS" argument to _ArgParser_ST_END_PQDIR_NETWORK_PPSS_PDRS class * Fix feed.exchange type conversion in publish_assets.py * Add print statement for payout completion * Add system level test for pdr topup * Add conditional break for testing via env * Add conditional break for testing via env * Black * Add test for pdr rose payout system * System level test pdr check network * System level test pdr claim OCEAN * System level test pdr trueval agent * Remove unused patchs * Fix wrong import position in conftest.py * Remove unused imports * System level test for pdr dfbuyer * System level tests for pdr trader * System level tests for publisher * Rename publisher test file * Add conditional break in take_step() method * Update dftool->pdr names in system tests * Refactor test_trader_agent_system.py * Add mock fixtures for SubgraphFeed and PredictoorContract * Add system tests for predictoor * Black * Refactor system test files - linter fixes * Linter fixes * Black * Add missing mock * Add savefig assertion in test_topup * Update VPS configuration to use development entry * Patch verify_feed_dependencies * Refactor test_predictoor_system.py to use a common test function * Refactor trader approach tests to improve DRY * Black * Indent * Ditch NETWORK_OVERRIDE * Black * Remove unused imports * updated and extended tests * fix pylint issue * fixed new pulled tests * changed names to use snake case * fix failing publisher ss test * fix black failing issue * removing aggregate_prediction_statistics as well, since this isn't used anywhere * cleaning up pylint --------- Co-authored-by: idiom-bytes Co-authored-by: Trent McConaghy Co-authored-by: trizin <25263018+trizin@users.noreply.github.com> Co-authored-by: Idiom <69865342+idiom-bytes@users.noreply.github.com> Co-authored-by: Călina Cenan Co-authored-by: Mustafa Tunçay --- READMEs/dev.md | 2 +- pdr_backend/analytics/check_network.py | 1 - pdr_backend/analytics/get_predictions_info.py | 67 +++--- pdr_backend/analytics/get_predictoors_info.py | 52 ++--- pdr_backend/analytics/get_traction_info.py | 20 +- pdr_backend/analytics/predictoor_stats.py | 128 +++--------- .../test/test_get_predictions_info.py | 197 ++++++++++++++---- .../test/test_get_predictoors_info.py | 162 +++++++++++--- .../analytics/test/test_get_traction_info.py | 110 ++++------ .../analytics/test/test_predictoor_stats.py | 48 ++--- pdr_backend/cli/cli_module.py | 4 +- pdr_backend/lake/gql_data_factory.py | 2 +- pdr_backend/lake/table_pdr_predictions.py | 23 ++ pdr_backend/publisher/publish_assets.py | 1 - pdr_backend/subgraph/core_subgraph.py | 1 - pdr_backend/subgraph/prediction.py | 20 +- pdr_backend/subgraph/subgraph_predictions.py | 3 +- pdr_backend/subgraph/test/test_prediction.py | 6 +- .../test/test_subgraph_predictions.py | 4 + .../test_get_predictions_info_system.py | 54 ++--- .../test_get_predictoors_info_system.py | 61 ++++-- system_tests/test_get_traction_info_system.py | 51 +++-- 22 files changed, 583 insertions(+), 434 deletions(-) diff --git a/READMEs/dev.md b/READMEs/dev.md index 3c0bbaebe..eea5a61d4 100644 --- a/READMEs/dev.md +++ b/READMEs/dev.md @@ -69,12 +69,12 @@ pylint pdr_backend/* black ./ ``` +======= Check code coverage: ```console coverage run --omit="*test*" -m pytest # Run all. For subset, add eg: pdr_backend/lake coverage report # show results ``` - ### Local Usage: Run a custom agent Let's say you want to change the trader agent, and use off-the-shelf agents for everything else. Here's how. diff --git a/pdr_backend/analytics/check_network.py b/pdr_backend/analytics/check_network.py index 8788362e9..2c05aa2b8 100644 --- a/pdr_backend/analytics/check_network.py +++ b/pdr_backend/analytics/check_network.py @@ -91,7 +91,6 @@ def get_expected_consume(for_ut: int, token_amt: int) -> Union[float, int]: @enforce_types def check_network_main(ppss: PPSS, lookback_hours: int): web3_pp = ppss.web3_pp - cur_ut = current_ut_s() start_ut = cur_ut - lookback_hours * 60 * 60 query = """ diff --git a/pdr_backend/analytics/get_predictions_info.py b/pdr_backend/analytics/get_predictions_info.py index 4749bc25c..090e8ce6e 100644 --- a/pdr_backend/analytics/get_predictions_info.py +++ b/pdr_backend/analytics/get_predictions_info.py @@ -1,57 +1,38 @@ from typing import Union from enforce_typing import enforce_types - -from pdr_backend.analytics.predictoor_stats import get_cli_statistics from pdr_backend.ppss.ppss import PPSS -from pdr_backend.subgraph.subgraph_predictions import ( - FilterMode, - fetch_filtered_predictions, - get_all_contract_ids_by_owner, -) -from pdr_backend.util.csvs import save_analysis_csv -from pdr_backend.util.networkutil import get_sapphire_postfix -from pdr_backend.util.timeutil import ms_to_seconds, timestr_to_ut +from pdr_backend.lake.gql_data_factory import GQLDataFactory +from pdr_backend.util.timeutil import timestr_to_ut +from pdr_backend.analytics.predictoor_stats import get_feed_summary_stats @enforce_types def get_predictions_info_main( - ppss: PPSS, - feed_addrs_str: Union[str, None], - start_timestr: str, - end_timestr: str, - pq_dir: str, + ppss: PPSS, start_timestr: str, end_timestr: str, feed_addrs_str: Union[str, None] ): - network = get_sapphire_postfix(ppss.web3_pp.network) - start_ut: int = ms_to_seconds(timestr_to_ut(start_timestr)) - end_ut: int = ms_to_seconds(timestr_to_ut(end_timestr)) - - # filter by feed contract address - feed_contract_list = get_all_contract_ids_by_owner( - owner_address=ppss.web3_pp.owner_addrs, - network=network, - ) - feed_contract_list = [f.lower() for f in feed_contract_list] - - if feed_addrs_str: - keep = feed_addrs_str.lower().split(",") - feed_contract_list = [f for f in feed_contract_list if f in keep] + gql_data_factory = GQLDataFactory(ppss) + gql_dfs = gql_data_factory.get_gql_dfs() - # fetch predictions - predictions = fetch_filtered_predictions( - start_ut, - end_ut, - feed_contract_list, - network, - FilterMode.CONTRACT, - payout_only=True, - trueval_only=True, - ) - - if not predictions: + if len(gql_dfs["pdr_predictions"]) == 0: print("No records found. Please adjust start and end times.") return + predictions_df = gql_dfs["pdr_predictions"] - save_analysis_csv(predictions, pq_dir) + # filter by feed addresses + if feed_addrs_str: + feed_addrs_list = feed_addrs_str.lower().split(",") + predictions_df = predictions_df.filter( + predictions_df["ID"] + .map_elements(lambda x: x.split("-")[0]) + .is_in(feed_addrs_list) + ) + + # filter by start and end dates + predictions_df = predictions_df.filter( + (predictions_df["timestamp"] >= timestr_to_ut(start_timestr) / 1000) + & (predictions_df["timestamp"] <= timestr_to_ut(end_timestr) / 1000) + ) - get_cli_statistics(predictions) + feed_summary_df = get_feed_summary_stats(predictions_df) + print(feed_summary_df) diff --git a/pdr_backend/analytics/get_predictoors_info.py b/pdr_backend/analytics/get_predictoors_info.py index e40b6f347..3d4a0f891 100644 --- a/pdr_backend/analytics/get_predictoors_info.py +++ b/pdr_backend/analytics/get_predictoors_info.py @@ -1,42 +1,36 @@ from typing import Union from enforce_typing import enforce_types - -from pdr_backend.analytics.predictoor_stats import get_cli_statistics +from pdr_backend.lake.gql_data_factory import GQLDataFactory +from pdr_backend.analytics.predictoor_stats import get_predictoor_summary_stats from pdr_backend.ppss.ppss import PPSS -from pdr_backend.subgraph.subgraph_predictions import ( - FilterMode, - fetch_filtered_predictions, -) -from pdr_backend.util.csvs import save_prediction_csv -from pdr_backend.util.networkutil import get_sapphire_postfix -from pdr_backend.util.timeutil import ms_to_seconds, timestr_to_ut +from pdr_backend.util.timeutil import timestr_to_ut @enforce_types def get_predictoors_info_main( - ppss: PPSS, - pdr_addrs_str: Union[str, None], - start_timestr: str, - end_timestr: str, - csv_output_dir: str, + ppss: PPSS, start_timestr: str, end_timestr: str, pdr_addrs_str: Union[str, None] ): - network = get_sapphire_postfix(ppss.web3_pp.network) - start_ut: int = ms_to_seconds(timestr_to_ut(start_timestr)) - end_ut: int = ms_to_seconds(timestr_to_ut(end_timestr)) + gql_data_factory = GQLDataFactory(ppss) + gql_dfs = gql_data_factory.get_gql_dfs() - pdr_addrs_filter = [] - if pdr_addrs_str: - pdr_addrs_filter = pdr_addrs_str.lower().split(",") + if len(gql_dfs) == 0: + print("No records found. Please adjust start and end times.") + return + predictions_df = gql_dfs["pdr_predictions"] - predictions = fetch_filtered_predictions( - start_ut, - end_ut, - pdr_addrs_filter, - network, - FilterMode.PREDICTOOR, + # filter by user addresses + if pdr_addrs_str: + pdr_addrs_list = pdr_addrs_str.lower().split(",") + predictions_df = predictions_df.filter( + predictions_df["user"].is_in(pdr_addrs_list) + ) + + # filter by start and end dates + predictions_df = predictions_df.filter( + (predictions_df["timestamp"] >= timestr_to_ut(start_timestr) / 1000) + & (predictions_df["timestamp"] <= timestr_to_ut(end_timestr) / 1000) ) - save_prediction_csv(predictions, csv_output_dir) - - get_cli_statistics(predictions) + predictoor_summary_df = get_predictoor_summary_stats(predictions_df) + print(predictoor_summary_df) diff --git a/pdr_backend/analytics/get_traction_info.py b/pdr_backend/analytics/get_traction_info.py index 5633f2395..54ce096dd 100644 --- a/pdr_backend/analytics/get_traction_info.py +++ b/pdr_backend/analytics/get_traction_info.py @@ -13,25 +13,31 @@ ) from pdr_backend.lake.gql_data_factory import GQLDataFactory from pdr_backend.ppss.ppss import PPSS +from pdr_backend.util.timeutil import timestr_to_ut @enforce_types def get_traction_info_main( ppss: PPSS, start_timestr: str, end_timestr: str, pq_dir: str ): - lake_ss = ppss.lake_ss - lake_ss.d["st_timestr"] = start_timestr - lake_ss.d["fin_timestr"] = end_timestr - gql_data_factory = GQLDataFactory(ppss) gql_dfs = gql_data_factory.get_gql_dfs() - - if len(gql_dfs) == 0: - print("No records found. Please adjust start and end times.") + if len(gql_dfs) == 0 or gql_dfs["pdr_predictions"].shape[0] == 0: + print("No records found. Please adjust start and end times inside ppss.yaml.") return predictions_df = gql_dfs["pdr_predictions"] + # filter by start and end dates + predictions_df = predictions_df.filter( + (predictions_df["timestamp"] >= timestr_to_ut(start_timestr) / 1000) + & (predictions_df["timestamp"] <= timestr_to_ut(end_timestr) / 1000) + ) + + if predictions_df.shape[0] == 0: + print("No records found. Please adjust start and end times params.") + return + # calculate predictoor traction statistics and draw plots stats_df = get_traction_statistics(predictions_df) plot_traction_cum_sum_statistics(stats_df, pq_dir) diff --git a/pdr_backend/analytics/predictoor_stats.py b/pdr_backend/analytics/predictoor_stats.py index 43bb355ed..8880a128a 100644 --- a/pdr_backend/analytics/predictoor_stats.py +++ b/pdr_backend/analytics/predictoor_stats.py @@ -1,11 +1,10 @@ import os -from typing import Dict, List, Set, Tuple, TypedDict +from typing import Set, Tuple, TypedDict import matplotlib.pyplot as plt import polars as pl from enforce_typing import enforce_types -from pdr_backend.subgraph.prediction import Prediction from pdr_backend.util.csvs import get_plots_dir @@ -13,6 +12,7 @@ class PairTimeframeStat(TypedDict): pair: str timeframe: str accuracy: float + exchange: str stake: float payout: float number_of_predictions: int @@ -28,107 +28,41 @@ class PredictoorStat(TypedDict): @enforce_types -def aggregate_prediction_statistics( - all_predictions: List[Prediction], -) -> Tuple[Dict[str, Dict], int]: - """ - Aggregates statistics from a list of prediction objects. It organizes statistics - by currency pair and timeframe and predictor address. For each category, it - tallies the total number of predictions, the number of correct predictions, - and the total stakes and payouts. It also returns the total number of correct - predictions across all categories. - - Args: - all_predictions (List[Prediction]): A list of Prediction objects to aggregate. - - Returns: - Tuple[Dict[str, Dict], int]: A tuple containing a dictionary of aggregated - statistics and the total number of correct predictions. - """ - stats: Dict[str, Dict] = {"pair_timeframe": {}, "predictor": {}} - correct_predictions = 0 - - for prediction in all_predictions: - pair_timeframe_key = (prediction.pair, prediction.timeframe) - predictor_key = prediction.user - source = prediction.source - - is_correct = prediction.prediction == prediction.trueval - - if pair_timeframe_key not in stats["pair_timeframe"]: - stats["pair_timeframe"][pair_timeframe_key] = { - "correct": 0, - "total": 0, - "stake": 0, - "payout": 0.0, - } - - if predictor_key not in stats["predictor"]: - stats["predictor"][predictor_key] = { - "correct": 0, - "total": 0, - "stake": 0, - "payout": 0.0, - "details": set(), - } - - if is_correct: - correct_predictions += 1 - stats["pair_timeframe"][pair_timeframe_key]["correct"] += 1 - stats["predictor"][predictor_key]["correct"] += 1 - - stats["pair_timeframe"][pair_timeframe_key]["total"] += 1 - stats["pair_timeframe"][pair_timeframe_key]["stake"] += prediction.stake - stats["pair_timeframe"][pair_timeframe_key]["payout"] += prediction.payout - - stats["predictor"][predictor_key]["total"] += 1 - stats["predictor"][predictor_key]["stake"] += prediction.stake - stats["predictor"][predictor_key]["payout"] += prediction.payout - stats["predictor"][predictor_key]["details"].add( - (prediction.pair, prediction.timeframe, source) - ) - - return stats, correct_predictions - +def get_feed_summary_stats(predictions_df: pl.DataFrame) -> pl.DataFrame: + # 1 - filter from lake only the rows that you're looking for + df = predictions_df.filter( + ~((pl.col("trueval").is_null()) | (pl.col("payout").is_null())) + ) -@enforce_types -def get_cli_statistics(all_predictions: List[Prediction]) -> None: - total_predictions = len(all_predictions) + # Group by pair + df = df.group_by(["pair", "timeframe"]).agg( + pl.col("source").first().alias("source"), + pl.col("payout").sum().alias("sum_payout"), + pl.col("stake").sum().alias("sum_stake"), + pl.col("prediction").count().alias("num_predictions"), + (pl.col("prediction").sum() / pl.col("pair").count() * 100).alias("accuracy"), + ) - stats, correct_predictions = aggregate_prediction_statistics(all_predictions) + return df - if total_predictions == 0: - print("No predictions found.") - return - if correct_predictions == 0: - print("No correct predictions found.") - return +@enforce_types +def get_predictoor_summary_stats(predictions_df: pl.DataFrame) -> pl.DataFrame: + # 1 - filter from lake only the rows that you're looking for + df = predictions_df.filter( + ~((pl.col("trueval").is_null()) | (pl.col("payout").is_null())) + ) - print(f"Overall Accuracy: {correct_predictions/total_predictions*100:.2f}%") + # Group by pair + df = df.group_by(["user", "pair", "timeframe"]).agg( + pl.col("source").first().alias("source"), + pl.col("payout").sum().alias("sum_payout"), + pl.col("stake").sum().alias("sum_stake"), + pl.col("prediction").count().alias("num_predictions"), + (pl.col("prediction").sum() / pl.col("pair").count() * 100).alias("accuracy"), + ) - for key, stat_pair_timeframe_item in stats["pair_timeframe"].items(): - pair, timeframe = key - accuracy = ( - stat_pair_timeframe_item["correct"] - / stat_pair_timeframe_item["total"] - * 100 - ) - print(f"Accuracy for Pair: {pair}, Timeframe: {timeframe}: {accuracy:.2f}%") - print(f"Total stake: {stat_pair_timeframe_item['stake']}") - print(f"Total payout: {stat_pair_timeframe_item['payout']}") - print(f"Number of predictions: {stat_pair_timeframe_item['total']}\n") - - for predictoor_addr, stat_predictoor_item in stats["predictor"].items(): - accuracy = stat_predictoor_item["correct"] / stat_predictoor_item["total"] * 100 - print(f"Accuracy for Predictoor Address: {predictoor_addr}: {accuracy:.2f}%") - print(f"Stake: {stat_predictoor_item['stake']}") - print(f"Payout: {stat_predictoor_item['payout']}") - print(f"Number of predictions: {stat_predictoor_item['total']}") - print("Details of Predictions:") - for detail in stat_predictoor_item["details"]: - print(f"Pair: {detail[0]}, Timeframe: {detail[1]}, Source: {detail[2]}") - print("\n") + return df @enforce_types diff --git a/pdr_backend/analytics/test/test_get_predictions_info.py b/pdr_backend/analytics/test/test_get_predictions_info.py index 61c4663cc..06ae42aa9 100644 --- a/pdr_backend/analytics/test/test_get_predictions_info.py +++ b/pdr_backend/analytics/test/test_get_predictions_info.py @@ -1,67 +1,174 @@ -from unittest.mock import Mock, patch - +from unittest.mock import patch from enforce_typing import enforce_types - +import polars as pl +from pdr_backend.lake.table_pdr_predictions import ( + _object_list_to_df, + predictions_schema, +) +from pdr_backend.util.timeutil import timestr_to_ut from pdr_backend.analytics.get_predictions_info import get_predictions_info_main from pdr_backend.ppss.ppss import mock_ppss -from pdr_backend.subgraph.subgraph_predictions import FilterMode @enforce_types +@patch( + "pdr_backend.analytics.get_predictions_info.get_feed_summary_stats", + spec=pl.DataFrame, +) +@patch("pdr_backend.analytics.get_predictions_info.GQLDataFactory.get_gql_dfs") def test_get_predictions_info_main_mainnet( + mock_get_polars, + mock_get_stats, + _sample_first_predictions, + tmpdir, +): + ppss = mock_ppss(["binance BTC/USDT c 5m"], "sapphire-mainnet", str(tmpdir)) + predictions_df = _object_list_to_df(_sample_first_predictions, predictions_schema) + mock_get_polars.return_value = {"pdr_predictions": predictions_df} + + st_timestr = "2023-12-03" + fin_timestr = "2023-12-05" + feed_addr = "0x2d8e2267779d27c2b3ed5408408ff15d9f3a3152" + + get_predictions_info_main( + ppss, + st_timestr, + fin_timestr, + feed_addr, + ) + + # manualy filter predictions for latter check Predictions + predictions_df = predictions_df.filter( + predictions_df["ID"].map_elements(lambda x: x.split("-")[0]).is_in([feed_addr]) + ) + + preds_df = predictions_df.filter( + (predictions_df["timestamp"] >= timestr_to_ut(st_timestr) / 1000) + & (predictions_df["timestamp"] <= timestr_to_ut(fin_timestr) / 1000) + ) + + # number of rows from data frames are the same + assert mock_get_stats.call_args[0][0][0].shape[0] == preds_df.shape[0] + + # the data frame was filtered by feed address + assert mock_get_stats.call_args[0][0][0]["ID"][0].split("-")[0] == feed_addr + + # data frame after filtering is same as manual filtered dataframe + pl.DataFrame.equals(mock_get_stats.call_args, preds_df) + + assert mock_get_polars.call_count == 1 + assert mock_get_stats.call_count == 1 + + +@enforce_types +@patch( + "pdr_backend.analytics.get_predictions_info.get_feed_summary_stats", + spec=pl.DataFrame, +) +@patch("pdr_backend.analytics.get_predictions_info.GQLDataFactory.get_gql_dfs") +def test_empty_data_frame_timeframe_filter_mainnet( + mock_get_polars, + mock_get_stats, _sample_first_predictions, tmpdir, ): ppss = mock_ppss(["binance BTC/USDT c 5m"], "sapphire-mainnet", str(tmpdir)) - mock_getids = Mock(return_value=["0x123", "0x234"]) - mock_fetch = Mock(return_value=_sample_first_predictions) - mock_save = Mock() - mock_getstats = Mock() - - PATH = "pdr_backend.analytics.get_predictions_info" - with patch(f"{PATH}.get_all_contract_ids_by_owner", mock_getids), patch( - f"{PATH}.fetch_filtered_predictions", mock_fetch - ), patch(f"{PATH}.save_analysis_csv", mock_save), patch( - f"{PATH}.get_cli_statistics", mock_getstats - ): - st_timestr = "2023-11-02" - fin_timestr = "2023-11-05" - - get_predictions_info_main( - ppss, "0x123", st_timestr, fin_timestr, "parquet_data/" - ) - - mock_fetch.assert_called_with( - 1698883200, - 1699142400, - ["0x123"], - "mainnet", - FilterMode.CONTRACT, - payout_only=True, - trueval_only=True, - ) - mock_save.assert_called() - mock_getstats.assert_called_with(_sample_first_predictions) + predictions_df = _object_list_to_df(_sample_first_predictions, predictions_schema) + mock_get_polars.return_value = {"pdr_predictions": predictions_df} + + st_timestr = "2023-12-20" + fin_timestr = "2023-12-21" + feed_addr = "0x2d8e2267779d27c2b3ed5408408ff15d9f3a3152" + + get_predictions_info_main( + ppss, + st_timestr, + fin_timestr, + feed_addr, + ) + + # manualy filter predictions for latter check Predictions + predictions_df = predictions_df.filter( + predictions_df["ID"].map_elements(lambda x: x.split("-")[0]).is_in([feed_addr]) + ) + + preds_df = predictions_df.filter( + (predictions_df["timestamp"] >= timestr_to_ut(st_timestr) / 1000) + & (predictions_df["timestamp"] <= timestr_to_ut(fin_timestr) / 1000) + ) + + # number of rows from data frames are the same + assert mock_get_stats.call_args[0][0][0].shape[0] == preds_df.shape[0] + + # the data frame is empy + assert mock_get_stats.call_args[0][0][0].shape[0] == 0 + + assert mock_get_polars.call_count == 1 + assert mock_get_stats.call_count == 1 @enforce_types -def test_get_predictions_info_empty(tmpdir, capfd): +@patch( + "pdr_backend.analytics.get_predictions_info.get_feed_summary_stats", + spec=pl.DataFrame, +) +@patch("pdr_backend.analytics.get_predictions_info.GQLDataFactory.get_gql_dfs") +def test_empty_data_frame_feed_addr_filter_mainnet( + mock_get_polars, + mock_get_stats, + _sample_first_predictions, + tmpdir, +): ppss = mock_ppss(["binance BTC/USDT c 5m"], "sapphire-mainnet", str(tmpdir)) - mock_getids = Mock(return_value=[]) - mock_fetch = Mock(return_value={}) + predictions_df = _object_list_to_df(_sample_first_predictions, predictions_schema) + mock_get_polars.return_value = {"pdr_predictions": predictions_df} + + st_timestr = "2023-12-03" + fin_timestr = "2023-12-05" + feed_addr = "0x8e0we267779d27c2b3ed5408408ff15d9f3a3152" + + get_predictions_info_main( + ppss, + st_timestr, + fin_timestr, + feed_addr, + ) + + # manualy filter predictions for latter check Predictions + predictions_df = predictions_df.filter( + predictions_df["ID"].map_elements(lambda x: x.split("-")[0]).is_in([feed_addr]) + ) + + preds_df = predictions_df.filter( + (predictions_df["timestamp"] >= timestr_to_ut(st_timestr) / 1000) + & (predictions_df["timestamp"] <= timestr_to_ut(fin_timestr) / 1000) + ) + + # number of rows from data frames are the same + assert mock_get_stats.call_args[0][0][0].shape[0] == preds_df.shape[0] + + # the data frame is empy + assert mock_get_stats.call_args[0][0][0].shape[0] == 0 - PATH = "pdr_backend.analytics.get_predictions_info" - with patch(f"{PATH}.get_all_contract_ids_by_owner", mock_getids), patch( - f"{PATH}.fetch_filtered_predictions", mock_fetch - ): - st_timestr = "2023-11-02" - fin_timestr = "2023-11-05" + assert mock_get_polars.call_count == 1 + assert mock_get_stats.call_count == 1 - get_predictions_info_main( - ppss, "0x123", st_timestr, fin_timestr, "parquet_data/" - ) + +@enforce_types +@patch("pdr_backend.analytics.get_predictions_info.GQLDataFactory.get_gql_dfs") +def test_get_predictions_info_empty(mock_get_polars, tmpdir, capfd): + ppss = mock_ppss(["binance BTC/USDT c 5m"], "sapphire-mainnet", str(tmpdir)) + + mock_get_polars.return_value = {"pdr_predictions": pl.DataFrame()} + + get_predictions_info_main( + ppss, + "2023-11-03", + "2023-11-05", + "0x2d8e2267779d27c2b3ed5408408ff15d9f3a3152", + ) assert ( "No records found. Please adjust start and end times" in capfd.readouterr().out diff --git a/pdr_backend/analytics/test/test_get_predictoors_info.py b/pdr_backend/analytics/test/test_get_predictoors_info.py index 2b736f336..3662f6ddf 100644 --- a/pdr_backend/analytics/test/test_get_predictoors_info.py +++ b/pdr_backend/analytics/test/test_get_predictoors_info.py @@ -1,38 +1,144 @@ -from unittest.mock import Mock, patch +from unittest.mock import patch from enforce_typing import enforce_types +import polars as pl +from pdr_backend.lake.table_pdr_predictions import ( + _object_list_to_df, + predictions_schema, +) from pdr_backend.analytics.get_predictoors_info import get_predictoors_info_main from pdr_backend.ppss.ppss import mock_ppss -from pdr_backend.subgraph.subgraph_predictions import FilterMode +from pdr_backend.util.timeutil import timestr_to_ut @enforce_types -def test_get_predictoors_info_main_mainnet(tmpdir): +@patch( + "pdr_backend.analytics.get_predictoors_info.get_predictoor_summary_stats", + spec=pl.DataFrame, +) +@patch("pdr_backend.analytics.get_predictoors_info.GQLDataFactory.get_gql_dfs") +def test_get_predictoors_info_main_mainnet( + mock_get_polars, mock_get_stats, _sample_first_predictions, tmpdir +): ppss = mock_ppss(["binance BTC/USDT c 5m"], "sapphire-mainnet", str(tmpdir)) - mock_fetch = Mock(return_value=[]) - mock_save = Mock() - mock_getstats = Mock() - - PATH = "pdr_backend.analytics.get_predictoors_info" - with patch(f"{PATH}.fetch_filtered_predictions", mock_fetch), patch( - f"{PATH}.save_prediction_csv", mock_save - ), patch(f"{PATH}.get_cli_statistics", mock_getstats): - get_predictoors_info_main( - ppss, - "0x123", - "2023-01-01", - "2023-01-02", - "parquet_data/", - ) - - mock_fetch.assert_called_with( - 1672531200, - 1672617600, - ["0x123"], - "mainnet", - FilterMode.PREDICTOOR, - ) - mock_save.assert_called_with([], "parquet_data/") - mock_getstats.assert_called_with([]) + predictions_df = _object_list_to_df(_sample_first_predictions, predictions_schema) + mock_get_polars.return_value = {"pdr_predictions": predictions_df} + + st_timestr = "2023-12-03" + fin_timestr = "2023-12-05" + user_addr = "0xaaaa4cb4ff2584bad80ff5f109034a891c3d88dd" + get_predictoors_info_main( + ppss, + st_timestr, + fin_timestr, + user_addr, + ) + + # manualy filter predictions for latter check Predictions + predictions_df = predictions_df.filter(predictions_df["user"].is_in([user_addr])) + preds_df = predictions_df.filter( + (predictions_df["timestamp"] >= timestr_to_ut(st_timestr) / 1000) + & (predictions_df["timestamp"] <= timestr_to_ut(fin_timestr) / 1000) + ) + + # data frame after filtering is same as manual filtered dataframe + pl.DataFrame.equals(mock_get_stats.call_args, preds_df) + + # number of rows from data frames are the same + assert mock_get_stats.call_args[0][0][0].shape[0] == preds_df.shape[0] + + # the data frame was filtered by user address + assert mock_get_stats.call_args[0][0][0]["user"][0] == user_addr + + assert mock_get_polars.call_count == 1 + assert mock_get_stats.call_count == 1 + + +@enforce_types +@patch( + "pdr_backend.analytics.get_predictoors_info.get_predictoor_summary_stats", + spec=pl.DataFrame, +) +@patch("pdr_backend.analytics.get_predictoors_info.GQLDataFactory.get_gql_dfs") +def test_empty_data_frame_timeframe_filter_mainnet( + mock_get_polars, mock_get_stats, _sample_first_predictions, tmpdir +): + ppss = mock_ppss(["binance BTC/USDT c 5m"], "sapphire-mainnet", str(tmpdir)) + + predictions_df = _object_list_to_df(_sample_first_predictions, predictions_schema) + mock_get_polars.return_value = {"pdr_predictions": predictions_df} + + st_timestr = "2023-12-20" + fin_timestr = "2023-12-30" + user_addr = "0xaaaa4cb4ff2584bad80ff5f109034a891c3d88dd" + get_predictoors_info_main( + ppss, + st_timestr, + fin_timestr, + user_addr, + ) + + # manualy filter predictions for latter check Predictions + predictions_df = predictions_df.filter(predictions_df["user"].is_in([user_addr])) + preds_df = predictions_df.filter( + (predictions_df["timestamp"] >= timestr_to_ut(st_timestr) / 1000) + & (predictions_df["timestamp"] <= timestr_to_ut(fin_timestr) / 1000) + ) + + # data frame after filtering is same as manual filtered dataframe + pl.DataFrame.equals(mock_get_stats.call_args, preds_df) + + # number of rows from data frames are the same + assert mock_get_stats.call_args[0][0][0].shape[0] == preds_df.shape[0] + + # the data frame is empy + assert mock_get_stats.call_args[0][0][0].shape[0] == 0 + + assert mock_get_polars.call_count == 1 + assert mock_get_stats.call_count == 1 + + +@enforce_types +@patch( + "pdr_backend.analytics.get_predictoors_info.get_predictoor_summary_stats", + spec=pl.DataFrame, +) +@patch("pdr_backend.analytics.get_predictoors_info.GQLDataFactory.get_gql_dfs") +def test_empty_data_frame_user_address_filter_mainnet( + mock_get_polars, mock_get_stats, _sample_first_predictions, tmpdir +): + ppss = mock_ppss(["binance BTC/USDT c 5m"], "sapphire-mainnet", str(tmpdir)) + + predictions_df = _object_list_to_df(_sample_first_predictions, predictions_schema) + mock_get_polars.return_value = {"pdr_predictions": predictions_df} + + st_timestr = "2023-12-03" + fin_timestr = "2023-12-05" + user_addr = "0xbbbb4cb4ff2584bad80ff5f109034a891c3d223" + get_predictoors_info_main( + ppss, + st_timestr, + fin_timestr, + user_addr, + ) + + # manualy filter predictions for latter check Predictions + predictions_df = predictions_df.filter(predictions_df["user"].is_in([user_addr])) + preds_df = predictions_df.filter( + (predictions_df["timestamp"] >= timestr_to_ut(st_timestr) / 1000) + & (predictions_df["timestamp"] <= timestr_to_ut(fin_timestr) / 1000) + ) + + # data frame after filtering is same as manual filtered dataframe + pl.DataFrame.equals(mock_get_stats.call_args, preds_df) + + # number of rows from data frames are the same + assert mock_get_stats.call_args[0][0][0].shape[0] == preds_df.shape[0] + + # the data frame is empy + assert mock_get_stats.call_args[0][0][0].shape[0] == 0 + + assert mock_get_polars.call_count == 1 + assert mock_get_stats.call_count == 1 diff --git a/pdr_backend/analytics/test/test_get_traction_info.py b/pdr_backend/analytics/test/test_get_traction_info.py index d13ccb180..6e520bd94 100644 --- a/pdr_backend/analytics/test/test_get_traction_info.py +++ b/pdr_backend/analytics/test/test_get_traction_info.py @@ -1,100 +1,68 @@ -from unittest.mock import Mock, patch +from unittest.mock import patch import polars as pl -import pytest from enforce_typing import enforce_types from pdr_backend.analytics.get_traction_info import get_traction_info_main from pdr_backend.ppss.ppss import mock_ppss -from pdr_backend.subgraph.subgraph_predictions import FilterMode from pdr_backend.util.timeutil import timestr_to_ut +from pdr_backend.lake.table_pdr_predictions import ( + _object_list_to_df, + predictions_schema, +) @enforce_types +@patch("pdr_backend.analytics.get_traction_info.get_traction_statistics") +@patch("pdr_backend.analytics.get_traction_info.plot_traction_cum_sum_statistics") +@patch("pdr_backend.analytics.get_traction_info.plot_traction_daily_statistics") +@patch("pdr_backend.analytics.get_traction_info.GQLDataFactory.get_gql_dfs") def test_get_traction_info_main_mainnet( + mock_predictions_df, + mock_plot_daily, + mock_plot_cumsum, + mock_traction_stat, _sample_daily_predictions, tmpdir, ): ppss = mock_ppss(["binance BTC/USDT c 5m"], "sapphire-mainnet", str(tmpdir)) + predictions_df = _object_list_to_df(_sample_daily_predictions, predictions_schema) - mock_traction_stat = Mock() - mock_plot_cumsum = Mock() - mock_plot_daily = Mock() - mock_getids = Mock(return_value=["0x123"]) - mock_fetch = Mock(return_value=_sample_daily_predictions) + mock_predictions_df.return_value = {"pdr_predictions": predictions_df} - PATH = "pdr_backend.analytics.get_traction_info" - PATH2 = "pdr_backend.lake" - with patch(f"{PATH}.get_traction_statistics", mock_traction_stat), patch( - f"{PATH}.plot_traction_cum_sum_statistics", mock_plot_cumsum - ), patch(f"{PATH}.plot_traction_daily_statistics", mock_plot_daily), patch( - f"{PATH2}.gql_data_factory.get_all_contract_ids_by_owner", mock_getids - ), patch( - f"{PATH2}.table_pdr_predictions.fetch_filtered_predictions", mock_fetch - ): - st_timestr = "2023-11-02" - fin_timestr = "2023-11-05" + st_timestr = "2023-11-02" + fin_timestr = "2023-11-05" - get_traction_info_main(ppss, st_timestr, fin_timestr, "parquet_data/") + get_traction_info_main(ppss, st_timestr, fin_timestr, "parquet_data/") - mock_fetch.assert_called_with( - 1698883200, - 1699142400, - ["0x123"], - "mainnet", - FilterMode.CONTRACT_TS, - payout_only=False, - trueval_only=False, - ) - - # calculate ms locally so we can filter raw Predictions - st_ut = timestr_to_ut(st_timestr) - fin_ut = timestr_to_ut(fin_timestr) - st_ut_sec = st_ut // 1000 - fin_ut_sec = fin_ut // 1000 - - # Get all predictions into a dataframe - preds = [ - x - for x in _sample_daily_predictions - if st_ut_sec <= x.timestamp <= fin_ut_sec - ] - preds = [pred.__dict__ for pred in preds] - preds_df = pl.DataFrame(preds) - preds_df = preds_df.with_columns( - [ - pl.col("timestamp").mul(1000).alias("timestamp"), - ] - ) + # calculate ms locally so we can filter raw Predictions + preds_df = predictions_df.filter( + (predictions_df["timestamp"] >= timestr_to_ut(st_timestr) / 1000) + & (predictions_df["timestamp"] <= timestr_to_ut(fin_timestr) / 1000) + ) - # Assert calls and values - pl.DataFrame.equals(mock_traction_stat.call_args, preds_df) - mock_plot_cumsum.assert_called() - mock_plot_daily.assert_called() + # Assert calls and values + pl.DataFrame.equals(mock_traction_stat.call_args, preds_df) + mock_plot_cumsum.assert_called() + mock_plot_daily.assert_called() @enforce_types -def test_get_traction_info_empty(tmpdir, capfd): +@patch("pdr_backend.analytics.get_traction_info.GQLDataFactory.get_gql_dfs") +def test_get_traction_info_empty( + mock_predictions_df, + tmpdir, + capfd, +): ppss = mock_ppss(["binance BTC/USDT c 5m"], "sapphire-mainnet", str(tmpdir)) - mock_empty = Mock(return_value=[]) - - PATH = "pdr_backend.analytics.get_traction_info" - with patch(f"{PATH}.GQLDataFactory.get_gql_dfs", mock_empty): - st_timestr = "2023-11-02" - fin_timestr = "2023-11-05" + mock_predictions_df.return_value = {"pdr_predictions": pl.DataFrame()} + st_timestr = "2023-11-02" + fin_timestr = "2023-11-05" - get_traction_info_main(ppss, st_timestr, fin_timestr, "parquet_data/") + get_traction_info_main(ppss, st_timestr, fin_timestr, "parquet_data/") assert ( - "No records found. Please adjust start and end times." in capfd.readouterr().out + "No records found. Please adjust start and end times inside ppss.yaml." + in capfd.readouterr().out ) - - with patch("requests.post") as mock_post: - mock_post.return_value.status_code = 503 - # don't actually sleep in tests - with patch("time.sleep"): - with pytest.raises(Exception): - get_traction_info_main(ppss, st_timestr, fin_timestr, "parquet_data/") - - assert mock_post.call_count == 3 diff --git a/pdr_backend/analytics/test/test_predictoor_stats.py b/pdr_backend/analytics/test/test_predictoor_stats.py index c04370a8e..0f385e231 100644 --- a/pdr_backend/analytics/test/test_predictoor_stats.py +++ b/pdr_backend/analytics/test/test_predictoor_stats.py @@ -2,11 +2,17 @@ import polars as pl from enforce_typing import enforce_types +from pdr_backend.lake.table_pdr_predictions import ( + _object_list_to_df, + predictions_schema, + predictoor_summary_df_schema, + feed_summary_df_schema, +) from pdr_backend.analytics.predictoor_stats import ( - aggregate_prediction_statistics, calculate_slot_daily_statistics, - get_cli_statistics, + get_feed_summary_stats, + get_predictoor_summary_stats, get_slot_statistics, get_traction_statistics, plot_slot_daily_statistics, @@ -16,35 +22,21 @@ @enforce_types -def test_aggregate_prediction_statistics(_sample_first_predictions): - stats, correct_predictions = aggregate_prediction_statistics( - _sample_first_predictions - ) - assert isinstance(stats, dict) - assert "pair_timeframe" in stats - assert "predictor" in stats - assert correct_predictions == 1 # Adjust based on your sample data +def test_get_feed_statistics(_sample_first_predictions): + predictions_df = _object_list_to_df(_sample_first_predictions, predictions_schema) + feed_summary_df = get_feed_summary_stats(predictions_df) + + assert isinstance(feed_summary_df, pl.DataFrame) + assert len(feed_summary_df.schema) == len(feed_summary_df_schema) @enforce_types -def test_get_cli_statistics(capsys, _sample_first_predictions): - get_cli_statistics(_sample_first_predictions) - captured = capsys.readouterr() - output = captured.out - assert "Overall Accuracy" in output - assert "Accuracy for Pair" in output - assert "Accuracy for Predictoor Address" in output - - get_cli_statistics([]) - assert "No predictions found" in capsys.readouterr().out - - with patch( - "pdr_backend.analytics.predictoor_stats.aggregate_prediction_statistics" - ) as mock: - mock.return_value = ({}, 0) - get_cli_statistics(_sample_first_predictions) - - assert "No correct predictions found" in capsys.readouterr().out +def test_get_predictoor_statistics(_sample_first_predictions): + predictions_df = _object_list_to_df(_sample_first_predictions, predictions_schema) + predictoor_summary_df = get_predictoor_summary_stats(predictions_df) + + assert isinstance(predictoor_summary_df, pl.DataFrame) + assert len(predictoor_summary_df.schema) == len(predictoor_summary_df_schema) @enforce_types diff --git a/pdr_backend/cli/cli_module.py b/pdr_backend/cli/cli_module.py index 81ee76185..0d6dc404b 100644 --- a/pdr_backend/cli/cli_module.py +++ b/pdr_backend/cli/cli_module.py @@ -113,13 +113,13 @@ def do_claim_ROSE(args): @enforce_types def do_get_predictoors_info(args): ppss = PPSS(yaml_filename=args.PPSS_FILE, network=args.NETWORK) - get_predictoors_info_main(ppss, args.PDRS, args.ST, args.END, args.PQDIR) + get_predictoors_info_main(ppss, args.ST, args.END, args.PDRS) @enforce_types def do_get_predictions_info(args): ppss = PPSS(yaml_filename=args.PPSS_FILE, network=args.NETWORK) - get_predictions_info_main(ppss, args.FEEDS, args.ST, args.END, args.PQDIR) + get_predictions_info_main(ppss, args.ST, args.END, args.FEEDS) @enforce_types diff --git a/pdr_backend/lake/gql_data_factory.py b/pdr_backend/lake/gql_data_factory.py index d2348965b..856799106 100644 --- a/pdr_backend/lake/gql_data_factory.py +++ b/pdr_backend/lake/gql_data_factory.py @@ -13,8 +13,8 @@ get_pdr_subscriptions_df, subscriptions_schema, ) -from pdr_backend.ppss.ppss import PPSS from pdr_backend.subgraph.subgraph_predictions import get_all_contract_ids_by_owner +from pdr_backend.ppss.ppss import PPSS from pdr_backend.util.networkutil import get_sapphire_postfix from pdr_backend.util.timeutil import current_ut_ms, pretty_timestr diff --git a/pdr_backend/lake/table_pdr_predictions.py b/pdr_backend/lake/table_pdr_predictions.py index eac364e4b..b3767a48d 100644 --- a/pdr_backend/lake/table_pdr_predictions.py +++ b/pdr_backend/lake/table_pdr_predictions.py @@ -27,6 +27,29 @@ "user": Utf8, } +# PREDICTOOR_SUMMARY_SCHEMA +predictoor_summary_df_schema = { + "timeframe": Utf8, + "pair": Utf8, + "source": Utf8, + "accuracy": Float64, + "sum_stake": Float64, + "sum_payout": Float64, + "n_predictions": Int64, + "user": Utf8, +} + +# FEED_SUMMARY_SCHEMA +feed_summary_df_schema = { + "timeframe": Utf8, + "pair": Utf8, + "source": Utf8, + "accuracy": Float64, + "sum_stake": Float64, + "sum_payout": Float64, + "n_predictions": Int64, +} + def _transform_timestamp_to_ms(df: pl.DataFrame) -> pl.DataFrame: df = df.with_columns( diff --git a/pdr_backend/publisher/publish_assets.py b/pdr_backend/publisher/publish_assets.py index 20f07a0bd..f70cffb32 100644 --- a/pdr_backend/publisher/publish_assets.py +++ b/pdr_backend/publisher/publish_assets.py @@ -42,5 +42,4 @@ def publish_assets(web3_pp: Web3PP, publisher_ss: PublisherSS): cut=_CUT, web3_pp=web3_pp, ) - print("Done publishing.") diff --git a/pdr_backend/subgraph/core_subgraph.py b/pdr_backend/subgraph/core_subgraph.py index 1ead768c3..052bdecaf 100644 --- a/pdr_backend/subgraph/core_subgraph.py +++ b/pdr_backend/subgraph/core_subgraph.py @@ -31,5 +31,4 @@ def query_subgraph( ) result = response.json() - return result diff --git a/pdr_backend/subgraph/prediction.py b/pdr_backend/subgraph/prediction.py index ed1cccf24..904be4b27 100644 --- a/pdr_backend/subgraph/prediction.py +++ b/pdr_backend/subgraph/prediction.py @@ -18,6 +18,7 @@ def __init__( source: str, payout: Union[float, None], slot: int, # slot/epoch timestamp + address: str, user: str, ) -> None: self.ID = ID @@ -30,6 +31,7 @@ def __init__( self.source = source self.payout = payout self.slot = slot + self.address = (address,) self.user = user @@ -49,10 +51,11 @@ def mock_prediction(prediction_tuple: tuple) -> Prediction: source, payout, slot, + address, user, ) = prediction_tuple - ID = f"{pair_str}-{timeframe_str}-{slot}-{user}" + ID = f"{address}-{slot}-{user}" return Prediction( ID=ID, pair=pair_str, @@ -63,6 +66,7 @@ def mock_prediction(prediction_tuple: tuple) -> Prediction: timestamp=timestamp, source=source, payout=payout, + address=address, slot=slot, user=user, ) @@ -101,6 +105,7 @@ def mock_daily_predictions() -> List[Prediction]: "binance", 0.0, 1701503100, + "0x18f54cc21b7a2fdd011bea06bba7801b280e3151", "0xaaaa4cb4ff2584bad80ff5f109034a891c3d88dd", ), ( @@ -113,6 +118,7 @@ def mock_daily_predictions() -> List[Prediction]: "binance", 0.0, 1701589500, + "0x2d8e2267779d27c2b3ed5408408ff15d9f3a3152", "0xaaaa4cb4ff2584bad80ff5f109034a891c3d88dd", ), ] @@ -128,6 +134,7 @@ def mock_daily_predictions() -> List[Prediction]: "binance", 0.0500, 1701675900, + "0x30f1c55e72fe105e4a1fbecdff3145fc14177695", "0xd2a24cb4ff2584bad80ff5f109034a891c3d88dd", ), ( @@ -140,6 +147,7 @@ def mock_daily_predictions() -> List[Prediction]: "binance", 0.0, 1701503000, + "0xaaaa4cb4ff2584bad80ff5f109034a891c3d88dd", "0xbbbb4cb4ff2584bad80ff5f109034a891c3d88dd", ), ( @@ -152,6 +160,7 @@ def mock_daily_predictions() -> List[Prediction]: "binance", 0.0500, 1701589500, + "0x18f54cc21b7a2fdd011bea06bba7801b280e3151", "0xbbbb4cb4ff2584bad80ff5f109034a891c3d88dd", ), ( @@ -164,6 +173,7 @@ def mock_daily_predictions() -> List[Prediction]: "kraken", 0.0500, 1701675900, + "0x31fabe1fc9887af45b77c7d1e13c5133444ebfbd", "0xbbbb4cb4ff2584bad80ff5f109034a891c3d88dd", ), ( @@ -176,6 +186,7 @@ def mock_daily_predictions() -> List[Prediction]: "binance", 0.0, 1701589500, + "0x30f1c55e72fe105e4a1fbecdff3145fc14177695", "0xcccc4cb4ff2584bad80ff5f109034a891c3d88dd", ), ( @@ -188,6 +199,7 @@ def mock_daily_predictions() -> List[Prediction]: "binance", 0.0500, 1701675900, + "0x30f1c55e72fe105e4a1fbecdff3145fc14177695", "0xd2a24cb4ff2584bad80ff5f109034a891c3d88dd", ), ] @@ -203,6 +215,7 @@ def mock_daily_predictions() -> List[Prediction]: "binance", 0.0500, 1698865200, + "0x30f1c55e72fe105e4a1fbecdff3145fc14177695", "0xd2a24cb4ff2584bad80ff5f109034a891c3d88dd", ), ( @@ -215,6 +228,7 @@ def mock_daily_predictions() -> List[Prediction]: "binance", 0.0, 1698951600, + "0xe66421fd29fc2d27d0724f161f01b8cbdcd69690", "0xd2a24cb4ff2584bad80ff5f109034a891c3d88dd", ), ( @@ -227,6 +241,7 @@ def mock_daily_predictions() -> List[Prediction]: "binance", 0.0500, 1699038000, + "0x18f54cc21b7a2fdd011bea06bba7801b280e3151", "0xd2a24cb4ff2584bad80ff5f109034a891c3d88dd", ), ( @@ -239,6 +254,7 @@ def mock_daily_predictions() -> List[Prediction]: "kraken", 0.0500, 1699124400, + "0x31fabe1fc9887af45b77c7d1e13c5133444ebfbd", "0xd2a24cb4ff2584bad80ff5f109034a891c3d88dd", ), ( @@ -251,6 +267,7 @@ def mock_daily_predictions() -> List[Prediction]: "binance", 0.0, 1701589500, + "0xaa6515c138183303b89b98aea756b54f711710c5", "0xd2a24cb4ff2584bad80ff5f109034a891c3d88dd", ), ( @@ -263,6 +280,7 @@ def mock_daily_predictions() -> List[Prediction]: "binance", 0.0500, 1699300800, + "0x30f1c55e72fe105e4a1fbecdff3145fc14177695", "0xd2a24cb4ff2584bad80ff5f109034a891c3d88dd", ), ] diff --git a/pdr_backend/subgraph/subgraph_predictions.py b/pdr_backend/subgraph/subgraph_predictions.py index 9ddef8474..491bacb54 100644 --- a/pdr_backend/subgraph/subgraph_predictions.py +++ b/pdr_backend/subgraph/subgraph_predictions.py @@ -139,7 +139,7 @@ def fetch_filtered_predictions( timestamp = prediction_sg_dict["timestamp"] slot = prediction_sg_dict["slot"]["slot"] user = prediction_sg_dict["user"]["id"] - + address = prediction_sg_dict["id"].split("-")[0] trueval = None payout = None predicted_value = None @@ -167,6 +167,7 @@ def fetch_filtered_predictions( timestamp=timestamp, source=source, payout=payout, + address=address, slot=slot, user=user, ) diff --git a/pdr_backend/subgraph/test/test_prediction.py b/pdr_backend/subgraph/test/test_prediction.py index a73e584aa..0946fbfa7 100644 --- a/pdr_backend/subgraph/test/test_prediction.py +++ b/pdr_backend/subgraph/test/test_prediction.py @@ -6,15 +6,17 @@ @enforce_types def test_predictions(): predictions = mock_first_predictions() + contract_address_1 = "0x18f54cc21b7a2fdd011bea06bba7801b280e3151" + contract_address_2 = "0x2d8e2267779d27c2b3ed5408408ff15d9f3a3152" assert len(predictions) == 2 assert isinstance(predictions[0], Prediction) assert isinstance(predictions[1], Prediction) assert ( predictions[0].ID - == "ADA/USDT-5m-1701503100-0xaaaa4cb4ff2584bad80ff5f109034a891c3d88dd" + == contract_address_1 + "-1701503100-0xaaaa4cb4ff2584bad80ff5f109034a891c3d88dd" ) assert ( predictions[1].ID - == "BTC/USDT-5m-1701589500-0xaaaa4cb4ff2584bad80ff5f109034a891c3d88dd" + == contract_address_2 + "-1701589500-0xaaaa4cb4ff2584bad80ff5f109034a891c3d88dd" ) diff --git a/pdr_backend/subgraph/test/test_subgraph_predictions.py b/pdr_backend/subgraph/test/test_subgraph_predictions.py index d42053400..50d96782e 100644 --- a/pdr_backend/subgraph/test/test_subgraph_predictions.py +++ b/pdr_backend/subgraph/test/test_subgraph_predictions.py @@ -12,6 +12,8 @@ get_all_contract_ids_by_owner, ) +ADA_CONTRACT_ADDRESS = "0x18f54cc21b7a2fdd011bea06bba7801b280e3151" + SAMPLE_PREDICTION = Prediction( # pylint: disable=line-too-long ID="0x18f54cc21b7a2fdd011bea06bba7801b280e3151-1698527100-0xd2a24cb4ff2584bad80ff5f109034a891c3d88dd", @@ -24,6 +26,7 @@ source="binance", payout=0.0, slot=1698527100, + address="0x18f54cc21b7a2fdd011bea06bba7801b280e3151", user="0xd2a24cb4ff2584bad80ff5f109034a891c3d88dd", ) @@ -116,6 +119,7 @@ def test_fetch_filtered_predictions(mock_query_subgraph): assert isinstance(predictions[0], Prediction) assert predictions[0].user == "0xd2a24cb4ff2584bad80ff5f109034a891c3d88dd" assert predictions[0].pair == "ADA/USDT" + assert predictions[0].address[0] == "0x18f54cc21b7a2fdd011bea06bba7801b280e3151" assert predictions[0].trueval is False assert predictions[0].prediction is True assert mock_query_subgraph.call_count == 1 diff --git a/system_tests/test_get_predictions_info_system.py b/system_tests/test_get_predictions_info_system.py index e3ca742e4..0bc507a49 100644 --- a/system_tests/test_get_predictions_info_system.py +++ b/system_tests/test_get_predictions_info_system.py @@ -1,41 +1,41 @@ import sys from unittest.mock import Mock, patch, MagicMock - +from pdr_backend.lake.table_pdr_predictions import ( + _object_list_to_df, + predictions_schema, +) +from pdr_backend.subgraph.prediction import Prediction from pdr_backend.cli import cli_module from pdr_backend.ppss.web3_pp import Web3PP -from pdr_backend.subgraph.prediction import Prediction -from pdr_backend.subgraph.subgraph_predictions import FilterMode from pdr_backend.util.web3_config import Web3Config -@patch("pdr_backend.analytics.get_predictions_info.get_cli_statistics") -@patch("pdr_backend.analytics.get_predictions_info.fetch_filtered_predictions") -@patch("pdr_backend.analytics.get_predictions_info.save_analysis_csv") -@patch("pdr_backend.analytics.get_predictions_info.get_all_contract_ids_by_owner") -def test_topup( - mock_get_all_contract_ids_by_owner, - mock_save_analysis_csv, - mock_fetch_filtered_predictions, - mock_get_cli_statistics, -): - mock_get_all_contract_ids_by_owner.return_value = ["0xfeed"] +@patch("pdr_backend.analytics.get_predictions_info.get_feed_summary_stats") +@patch("pdr_backend.analytics.get_predictions_info.GQLDataFactory.get_gql_dfs") +def test_topup(mock_get_polars, mock_get_stats): + feed_addr = "0x2d8e2267779d27c2b3ed5408408ff15d9f3a3152" + user_addr = "0xaaaa4cb4ff2584bad80ff5f109034a891c3d88dd" mock_predictions = [ Prediction( - "0xfeed", + "{feed_addr}-31232-{user_addr}", "BTC", "5m", True, 100.0, False, - 100, + 1701532572, "binance", 10.0, 10, - "0xuser", + feed_addr, + user_addr, ) ] - mock_fetch_filtered_predictions.return_value = mock_predictions + predictions_df = _object_list_to_df(mock_predictions, predictions_schema) + + mock_get_stats.return_value = predictions_df + mock_get_polars.return_value = {"pdr_predictions": predictions_df} mock_web3_pp = MagicMock(spec=Web3PP) mock_web3_pp.network = "sapphire-mainnet" @@ -46,6 +46,7 @@ def test_topup( mock_web3_config = Mock(spec=Web3Config) mock_web3_config.w3 = Mock() mock_web3_pp.web3_config = mock_web3_config + mock_web3_pp.owner_addrs = user_addr with patch("pdr_backend.ppss.ppss.Web3PP", return_value=mock_web3_pp): # Mock sys.argv @@ -58,7 +59,7 @@ def test_topup( "ppss.yaml", "development", "--FEEDS", - "0xfeed", + "{feed_addr}", ] with patch("builtins.print") as mock_print: @@ -69,17 +70,8 @@ def test_topup( mock_print.assert_any_call("Arguments:") mock_print.assert_any_call("PPSS_FILE=ppss.yaml") mock_print.assert_any_call("NETWORK=development") - mock_print.assert_any_call("FEEDS=0xfeed") + mock_print.assert_any_call("FEEDS={feed_addr}") # Additional assertions - mock_save_analysis_csv.assert_called_with(mock_predictions, "./dir") - mock_get_cli_statistics.assert_called_with(mock_predictions) - mock_fetch_filtered_predictions.assert_called_with( - 1701388800, - 1703980800, - ["0xfeed"], - "mainnet", - FilterMode.CONTRACT, - payout_only=True, - trueval_only=True, - ) + mock_get_polars.assert_called_with() + mock_get_stats.call_args[0][0].equals(predictions_df) diff --git a/system_tests/test_get_predictoors_info_system.py b/system_tests/test_get_predictoors_info_system.py index 83408a40d..070c20802 100644 --- a/system_tests/test_get_predictoors_info_system.py +++ b/system_tests/test_get_predictoors_info_system.py @@ -1,18 +1,19 @@ import sys from unittest.mock import Mock, patch, MagicMock - +from pdr_backend.lake.table_pdr_predictions import ( + _object_list_to_df, + predictions_schema, +) +from pdr_backend.subgraph.prediction import Prediction from pdr_backend.cli import cli_module from pdr_backend.ppss.web3_pp import Web3PP from pdr_backend.util.web3_config import Web3Config -@patch("pdr_backend.analytics.get_predictoors_info.fetch_filtered_predictions") -@patch("pdr_backend.analytics.get_predictoors_info.get_cli_statistics") -@patch("pdr_backend.analytics.get_predictoors_info.save_prediction_csv") -def test_topup( - mock_fetch_filtered_predictions, mock_get_cli_statistics, mock_save_prediction_csv -): +@patch("pdr_backend.analytics.get_predictoors_info.get_predictoor_summary_stats") +@patch("pdr_backend.analytics.get_predictoors_info.GQLDataFactory.get_gql_dfs") +def test_topup(mock_get_polars, mock_get_stats): mock_web3_pp = MagicMock(spec=Web3PP) mock_web3_pp.network = "sapphire-mainnet" mock_web3_pp.subgraph_url = ( @@ -23,22 +24,37 @@ def test_topup( mock_web3_config.w3 = Mock() mock_web3_config.w3.eth.get_balance.return_value = 100 mock_web3_pp.web3_config = mock_web3_config - mock_web3_pp.web3_config.owner = "0xowner" + mock_web3_pp.web3_config.owner = "0xaaaa4cb4ff2584bad80ff5f109034a891c3d88dd" + mock_web3_pp.owner_addrs = "0xaaaa4cb4ff2584bad80ff5f109034a891c3d88dd" mock_token = MagicMock() mock_token.balanceOf.return_value = int(5e18) mock_token.transfer.return_value = True + feed_addr = "0x2d8e2267779d27c2b3ed5408408ff15d9f3a3152" + user_addr = "0xaaaa4cb4ff2584bad80ff5f109034a891c3d88dd" + + mock_predictions = [ + Prediction( + "{feed_addr}-31232-{user_addr}", + "BTC", + "5m", + True, + 100.0, + False, + 1701532572, + "binance", + 10.0, + 10, + feed_addr, + user_addr, + ) + ] + + predictions_df = _object_list_to_df(mock_predictions, predictions_schema) + + mock_get_stats.return_value = predictions_df + mock_get_polars.return_value = {"pdr_predictions": predictions_df} - mock_query_subgraph = Mock() - mock_query_subgraph.return_value = { - "data": { - "predictContracts": [ - {}, - {}, - {}, - ] - } - } with patch("pdr_backend.contract.token.Token", return_value=mock_token), patch( "pdr_backend.ppss.ppss.Web3PP", return_value=mock_web3_pp ): @@ -52,7 +68,7 @@ def test_topup( "ppss.yaml", "development", "--PDRS", - "0xpredictoor", + "{user_addr}", ] with patch("builtins.print") as mock_print: @@ -63,9 +79,8 @@ def test_topup( mock_print.assert_any_call("Arguments:") mock_print.assert_any_call("PPSS_FILE=ppss.yaml") mock_print.assert_any_call("NETWORK=development") - mock_print.assert_any_call("PDRS=0xpredictoor") + mock_print.assert_any_call("PDRS={user_addr}") # Additional assertions - mock_fetch_filtered_predictions.assert_called() - mock_get_cli_statistics.assert_called() - mock_save_prediction_csv.assert_called() + mock_get_polars.assert_called() + mock_get_stats.call_args[0][0].equals(predictions_df) diff --git a/system_tests/test_get_traction_info_system.py b/system_tests/test_get_traction_info_system.py index 6a0d360e3..c59b9ea84 100644 --- a/system_tests/test_get_traction_info_system.py +++ b/system_tests/test_get_traction_info_system.py @@ -1,21 +1,41 @@ import sys - from unittest.mock import Mock, patch, MagicMock +from pdr_backend.lake.table_pdr_predictions import ( + _object_list_to_df, + predictions_schema, +) +from pdr_backend.subgraph.prediction import Prediction from pdr_backend.cli import cli_module from pdr_backend.ppss.web3_pp import Web3PP from pdr_backend.util.web3_config import Web3Config @patch("pdr_backend.analytics.get_traction_info.plot_slot_daily_statistics") -@patch("pdr_backend.lake.gql_data_factory.get_all_contract_ids_by_owner") -@patch("pdr_backend.analytics.predictoor_stats.plt.savefig") -def test_topup( - mock_savefig, - mock_get_all_contract_ids_by_owner, - mock_plot_slot_daily_statistics, -): - mock_get_all_contract_ids_by_owner.return_value = ["0xfeed"] +@patch("pdr_backend.analytics.get_traction_info.GQLDataFactory.get_gql_dfs") +def test_topup(mock_get_polars, mock_plot_stats): + feed_addr = "0x2d8e2267779d27c2b3ed5408408ff15d9f3a3152" + user_addr = "0xaaaa4cb4ff2584bad80ff5f109034a891c3d88dd" + mock_predictions = [ + Prediction( + "{feed_addr}-31232-{0xaaaa4cb4ff2584bad80ff5f109034a891c3d88dd}", + "BTC", + "5m", + True, + 100.0, + False, + 1701532572, + "binance", + 10.0, + 10, + feed_addr, + user_addr, + ) + ] + + predictions_df = _object_list_to_df(mock_predictions, predictions_schema) + + mock_get_polars.return_value = {"pdr_predictions": predictions_df} mock_web3_pp = MagicMock(spec=Web3PP) mock_web3_pp.network = "sapphire-mainnet" @@ -49,20 +69,9 @@ def test_topup( mock_print.assert_any_call("Arguments:") mock_print.assert_any_call("PPSS_FILE=ppss.yaml") mock_print.assert_any_call("NETWORK=development") - mock_print.assert_any_call( - "Get predictions data across many feeds and timeframes." - ) - mock_print.assert_any_call( - " Data start: timestamp=1701388800000, dt=2023-12-01_00:00:00.000" - ) - mock_print.assert_any_call( - " Data fin: timestamp=1703980800000, dt=2023-12-31_00:00:00.000" - ) mock_print.assert_any_call( "Chart created:", "./dir/plots/daily_unique_predictoors.png" ) # Additional assertions - mock_get_all_contract_ids_by_owner.assert_called() - mock_plot_slot_daily_statistics.assert_called() - mock_savefig.assert_called_with("./dir/plots/daily_unique_predictoors.png") + mock_plot_stats.assert_called()