diff --git a/pdr_backend/ppss/predictoor_ss.py b/pdr_backend/ppss/predictoor_ss.py index eb331d658..23f6d0bcb 100644 --- a/pdr_backend/ppss/predictoor_ss.py +++ b/pdr_backend/ppss/predictoor_ss.py @@ -8,9 +8,10 @@ from pdr_backend.util.currency_types import Eth # Approaches: -# 1: Allocate up-vs-down stake equally (50-50). Baseline. -# 2: Allocate up-vs-down stake on model prediction confidence. -CAND_APPROACHES = [1, 2] +# 1: Two-sided: Allocate up-vs-down stake equally (50-50). Baseline. +# 2: Two-sided: Allocate up-vs-down stake on model prediction confidence. +# 3: One sided: If up, allocate 2's up-minus-down stake. If down, vice versa. +CAND_APPROACHES = [1, 2, 3] class PredictoorSS(SingleFeedMixin, StrMixin): diff --git a/pdr_backend/ppss/test/test_predictoor_ss.py b/pdr_backend/ppss/test/test_predictoor_ss.py index fe19a993e..0f9a48749 100644 --- a/pdr_backend/ppss/test/test_predictoor_ss.py +++ b/pdr_backend/ppss/test/test_predictoor_ss.py @@ -38,10 +38,13 @@ def test_predictoor_ss(): # test str assert "PredictoorSS" in str(ss) - # test setters + # test setters; test approach 2 & 3 ss.set_approach(2) assert ss.approach == 2 + ss.set_approach(3) + assert ss.approach == 3 + @enforce_types def test_predictoor_ss_test_dict(): @@ -77,14 +80,14 @@ def test_predictoor_ss_test_dict(): @enforce_types def test_predictoor_ss_bad_approach(): # catch bad approach in __init__() - for bad_approach in [0, 3]: + for bad_approach in [0, 4]: d = predictoor_ss_test_dict() d["approach"] = bad_approach with pytest.raises(ValueError): PredictoorSS(d) # catch bad approach in set_approach() - for bad_approach in [0, 3]: + for bad_approach in [0, 4]: d = predictoor_ss_test_dict() ss = PredictoorSS(d) with pytest.raises(ValueError): diff --git a/pdr_backend/predictoor/predictoor_agent.py b/pdr_backend/predictoor/predictoor_agent.py index 14868c703..a818e53b0 100644 --- a/pdr_backend/predictoor/predictoor_agent.py +++ b/pdr_backend/predictoor/predictoor_agent.py @@ -13,10 +13,10 @@ from pdr_backend.payout.payout import do_ocean_payout from pdr_backend.ppss.ppss import PPSS from pdr_backend.subgraph.subgraph_feed import print_feeds, SubgraphFeed +from pdr_backend.util.currency_types import Eth from pdr_backend.util.logutil import logging_has_stdout from pdr_backend.util.time_types import UnixTimeS from pdr_backend.util.web3_config import Web3Config -from pdr_backend.util.currency_types import Eth logger = logging.getLogger("predictoor_agent") @@ -243,21 +243,43 @@ def submit_prediction_txs( stake_down: Eth, target_slot: UnixTimeS, # a timestamp ): - logger.info("Submit 'up' prediction tx to chain...") + assert stake_up >= 0 and stake_down >= 0, (stake_up, stake_down) + + # case: no stake + if stake_up == 0 and stake_down == 0: + logger.info("Both 'up' and 'down' have 0 stake. So, no txs.") + return + + # case: 1-sided staking up (eg approach 3) + if stake_up > 0: + logger.info("Submit prediction tx 1/1 just 'up' to chain...") + tx1 = self.submit_1prediction_tx(True, stake_up, target_slot) + # note: we don't need special error-handling when just 1 tx + return + + # case: 1-sided staking down (eg approach 3) + if stake_down > 0: + logger.info("Submit prediction tx 1/1 just 'down' to chain...") + tx1 = self.submit_1prediction_tx(False, stake_down, target_slot) + # note: we don't need special error-handling when just 1 tx + return + + # case: 2-sided staking (eg approach 1, 2) + logger.info("Submit prediction tx 1/2 'up' to chain...") tx1 = self.submit_1prediction_tx(True, stake_up, target_slot) - logger.info("Submit 'down' prediction tx to chain...") + logger.info("Submit prediction tx 2/2 'down' to chain...") tx2 = self.submit_1prediction_tx(False, stake_down, target_slot) - # handle errors + # special error-handling when 2 txs, to avert danger when 1 of 2 fail if _tx_failed(tx1) or _tx_failed(tx2): s = "One or both txs failed. So, resubmit both with zero stake." s += f"\ntx1={tx1}\ntx2={tx2}" logger.warning(s) - logger.info("Re-submit 'up' prediction tx to chain... (stake=0)") + logger.info("Re-submit prediction tx 1/2 'up' to chain... (stake=0)") self.submit_1prediction_tx(True, Eth(1e-10), target_slot) - logger.info("Re-submit 'down' prediction tx to chain... (stake=0)") + logger.info("Re-submit prediction tx 2/2 'down' to chain... (stake=0)") self.submit_1prediction_tx(False, Eth(1e-10), target_slot) @enforce_types @@ -298,6 +320,8 @@ def calc_stakes(self) -> Tuple[Eth, Eth]: return self.calc_stakes1() if approach == 2: return self.calc_stakes2() + if approach == 3: + return self.calc_stakes3() raise ValueError(approach) @enforce_types @@ -321,14 +345,48 @@ def calc_stakes2(self) -> Tuple[Eth, Eth]: """ @description Calculate up-vs-down stake according to approach 2. - How: use classifier model's confidence + How: use classifier model's confidence, two-sided @return stake_up -- amt to stake up, in units of Eth stake_down -- amt to stake down, "" """ assert self.ppss.predictoor_ss.approach == 2 + (stake_up, stake_down) = self.calc_stakes_2ss_model() + return (stake_up, stake_down) + def calc_stakes3(self) -> Tuple[Eth, Eth]: + """ + @description + Calculate up-vs-down stake according to approach 3. + How: Like approach 2, but one-sided difference of (larger - smaller) + + @return + stake_up -- amt to stake up, in units of Eth + stake_down -- amt to stake down, "" + """ + assert self.ppss.predictoor_ss.approach == 3 + (stake_up, stake_down) = self.calc_stakes_2ss_model() + if stake_up == stake_down: + return (Eth(0), Eth(0)) + + if stake_up > stake_down: + return (stake_up - stake_down, Eth(0)) + + # stake_up < stake_down + return (Eth(0), stake_down - stake_up) + + @enforce_types + def calc_stakes_2ss_model(self) -> Tuple[Eth, Eth]: + """ + @description + Model-based calculate up-vs-down stake. + How: use classifier model's confidence + + @return + stake_up -- amt to stake up, in units of Eth + stake_down -- amt to stake down, "" + """ mergedohlcv_df = self.get_ohlcv_data() data_f = AimodelDataFactory(self.ppss.predictoor_ss) @@ -356,7 +414,7 @@ def calc_stakes2(self) -> Tuple[Eth, Eth]: @enforce_types def use_ohlcv_data(self) -> bool: """Do we use ohlcv data?""" - return self.ppss.predictoor_ss.approach == 2 + return self.ppss.predictoor_ss.approach in [2, 3] @enforce_types def get_ohlcv_data(self): diff --git a/pdr_backend/predictoor/test/test_predictoor_agent.py b/pdr_backend/predictoor/test/test_predictoor_agent.py index 04560c1b2..ac5447252 100644 --- a/pdr_backend/predictoor/test/test_predictoor_agent.py +++ b/pdr_backend/predictoor/test/test_predictoor_agent.py @@ -20,7 +20,10 @@ # =========================================================================== -# test approach 1 & 2 - main loop +# test approach {1, 2, 3} - main loop + +# do _not_ parameterize these. It's much easier to test them individually +# and debug when they're separate @enforce_types @@ -33,6 +36,11 @@ def test_predictoor_agent_main2(tmpdir, monkeypatch): _test_predictoor_agent_main(2, str(tmpdir), monkeypatch) +@enforce_types +def test_predictoor_agent_main3(tmpdir, monkeypatch): + _test_predictoor_agent_main(3, str(tmpdir), monkeypatch) + + @enforce_types def _test_predictoor_agent_main(approach: int, tmpdir: str, monkeypatch): """ @@ -40,7 +48,7 @@ def _test_predictoor_agent_main(approach: int, tmpdir: str, monkeypatch): Run the agent for a while, and then do some basic sanity checks. Uses get_agent_1feed, *not* 2feeds. """ - assert approach in [1, 2] + assert approach in [1, 2, 3] # mock tokens mock_token = Mock() diff --git a/ppss.yaml b/ppss.yaml index f6f21f50a..4ee40c941 100644 --- a/ppss.yaml +++ b/ppss.yaml @@ -10,7 +10,7 @@ lake_ss: predictoor_ss: predict_feed: binance BTC/USDT c 5m - approach: 2 # 1->50/50; 2->model-based + approach: 2 # 1->50/50; 2->two-sided model-based; 3-> one-sided model-based stake_amount: 100 # How much your bot stakes. In OCEAN per epoch, per feed sim_only: