diff --git a/neurons/validator.py b/neurons/validator.py index f8c0ceeff..a645faa48 100644 --- a/neurons/validator.py +++ b/neurons/validator.py @@ -288,6 +288,7 @@ def rc_priority_fn(synapse: template.protocol.ValidatorCheckpoint) -> float: self.position_syncer.sync_positions( False, candidate_data=self.position_syncer.read_validator_checkpoint_from_gcloud_zip()) + self.position_manager.update_all_positions() diff --git a/tests/vali_tests/test_positions.py b/tests/vali_tests/test_positions.py index 04b042ec0..1f33d90f3 100644 --- a/tests/vali_tests/test_positions.py +++ b/tests/vali_tests/test_positions.py @@ -203,6 +203,9 @@ def test_simple_long_position_with_explicit_FLAT(self): 'close_ms': None, 'return_at_close': 0.9995, 'current_return': 1.0, + 'max_return': 1.0, + 'drawdown': 0.9995, + 'mdd': 0.9995, 'miner_hotkey': self.DEFAULT_MINER_HOTKEY, 'open_ms': FEE_V6_TIME_MS, 'trade_pair': self.DEFAULT_TRADE_PAIR, @@ -220,6 +223,9 @@ def test_simple_long_position_with_explicit_FLAT(self): 'close_ms': o2.processed_ms, 'return_at_close': 1.0987836209351618, 'current_return': 1.1, + 'max_return': 1.0987836209351618, + 'drawdown': 1.0, + 'mdd': 0.9995, 'miner_hotkey': self.DEFAULT_MINER_HOTKEY, 'open_ms': FEE_V6_TIME_MS, 'trade_pair': self.DEFAULT_TRADE_PAIR, @@ -298,6 +304,9 @@ def test_simple_long_position_with_implicit_FLAT(self): 'close_ms': None, 'return_at_close': 0.9995, 'current_return': 1.0, + 'max_return': 1.0, + 'drawdown': 0.9995, + 'mdd': 0.9995, 'miner_hotkey': self.DEFAULT_MINER_HOTKEY, 'open_ms': FEE_V6_TIME_MS, 'trade_pair': self.DEFAULT_TRADE_PAIR, @@ -315,6 +324,9 @@ def test_simple_long_position_with_implicit_FLAT(self): 'close_ms': o2.processed_ms, 'return_at_close': 1.9958850251380311, 'current_return': 2.0, + 'max_return': 1.9958850251380311, + 'drawdown': 1.0, + 'mdd': 0.9995, 'miner_hotkey': self.DEFAULT_MINER_HOTKEY, 'open_ms': FEE_V6_TIME_MS, 'trade_pair': self.DEFAULT_TRADE_PAIR, @@ -353,6 +365,9 @@ def test_simple_short_position_with_explicit_FLAT(self): 'close_ms': None, 'return_at_close': 0.9995, 'current_return': 1.0, + 'max_return': 1.0, + 'drawdown': 0.9995, + 'mdd': 0.9995, 'miner_hotkey': self.DEFAULT_MINER_HOTKEY, 'open_ms': o1.processed_ms, 'trade_pair': self.DEFAULT_TRADE_PAIR, @@ -370,6 +385,9 @@ def test_simple_short_position_with_explicit_FLAT(self): 'close_ms': o2.processed_ms, 'return_at_close': 1.0985508997795737, 'current_return': 1.1, + 'max_return': 1.0985508997795737, + 'drawdown': 1.0, + 'mdd': 0.9995, 'miner_hotkey': self.DEFAULT_MINER_HOTKEY, 'open_ms': o1.processed_ms, 'trade_pair': self.DEFAULT_TRADE_PAIR, @@ -405,6 +423,9 @@ def test_liquidated_long_position_with_explicit_FLAT(self): 'close_ms': None, 'return_at_close': 0.99, 'current_return': 1.0, + 'max_return': 1.0, + 'drawdown': 0.99, + 'mdd': 0.99, 'miner_hotkey': self.DEFAULT_MINER_HOTKEY, 'open_ms': self.DEFAULT_OPEN_MS, 'trade_pair': self.DEFAULT_TRADE_PAIR, @@ -422,6 +443,9 @@ def test_liquidated_long_position_with_explicit_FLAT(self): 'close_ms': o2.processed_ms, 'return_at_close': 0.0, 'current_return': 0.0, + 'max_return': 1.0, + 'drawdown': 0.0, + 'mdd': 0.0, 'miner_hotkey': self.DEFAULT_MINER_HOTKEY, 'open_ms': self.DEFAULT_OPEN_MS, 'trade_pair': self.DEFAULT_TRADE_PAIR, @@ -457,6 +481,9 @@ def test_liquidated_short_position_with_explicit_FLAT(self): 'close_ms': None, 'return_at_close': .999, 'current_return': 1.0, + 'max_return': 1.0, + 'drawdown': 0.999, + 'mdd': 0.999, 'miner_hotkey': self.DEFAULT_MINER_HOTKEY, 'open_ms': self.DEFAULT_OPEN_MS, 'trade_pair': self.DEFAULT_TRADE_PAIR, @@ -474,6 +501,9 @@ def test_liquidated_short_position_with_explicit_FLAT(self): 'close_ms': o2.processed_ms, 'return_at_close': 0.0, 'current_return': 0.0, + 'max_return': 1.0, + 'drawdown': 0.0, + 'mdd': 0.0, 'miner_hotkey': self.DEFAULT_MINER_HOTKEY, 'open_ms': self.DEFAULT_OPEN_MS, 'trade_pair': self.DEFAULT_TRADE_PAIR, @@ -514,6 +544,9 @@ def test_liquidated_short_position_with_no_FLAT(self): 'close_ms': None, 'return_at_close': .999, 'current_return': 1.0, + 'max_return': 1.0, + 'drawdown': 0.999, + 'mdd': 0.999, 'miner_hotkey': self.DEFAULT_MINER_HOTKEY, 'open_ms': self.DEFAULT_OPEN_MS, 'trade_pair': self.DEFAULT_TRADE_PAIR, @@ -531,6 +564,9 @@ def test_liquidated_short_position_with_no_FLAT(self): 'close_ms': o2.processed_ms, 'return_at_close': 0.0, 'current_return': 0.0, + 'max_return': 1.0, + 'drawdown': 0.0, + 'mdd': 0.0, 'miner_hotkey': self.DEFAULT_MINER_HOTKEY, 'open_ms': self.DEFAULT_OPEN_MS, 'trade_pair': self.DEFAULT_TRADE_PAIR, @@ -549,6 +585,9 @@ def test_liquidated_short_position_with_no_FLAT(self): 'close_ms': o2.processed_ms, 'return_at_close': 0.0, 'current_return': 0.0, + 'max_return': 1.0, + 'drawdown': 0.0, + 'mdd': 0.0, 'miner_hotkey': self.DEFAULT_MINER_HOTKEY, 'open_ms': self.DEFAULT_OPEN_MS, 'trade_pair': self.DEFAULT_TRADE_PAIR, @@ -589,6 +628,9 @@ def test_liquidated_long_position_with_no_FLAT(self): 'close_ms': None, 'return_at_close': 0.99, 'current_return': 1.0, + 'max_return': 1.0, + 'drawdown': 0.99, + 'mdd': 0.99, 'miner_hotkey': self.DEFAULT_MINER_HOTKEY, 'open_ms': self.DEFAULT_OPEN_MS, 'trade_pair': self.DEFAULT_TRADE_PAIR, @@ -606,6 +648,9 @@ def test_liquidated_long_position_with_no_FLAT(self): 'close_ms': o2.processed_ms, 'return_at_close': 0.0, 'current_return': 0.0, + 'max_return': 1.0, + 'drawdown': 0.0, + 'mdd': 0.0, 'miner_hotkey': self.DEFAULT_MINER_HOTKEY, 'open_ms': self.DEFAULT_OPEN_MS, 'trade_pair': self.DEFAULT_TRADE_PAIR, @@ -624,6 +669,9 @@ def test_liquidated_long_position_with_no_FLAT(self): 'close_ms': o2.processed_ms, 'return_at_close': 0.0, 'current_return': 0.0, + 'max_return': 1.0, + 'drawdown': 0.0, + 'mdd': 0.0, 'miner_hotkey': self.DEFAULT_MINER_HOTKEY, 'open_ms': self.DEFAULT_OPEN_MS, 'trade_pair': self.DEFAULT_TRADE_PAIR, @@ -659,6 +707,9 @@ def test_simple_short_position_with_implicit_FLAT(self): 'close_ms': None, 'return_at_close': .999, 'current_return': 1.0, + 'max_return': 1.0, + 'drawdown': 0.999, + 'mdd': 0.999, 'miner_hotkey': self.DEFAULT_MINER_HOTKEY, 'open_ms': self.DEFAULT_OPEN_MS, 'trade_pair': self.DEFAULT_TRADE_PAIR, @@ -676,6 +727,9 @@ def test_simple_short_position_with_implicit_FLAT(self): 'close_ms': o2.processed_ms, 'return_at_close': 1.4985, 'current_return': 1.5, + 'max_return': 1.4985, + 'drawdown': 1.0, + 'mdd': 0.999, 'miner_hotkey': self.DEFAULT_MINER_HOTKEY, 'open_ms': self.DEFAULT_OPEN_MS, 'trade_pair': self.DEFAULT_TRADE_PAIR, @@ -845,6 +899,9 @@ def test_three_orders_with_longs_no_drawdown(self): 'close_ms': None, 'return_at_close': .999, 'current_return': 1.0, + 'max_return': 1.0, + 'drawdown': 0.999, + 'mdd': 0.999, 'miner_hotkey': self.DEFAULT_MINER_HOTKEY, 'open_ms': self.DEFAULT_OPEN_MS, 'trade_pair': self.DEFAULT_TRADE_PAIR, @@ -862,6 +919,9 @@ def test_three_orders_with_longs_no_drawdown(self): 'close_ms': None, 'return_at_close': 1.9978, 'current_return': 2.0, + 'max_return': 1.9978, + 'drawdown': 1.0, + 'mdd': 0.999, 'miner_hotkey': self.DEFAULT_MINER_HOTKEY, 'open_ms': self.DEFAULT_OPEN_MS, 'trade_pair': self.DEFAULT_TRADE_PAIR, @@ -879,6 +939,9 @@ def test_three_orders_with_longs_no_drawdown(self): 'close_ms': 5000, 'return_at_close': 1.9978, 'current_return': 2.0, + 'max_return': 1.9978, + 'drawdown': 1.0, + 'mdd': 0.999, 'miner_hotkey': self.DEFAULT_MINER_HOTKEY, 'open_ms': self.DEFAULT_OPEN_MS, 'trade_pair': self.DEFAULT_TRADE_PAIR, @@ -913,6 +976,9 @@ def test_two_orders_with_a_loss(self): 'close_ms': None, 'return_at_close': 0.9995, 'current_return': 1.0, + 'max_return': 1.0, + 'drawdown': 0.9995, + 'mdd': 0.9995, 'miner_hotkey': self.DEFAULT_MINER_HOTKEY, 'open_ms': o1.processed_ms, 'trade_pair': self.DEFAULT_TRADE_PAIR, @@ -930,6 +996,9 @@ def test_two_orders_with_a_loss(self): 'close_ms': o2.processed_ms, 'return_at_close': 0.4995, 'current_return': 0.5, + 'max_return': 1.0, + 'drawdown': 0.49950000000000006, + 'mdd': 0.49950000000000006, 'miner_hotkey': self.DEFAULT_MINER_HOTKEY, 'open_ms': o1.processed_ms, 'trade_pair': self.DEFAULT_TRADE_PAIR, @@ -972,6 +1041,9 @@ def test_three_orders_with_a_loss_and_then_a_gain(self): 'close_ms': None, 'return_at_close': 0.9995, 'current_return': 1.0, + 'max_return': 1.0, + 'drawdown': 0.9995, + 'mdd': 0.9995, 'miner_hotkey': self.DEFAULT_MINER_HOTKEY, 'open_ms': o1.processed_ms, 'trade_pair': self.DEFAULT_TRADE_PAIR, @@ -989,6 +1061,9 @@ def test_three_orders_with_a_loss_and_then_a_gain(self): 'close_ms': None, 'return_at_close': 0.499725, 'current_return': 0.5, + 'max_return': 1.0, + 'drawdown': 0.499725, + 'mdd': 0.499725, 'miner_hotkey': self.DEFAULT_MINER_HOTKEY, 'open_ms': o1.processed_ms, 'trade_pair': self.DEFAULT_TRADE_PAIR, @@ -1006,6 +1081,9 @@ def test_three_orders_with_a_loss_and_then_a_gain(self): 'close_ms': None, 'return_at_close': 1.04937, 'current_return': 1.05, + 'max_return': 1.04937, + 'drawdown': 1.0, + 'mdd': 0.499725, 'miner_hotkey': self.DEFAULT_MINER_HOTKEY, 'open_ms': o1.processed_ms, 'trade_pair': self.DEFAULT_TRADE_PAIR, @@ -1065,6 +1143,9 @@ def test_returns_on_large_price_increase(self): 'close_ms': 5000, 'return_at_close': 43.7609328, 'current_return': 43.81, + 'max_return': 43.7609328, + 'drawdown': 1.0, + 'mdd': 0.999, 'miner_hotkey': self.DEFAULT_MINER_HOTKEY, 'open_ms': self.DEFAULT_OPEN_MS, 'trade_pair': self.DEFAULT_TRADE_PAIR, @@ -1123,6 +1204,9 @@ def test_returns_on_many_shorts(self): 'close_ms': 5000, 'return_at_close': 1.4313950399999997, 'current_return': 1.4329999999999998, + 'max_return': 1.4313950399999997, + 'drawdown': 1.0, + 'mdd': 0.999, 'miner_hotkey': self.DEFAULT_MINER_HOTKEY, 'open_ms': self.DEFAULT_OPEN_MS, 'trade_pair': self.DEFAULT_TRADE_PAIR, @@ -1182,6 +1266,9 @@ def test_returns_on_alternating_long_short(self): 'close_ms': 5000, 'return_at_close': 1.4364000000000001, 'current_return': 1.44, + 'max_return': 1.4364000000000001, + 'drawdown': 1.0, + 'mdd': 0.999, 'miner_hotkey': self.DEFAULT_MINER_HOTKEY, 'open_ms': self.DEFAULT_OPEN_MS, 'trade_pair': self.DEFAULT_TRADE_PAIR, @@ -1262,6 +1349,9 @@ def test_two_positions_no_collisions(self): 'close_ms': None, 'return_at_close': 0.999982, 'current_return': 1.0, + 'max_return': 1.0, + 'drawdown': 0.999982, + 'mdd': 0.999982, 'miner_hotkey': position1.miner_hotkey, 'open_ms': weekday_time_ms, 'trade_pair': trade_pair1, @@ -1279,6 +1369,9 @@ def test_two_positions_no_collisions(self): 'close_ms': None, 'return_at_close': .999986, 'current_return': 1.0, + 'max_return': 1.0, + 'drawdown': 0.999986, + 'mdd': 0.999986, 'miner_hotkey': position2.miner_hotkey, 'open_ms': weekday_time_ms, 'trade_pair': trade_pair2, @@ -1325,6 +1418,9 @@ def test_transition_to_positional_leverage_v2_high_positive_leverage(self): 'close_ms': None, 'return_at_close': position.return_at_close, 'current_return': position.current_return, + 'max_return': position.max_return, + 'drawdown': position.drawdown, + 'mdd': position.mdd, 'miner_hotkey': position.miner_hotkey, 'open_ms': self.DEFAULT_OPEN_MS, 'trade_pair': self.DEFAULT_TRADE_PAIR, @@ -1352,6 +1448,9 @@ def test_transition_to_positional_leverage_v2_high_positive_leverage(self): 'close_ms': None, 'return_at_close': position.return_at_close, 'current_return': position.current_return, + 'max_return': position.max_return, + 'drawdown': position.drawdown, + 'mdd': position.mdd, 'miner_hotkey': position.miner_hotkey, 'open_ms': self.DEFAULT_OPEN_MS, 'trade_pair': self.DEFAULT_TRADE_PAIR, @@ -1401,6 +1500,9 @@ def test_transition_to_positional_leverage_v2_small_positive_leverage(self): 'close_ms': None, 'return_at_close': position.return_at_close, 'current_return': position.current_return, + 'max_return': position.max_return, + 'drawdown': position.drawdown, + 'mdd': position.mdd, 'miner_hotkey': position.miner_hotkey, 'open_ms': self.DEFAULT_OPEN_MS, 'trade_pair': self.DEFAULT_TRADE_PAIR, @@ -1450,6 +1552,9 @@ def test_transition_to_positional_leverage_v2_small_negative_leverage(self): 'close_ms': None, 'return_at_close': position.return_at_close, 'current_return': position.current_return, + 'max_return': position.max_return, + 'drawdown': position.drawdown, + 'mdd': position.mdd, 'miner_hotkey': position.miner_hotkey, 'open_ms': self.DEFAULT_OPEN_MS, 'trade_pair': self.DEFAULT_TRADE_PAIR, @@ -1488,6 +1593,9 @@ def test_transition_to_positional_leverage_v2_high_negative_leverage(self): 'close_ms': None, 'return_at_close': position.return_at_close, 'current_return': position.current_return, + 'max_return': position.max_return, + 'drawdown': position.drawdown, + 'mdd': position.mdd, 'miner_hotkey': position.miner_hotkey, 'open_ms': self.DEFAULT_OPEN_MS, 'trade_pair': self.DEFAULT_TRADE_PAIR, @@ -1515,6 +1623,9 @@ def test_transition_to_positional_leverage_v2_high_negative_leverage(self): 'close_ms': None, 'return_at_close': position.return_at_close, 'current_return': position.current_return, + 'max_return': position.max_return, + 'drawdown': position.drawdown, + 'mdd': position.mdd, 'miner_hotkey': position.miner_hotkey, 'open_ms': self.DEFAULT_OPEN_MS, 'trade_pair': self.DEFAULT_TRADE_PAIR, @@ -1558,6 +1669,9 @@ def test_leverage_clamping_v1_long(self): 'close_ms': None, 'return_at_close': position.return_at_close, 'current_return': position.current_return, + 'max_return': position.max_return, + 'drawdown': position.drawdown, + 'mdd': position.mdd, 'miner_hotkey': position.miner_hotkey, 'open_ms': self.DEFAULT_OPEN_MS, 'trade_pair': self.DEFAULT_TRADE_PAIR, @@ -1602,6 +1716,9 @@ def test_leverage_clamping_v2_long(self): 'close_ms': None, 'return_at_close': position.return_at_close, 'current_return': position.current_return, + 'max_return': position.max_return, + 'drawdown': position.drawdown, + 'mdd': position.mdd, 'miner_hotkey': position.miner_hotkey, 'open_ms': LEVERAGE_BOUNDS_V2_START_TIME_MS, 'trade_pair': self.DEFAULT_TRADE_PAIR, @@ -1644,6 +1761,9 @@ def test_leverage_clamping_v1_skip_long_order(self): 'close_ms': None, 'return_at_close': position.return_at_close, 'current_return': position.current_return, + 'max_return': position.max_return, + 'drawdown': position.drawdown, + 'mdd': position.mdd, 'miner_hotkey': position.miner_hotkey, 'open_ms': 1000, 'trade_pair': self.DEFAULT_TRADE_PAIR, @@ -1683,6 +1803,9 @@ def test_leverage_clamping_v2_skip_long_order(self): 'close_ms': None, 'return_at_close': position.return_at_close, 'current_return': position.current_return, + 'max_return': position.max_return, + 'drawdown': position.drawdown, + 'mdd': position.mdd, 'miner_hotkey': position.miner_hotkey, 'open_ms': LEVERAGE_BOUNDS_V2_START_TIME_MS, 'trade_pair': self.DEFAULT_TRADE_PAIR, @@ -1726,6 +1849,9 @@ def test_leverage_clamping_short_v1(self): 'close_ms': None, 'return_at_close': position.return_at_close, 'current_return': position.current_return, + 'max_return': position.max_return, + 'drawdown': position.drawdown, + 'mdd': position.mdd, 'miner_hotkey': position.miner_hotkey, 'open_ms': self.DEFAULT_OPEN_MS, 'trade_pair': self.DEFAULT_TRADE_PAIR, @@ -1767,6 +1893,9 @@ def test_leverage_clamping_short_v2(self): 'close_ms': None, 'return_at_close': position.return_at_close, 'current_return': position.current_return, + 'max_return': position.max_return, + 'drawdown': position.drawdown, + 'mdd': position.mdd, 'miner_hotkey': position.miner_hotkey, 'open_ms': o1.processed_ms, 'trade_pair': self.DEFAULT_TRADE_PAIR, @@ -1836,6 +1965,9 @@ def test_leverage_v1_clamping_skip_short_order(self): 'close_ms': None, 'return_at_close': position.return_at_close, 'current_return': position.current_return, + 'max_return': position.max_return, + 'drawdown': position.drawdown, + 'mdd': position.mdd, 'miner_hotkey': position.miner_hotkey, 'open_ms': self.DEFAULT_OPEN_MS, 'trade_pair': self.DEFAULT_TRADE_PAIR, @@ -1875,6 +2007,9 @@ def test_leverage_v2_clamping_skip_short_order(self): 'close_ms': None, 'return_at_close': position.return_at_close, 'current_return': position.current_return, + 'max_return': position.max_return, + 'drawdown': position.drawdown, + 'mdd': position.mdd, 'miner_hotkey': position.miner_hotkey, 'open_ms': o1.processed_ms, 'trade_pair': self.DEFAULT_TRADE_PAIR, diff --git a/vali_objects/position.py b/vali_objects/position.py index 07df1f97b..0bf805dc2 100644 --- a/vali_objects/position.py +++ b/vali_objects/position.py @@ -49,6 +49,9 @@ class Position(BaseModel): average_entry_price: float = 0.0 position_type: Optional[OrderType] = None is_closed_position: bool = False + max_return: float = 1.0 + drawdown: float = 1.0 + mdd: float = 1.0 @model_validator(mode='before') def add_trade_pair_to_orders_and_self(cls, values): @@ -278,6 +281,9 @@ def rebuild_position_with_updated_orders(self): self.position_type = None self.is_closed_position = False self.position_type = None + self.max_return = 1.0 + self.drawdown = 1.0 + self.mdd = 1.0 self._update_position() @@ -289,6 +295,7 @@ def log_position_status(self): f"net leverage [{self.net_leverage}] " f"average entry price [{self.average_entry_price}] " f"return_at_close [{self.return_at_close}]" + f"drawdown [{self.drawdown}]" ) order_info = [ { @@ -574,3 +581,8 @@ def _update_position(self): # If the position is already closed, we don't need to process any more orders. break in case there are more orders. if self.position_type == OrderType.FLAT: break + + # calculate_drawdown + self.max_return = max(self.max_return, self.return_at_close) + self.drawdown = 1.0 + ((self.return_at_close - self.max_return) / self.max_return) + self.mdd = min(self.mdd, self.drawdown) \ No newline at end of file diff --git a/vali_objects/utils/position_manager.py b/vali_objects/utils/position_manager.py index 1a1342c4e..fd7138d2b 100644 --- a/vali_objects/utils/position_manager.py +++ b/vali_objects/utils/position_manager.py @@ -226,6 +226,14 @@ def dedupe_positions(self, positions, miner_hotkey): bt.logging.warning(f"Hotkey {miner_hotkey}: Deleted {n_positions_deleted} duplicate positions and {n_orders_deleted} " f"duplicate orders across {n_positions_rebuilt_with_new_orders} positions.") + def update_all_positions(self): + hotkey_to_positions = self.get_all_disk_positions_for_all_miners(sort_positions=True, only_open_positions=False, perform_exorcism=True) + + for hotkey, positions in hotkey_to_positions.items(): + for p in positions: + p.rebuild_position_with_updated_orders() + self.save_miner_position_to_disk(p, delete_open_position_if_exists=False) + def apply_order_corrections(self): """ This is our mechanism for manually synchronizing validator orders in situations where a bug prevented an