diff --git a/.circleci/config.yml b/.circleci/config.yml deleted file mode 100644 index f1652674..00000000 --- a/.circleci/config.yml +++ /dev/null @@ -1,168 +0,0 @@ -version: 2.1 - -orbs: - python: circleci/python@2.1.1 - python-lib: dialogue/python-lib@0.1.55 - # coveralls: coveralls/coveralls@1.0.6 - -jobs: - black: - resource_class: small - parameters: - python-version: - type: string - docker: - - image: cimg/python:<< parameters.python-version >> - - steps: - - checkout - - - restore_cache: - name: Restore cached black venv - keys: - - v1-pypi-py-black-<< parameters.python-version >> - - - run: - name: Update & Activate black venv - command: | - python -m venv env/ - . env/bin/activate - python -m pip install --upgrade pip - pip install black==23.7.0 - - - save_cache: - name: Save cached black venv - paths: - - "env/" - key: v1-pypi-py-black-<< parameters.python-version >> - - - run: - name: Black format check - command: | - . env/bin/activate - black --line-length 79 --exclude '(env|venv|.eggs|.git)' --check . - - pylint: - resource_class: small - parameters: - python-version: - type: string - docker: - - image: cimg/python:<< parameters.python-version >> - - steps: - - checkout - - - run: - name: Install Pylint - command: | - python -m venv env/ - . env/bin/activate - pip install pylint - - - run: - name: Pylint check - command: | - . env/bin/activate - pylint --fail-on=W,E,F --exit-zero ./ - - check_compatibility: - parameters: - python_version: - type: string - docker: - - image: cimg/python:3.10 - steps: - - checkout - - run: - name: Check if requirements files have changed - command: ./scripts/check_requirements_changes.sh - - run: - name: Install dependencies and Check compatibility - command: | - if [ "$REQUIREMENTS_CHANGED" == "true" ]; then - sudo apt-get update - sudo apt-get install -y jq curl - ./scripts/check_compatibility.sh << parameters.python_version >> - else - echo "Skipping compatibility checks..." - fi - - build: - resource_class: medium - parallelism: 2 - parameters: - python-version: - type: string - docker: - - image: cimg/python:<< parameters.python-version >> - - steps: - - checkout - - - restore_cache: - name: Restore cached venv - keys: - - v1-pypi-py<< parameters.python-version >>-{{ checksum "requirements.txt" }} - - v1-pypi-py<< parameters.python-version >> - - - run: - name: Update & Activate venv - command: | - python -m venv env/ - . env/bin/activate - python -m pip install --upgrade pip - - - save_cache: - name: Save cached venv - paths: - - "env/" - key: v1-pypi-py<< parameters.python-version >>-{{ checksum "requirements.txt" }} - - - run: - name: Install Bittensor Subnet Template - command: | - . env/bin/activate - pip install -e . - - - store_test_results: - path: test-results - - store_artifacts: - path: test-results - - coveralls: - docker: - - image: cimg/python:3.10 - steps: - - run: - name: Combine Coverage - command: | - pip3 install --upgrade coveralls - coveralls --finish --rcfile .coveragerc || echo "Failed to upload coverage" - -workflows: - compatibility_checks: - jobs: - - check_compatibility: - python_version: "3.8" - name: check-compatibility-3.8 - - check_compatibility: - python_version: "3.9" - name: check-compatibility-3.9 - - check_compatibility: - python_version: "3.10" - name: check-compatibility-3.10 - - check_compatibility: - python_version: "3.11" - name: check-compatibility-3.11 - - pr-requirements: - jobs: - - black: - python-version: "3.8.12" - - pylint: - python-version: "3.8.12" - - build: - matrix: - parameters: - python-version: ["3.9.13", "3.10.6", "3.11.4"] diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml new file mode 100644 index 00000000..3c42c396 --- /dev/null +++ b/.github/workflows/ci.yaml @@ -0,0 +1,39 @@ +name: CI + +on: + push: + branches: + - main + pull_request: + branches: + - main + +jobs: + lint: + runs-on: ubuntu-latest + steps: + - name: Checkout code + uses: actions/checkout@v4 + + - name: Set up Python + uses: actions/setup-python@v5 + with: + python-version: '3.9' + cache: 'pip' + + - name: Install dependencies + run: | + python -m pip install --upgrade pip + pip install pylint black + pip install -r requirements.txt + + - name: Run Pylint + run: | + pylint --fail-on=W,E,F --exit-zero ./ + + - name: Run Black + run: | + black --line-length 79 --exclude '(env|venv|.eggs|.git)' --check . + + # - name: Run unit tests + # run: python -m unittest discover tests diff --git a/alembic/versions/09dc2532fe57_add_input_params_for_miner_predictions.py b/alembic/versions/09dc2532fe57_add_input_params_for_miner_predictions.py index 7e4391bd..2ca77dbc 100644 --- a/alembic/versions/09dc2532fe57_add_input_params_for_miner_predictions.py +++ b/alembic/versions/09dc2532fe57_add_input_params_for_miner_predictions.py @@ -5,6 +5,7 @@ Create Date: 2024-12-26 11:59:36.925991 """ + from typing import Sequence, Union from alembic import op @@ -12,21 +13,32 @@ # revision identifiers, used by Alembic. -revision: str = '09dc2532fe57' -down_revision: Union[str, None] = 'bc6d5957a826' +revision: str = "09dc2532fe57" +down_revision: Union[str, None] = "bc6d5957a826" branch_labels: Union[str, Sequence[str], None] = None depends_on: Union[str, Sequence[str], None] = None def upgrade() -> None: - op.add_column('miner_predictions', sa.Column('asset', sa.String, nullable=True)) - op.add_column('miner_predictions', sa.Column('time_increment', sa.Integer, nullable=True)) - op.add_column('miner_predictions', sa.Column('time_length', sa.Integer, nullable=True)) - op.add_column('miner_predictions', sa.Column('num_simulations', sa.Integer, nullable=True)) + op.add_column( + "miner_predictions", sa.Column("asset", sa.String, nullable=True) + ) + op.add_column( + "miner_predictions", + sa.Column("time_increment", sa.Integer, nullable=True), + ) + op.add_column( + "miner_predictions", + sa.Column("time_length", sa.Integer, nullable=True), + ) + op.add_column( + "miner_predictions", + sa.Column("num_simulations", sa.Integer, nullable=True), + ) def downgrade() -> None: - op.drop_column('miner_predictions', 'num_simulations') - op.drop_column('miner_predictions', 'time_length') - op.drop_column('miner_predictions', 'time_increment') - op.drop_column('miner_predictions', 'asset') + op.drop_column("miner_predictions", "num_simulations") + op.drop_column("miner_predictions", "time_length") + op.drop_column("miner_predictions", "time_increment") + op.drop_column("miner_predictions", "asset") diff --git a/alembic/versions/1154ae96bd0a_add_table_for_miner_rewards.py b/alembic/versions/1154ae96bd0a_add_table_for_miner_rewards.py index a2c3be3b..4c34fe53 100644 --- a/alembic/versions/1154ae96bd0a_add_table_for_miner_rewards.py +++ b/alembic/versions/1154ae96bd0a_add_table_for_miner_rewards.py @@ -5,6 +5,7 @@ Create Date: 2024-12-08 15:49:07.612838 """ + from typing import Sequence, Union from alembic import op @@ -13,26 +14,36 @@ # revision identifiers, used by Alembic. -revision: str = '1154ae96bd0a' -down_revision: Union[str, None] = '9f5c6d18896d' +revision: str = "1154ae96bd0a" +down_revision: Union[str, None] = "9f5c6d18896d" branch_labels: Union[str, Sequence[str], None] = None depends_on: Union[str, Sequence[str], None] = None def upgrade() -> None: op.create_table( - 'miner_rewards', - sa.Column('id', sa.BigInteger, primary_key=True, nullable=False), - sa.Column('miner_uid', sa.Integer, nullable=False), - sa.Column('validation_time', sa.TIMESTAMP(timezone=True), nullable=False), - sa.Column('start_time', sa.TIMESTAMP(timezone=True), nullable=False), - sa.Column('reward_details', JSONB, nullable=False) + "miner_rewards", + sa.Column("id", sa.BigInteger, primary_key=True, nullable=False), + sa.Column("miner_uid", sa.Integer, nullable=False), + sa.Column( + "validation_time", sa.TIMESTAMP(timezone=True), nullable=False + ), + sa.Column("start_time", sa.TIMESTAMP(timezone=True), nullable=False), + sa.Column("reward_details", JSONB, nullable=False), + ) + op.create_index( + "ix_miner_rewards_validation_time", + "miner_rewards", + ["validation_time"], + ) + op.create_index( + "ix_miner_rewards_miner_uid", "miner_rewards", ["miner_uid"] ) - op.create_index('ix_miner_rewards_validation_time', 'miner_rewards', ['validation_time']) - op.create_index('ix_miner_rewards_miner_uid', 'miner_rewards', ['miner_uid']) def downgrade() -> None: - op.drop_index('ix_miner_rewards_validation_time', table_name='miner_rewards') - op.drop_index('ix_miner_rewards_miner_uid', table_name='miner_rewards') - op.drop_table('miner_rewards') + op.drop_index( + "ix_miner_rewards_validation_time", table_name="miner_rewards" + ) + op.drop_index("ix_miner_rewards_miner_uid", table_name="miner_rewards") + op.drop_table("miner_rewards") diff --git a/alembic/versions/448fada07788_add_columns_reward_and_real_prices_to_.py b/alembic/versions/448fada07788_add_columns_reward_and_real_prices_to_.py index 89f34ac2..fa0bc0d2 100644 --- a/alembic/versions/448fada07788_add_columns_reward_and_real_prices_to_.py +++ b/alembic/versions/448fada07788_add_columns_reward_and_real_prices_to_.py @@ -5,6 +5,7 @@ Create Date: 2024-12-08 16:21:37.857982 """ + from typing import Sequence, Union from alembic import op @@ -13,17 +14,21 @@ # revision identifiers, used by Alembic. -revision: str = '448fada07788' -down_revision: Union[str, None] = '1154ae96bd0a' +revision: str = "448fada07788" +down_revision: Union[str, None] = "1154ae96bd0a" branch_labels: Union[str, Sequence[str], None] = None depends_on: Union[str, Sequence[str], None] = None def upgrade() -> None: - op.add_column('miner_rewards', sa.Column('reward', sa.Float, nullable=True)) - op.add_column('miner_rewards', sa.Column('real_prices', JSON, nullable=True)) + op.add_column( + "miner_rewards", sa.Column("reward", sa.Float, nullable=True) + ) + op.add_column( + "miner_rewards", sa.Column("real_prices", JSON, nullable=True) + ) def downgrade() -> None: - op.drop_column('miner_rewards', 'real_prices') - op.drop_column('miner_rewards', 'reward') + op.drop_column("miner_rewards", "real_prices") + op.drop_column("miner_rewards", "reward") diff --git a/alembic/versions/8b8ee3e62171_add_index_to_miner_predictions_table_.py b/alembic/versions/8b8ee3e62171_add_index_to_miner_predictions_table_.py index c788811d..811113d9 100644 --- a/alembic/versions/8b8ee3e62171_add_index_to_miner_predictions_table_.py +++ b/alembic/versions/8b8ee3e62171_add_index_to_miner_predictions_table_.py @@ -5,6 +5,7 @@ Create Date: 2024-12-08 17:57:30.782720 """ + from typing import Sequence, Union from alembic import op @@ -12,21 +13,27 @@ # revision identifiers, used by Alembic. -revision: str = '8b8ee3e62171' -down_revision: Union[str, None] = 'dba3765d2374' +revision: str = "8b8ee3e62171" +down_revision: Union[str, None] = "dba3765d2374" branch_labels: Union[str, Sequence[str], None] = None depends_on: Union[str, Sequence[str], None] = None def upgrade() -> None: - op.alter_column('miner_predictions', 'start_time', new_column_name='validation_time') - op.drop_index('ix_start_time', table_name='miner_predictions') - op.create_index('ix_validation_time', 'miner_predictions', ['validation_time']) - op.create_index('ix_miner_uid', 'miner_predictions', ['miner_uid']) + op.alter_column( + "miner_predictions", "start_time", new_column_name="validation_time" + ) + op.drop_index("ix_start_time", table_name="miner_predictions") + op.create_index( + "ix_validation_time", "miner_predictions", ["validation_time"] + ) + op.create_index("ix_miner_uid", "miner_predictions", ["miner_uid"]) def downgrade() -> None: - op.drop_index('ix_miner_uid', table_name='miner_predictions') - op.drop_index('ix_validation_time', table_name='miner_predictions') - op.alter_column('miner_predictions', 'validation_time', new_column_name='start_time') - op.create_index('ix_start_time', 'miner_predictions', ['start_time']) + op.drop_index("ix_miner_uid", table_name="miner_predictions") + op.drop_index("ix_validation_time", table_name="miner_predictions") + op.alter_column( + "miner_predictions", "validation_time", new_column_name="start_time" + ) + op.create_index("ix_start_time", "miner_predictions", ["start_time"]) diff --git a/alembic/versions/9f5c6d18896d_add_table_for_miner_predictions.py b/alembic/versions/9f5c6d18896d_add_table_for_miner_predictions.py index 3f010cab..edef404f 100644 --- a/alembic/versions/9f5c6d18896d_add_table_for_miner_predictions.py +++ b/alembic/versions/9f5c6d18896d_add_table_for_miner_predictions.py @@ -5,6 +5,7 @@ Create Date: 2024-12-07 20:10:49.131459 """ + from typing import Sequence, Union from alembic import op @@ -13,7 +14,7 @@ # revision identifiers, used by Alembic. -revision: str = '9f5c6d18896d' +revision: str = "9f5c6d18896d" down_revision: Union[str, None] = None branch_labels: Union[str, Sequence[str], None] = None depends_on: Union[str, Sequence[str], None] = None @@ -21,15 +22,15 @@ def upgrade() -> None: op.create_table( - 'miner_predictions', - sa.Column('id', sa.BigInteger, primary_key=True), - sa.Column('miner_uid', sa.Integer, nullable=False), - sa.Column('start_time', sa.DateTime(timezone=True), nullable=False), - sa.Column('prediction', JSON, nullable=False), + "miner_predictions", + sa.Column("id", sa.BigInteger, primary_key=True), + sa.Column("miner_uid", sa.Integer, nullable=False), + sa.Column("start_time", sa.DateTime(timezone=True), nullable=False), + sa.Column("prediction", JSON, nullable=False), ) - op.create_index('ix_start_time', 'miner_predictions', ['start_time']) + op.create_index("ix_start_time", "miner_predictions", ["start_time"]) def downgrade() -> None: - op.drop_index('ix_start_time', table_name='miner_predictions') - op.drop_table('miner_predictions') + op.drop_index("ix_start_time", table_name="miner_predictions") + op.drop_table("miner_predictions") diff --git a/alembic/versions/bc6d5957a826_remove_start_time_column_and_rename_.py b/alembic/versions/bc6d5957a826_remove_start_time_column_and_rename_.py index bf645fea..119dd3a5 100644 --- a/alembic/versions/bc6d5957a826_remove_start_time_column_and_rename_.py +++ b/alembic/versions/bc6d5957a826_remove_start_time_column_and_rename_.py @@ -5,6 +5,7 @@ Create Date: 2024-12-23 14:45:01.871748 """ + from typing import Sequence, Union from alembic import op @@ -12,19 +13,30 @@ # revision identifiers, used by Alembic. -revision: str = 'bc6d5957a826' -down_revision: Union[str, None] = '8b8ee3e62171' +revision: str = "bc6d5957a826" +down_revision: Union[str, None] = "8b8ee3e62171" branch_labels: Union[str, Sequence[str], None] = None depends_on: Union[str, Sequence[str], None] = None def upgrade() -> None: - op.drop_column('miner_rewards', 'start_time') - op.alter_column('miner_rewards', 'validation_time', new_column_name='start_time') - op.alter_column('miner_predictions', 'validation_time', new_column_name='start_time') + op.drop_column("miner_rewards", "start_time") + op.alter_column( + "miner_rewards", "validation_time", new_column_name="start_time" + ) + op.alter_column( + "miner_predictions", "validation_time", new_column_name="start_time" + ) def downgrade() -> None: - op.alter_column('miner_predictions', 'start_time', new_column_name='validation_time') - op.alter_column('miner_rewards', 'start_time', new_column_name='validation_time') - op.add_column('miner_rewards', sa.Column('start_time', sa.TIMESTAMP(timezone=True), nullable=False)) + op.alter_column( + "miner_predictions", "start_time", new_column_name="validation_time" + ) + op.alter_column( + "miner_rewards", "start_time", new_column_name="validation_time" + ) + op.add_column( + "miner_rewards", + sa.Column("start_time", sa.TIMESTAMP(timezone=True), nullable=False), + ) diff --git a/alembic/versions/dba3765d2374_add_column_prediction_to_miner_.py b/alembic/versions/dba3765d2374_add_column_prediction_to_miner_.py index d81704e1..73a90b2c 100644 --- a/alembic/versions/dba3765d2374_add_column_prediction_to_miner_.py +++ b/alembic/versions/dba3765d2374_add_column_prediction_to_miner_.py @@ -5,6 +5,7 @@ Create Date: 2024-12-08 17:36:07.405120 """ + from typing import Sequence, Union from alembic import op @@ -13,15 +14,17 @@ # revision identifiers, used by Alembic. -revision: str = 'dba3765d2374' -down_revision: Union[str, None] = '448fada07788' +revision: str = "dba3765d2374" +down_revision: Union[str, None] = "448fada07788" branch_labels: Union[str, Sequence[str], None] = None depends_on: Union[str, Sequence[str], None] = None def upgrade() -> None: - op.add_column('miner_rewards', sa.Column('prediction', JSON, nullable=True)) + op.add_column( + "miner_rewards", sa.Column("prediction", JSON, nullable=True) + ) def downgrade() -> None: - op.drop_column('miner_rewards', 'prediction') + op.drop_column("miner_rewards", "prediction") diff --git a/docs/stream_tutorial/miner.py b/docs/stream_tutorial/miner.py index a62814d2..599680c3 100644 --- a/docs/stream_tutorial/miner.py +++ b/docs/stream_tutorial/miner.py @@ -78,13 +78,11 @@ def __init__(self, config=None, axon=None, wallet=None, subtensor=None): self.request_timestamps: Dict = {} @abstractmethod - def config(self) -> "bt.Config": - ... + def config(self) -> "bt.Config": ... @classmethod @abstractmethod - def add_args(cls, parser: argparse.ArgumentParser): - ... + def add_args(cls, parser: argparse.ArgumentParser): ... def _prompt(self, synapse: StreamPrompting) -> StreamPrompting: """ diff --git a/neurons/miner.py b/neurons/miner.py index 84c66aa1..afaf0209 100644 --- a/neurons/miner.py +++ b/neurons/miner.py @@ -41,9 +41,7 @@ def __init__(self, config=None): # TODO(developer): Anything specific to your use case you can do here - async def forward( - self, synapse: Simulation - ) -> Simulation: + async def forward(self, synapse: Simulation) -> Simulation: """ Processes the incoming 'Dummy' synapse by performing a predefined operation on the input data. This method should be replaced with actual logic relevant to the miner's purpose. @@ -70,24 +68,24 @@ async def forward( time_length = simulation_input.time_length num_simulations = simulation_input.num_simulations - if self.config.miner_type == 'dummy': - prediction = generate_fixed_simulation(start_time=dt, time_length=86400) + if self.config.miner_type == "dummy": + prediction = generate_fixed_simulation( + start_time=dt, time_length=86400 + ) else: prediction = generate_simulations( start_time=dt, asset=asset, time_increment=time_increment, time_length=time_length, - num_simulations=num_simulations + num_simulations=num_simulations, ) synapse.simulation_output = prediction return synapse - async def blacklist( - self, synapse: Simulation - ) -> typing.Tuple[bool, str]: + async def blacklist(self, synapse: Simulation) -> typing.Tuple[bool, str]: """ Determines whether an incoming request should be blacklisted and thus ignored. Your implementation should define the logic for blacklisting requests based on your needs and desired security parameters. diff --git a/neurons/validator.py b/neurons/validator.py index 2b0e638d..52c602f8 100644 --- a/neurons/validator.py +++ b/neurons/validator.py @@ -62,7 +62,9 @@ async def forward(self): - Updating the scores """ bt.logging.info("calling forward()") - return await forward(self, self.miner_data_handler, self.price_data_provider) + return await forward( + self, self.miner_data_handler, self.price_data_provider + ) # The main function parses the configuration and runs the validator. diff --git a/simulation/__init__.py b/simulation/__init__.py index 8a33d41f..549458e7 100644 --- a/simulation/__init__.py +++ b/simulation/__init__.py @@ -21,9 +21,9 @@ __version__ = "0.0.1" version_split = __version__.split(".") __spec_version__ = ( - (1000 * int(version_split[0])) - + (10 * int(version_split[1])) - + (1 * int(version_split[2])) + (1000 * int(version_split[0])) + + (10 * int(version_split[1])) + + (1 * int(version_split[2])) ) # Import all submodules. diff --git a/simulation/api/example.py b/simulation/api/example.py index 5b1a458f..7df0a450 100644 --- a/simulation/api/example.py +++ b/simulation/api/example.py @@ -29,7 +29,7 @@ async def test_prediction(): start_time=current_time, time_increment=300, time_length=86400, - num_simulations=1 + num_simulations=1, ) bt.logging.info(f"Sending {str(simulation_input)} to predict a path.") diff --git a/simulation/base/neuron.py b/simulation/base/neuron.py index 6b2026e8..d6965a1b 100644 --- a/simulation/base/neuron.py +++ b/simulation/base/neuron.py @@ -98,12 +98,10 @@ def __init__(self, config=None): self.step = 0 @abstractmethod - async def forward(self, synapse: bt.Synapse) -> bt.Synapse: - ... + async def forward(self, synapse: bt.Synapse) -> bt.Synapse: ... @abstractmethod - def run(self): - ... + def run(self): ... def sync(self): """ diff --git a/simulation/db/models.py b/simulation/db/models.py index c59c95ec..63339a75 100644 --- a/simulation/db/models.py +++ b/simulation/db/models.py @@ -1,14 +1,25 @@ import os from dotenv import load_dotenv -from sqlalchemy import create_engine, MetaData, Table, Column, Integer, DateTime, JSON, Float, String, BigInteger +from sqlalchemy import ( + create_engine, + MetaData, + Table, + Column, + Integer, + DateTime, + JSON, + Float, + String, + BigInteger, +) from sqlalchemy.dialects.postgresql import JSONB # Load environment variables from .env file load_dotenv() # Database connection -DATABASE_URL = os.getenv('DB_URL') +DATABASE_URL = os.getenv("DB_URL") engine = create_engine(DATABASE_URL) metadata = MetaData() diff --git a/simulation/miner.py b/simulation/miner.py index feb6e394..1049873b 100644 --- a/simulation/miner.py +++ b/simulation/miner.py @@ -1,9 +1,22 @@ -from simulation.simulations.price_simulation import simulate_crypto_price_paths, get_asset_price -from simulation.utils.helpers import get_current_time, convert_prices_to_time_format, round_time_to_minutes +from simulation.simulations.price_simulation import ( + simulate_crypto_price_paths, + get_asset_price, +) +from simulation.utils.helpers import ( + get_current_time, + convert_prices_to_time_format, + round_time_to_minutes, +) def generate_simulations( - asset='BTC', start_time=None, time_increment=300, time_length=86400, num_simulations=1, sigma=0.01): + asset="BTC", + start_time=None, + time_increment=300, + time_length=86400, + num_simulations=1, + sigma=0.01, +): """ Generate simulated price paths. @@ -30,15 +43,24 @@ def generate_simulations( time_increment=time_increment, time_length=time_length, num_simulations=num_simulations, - sigma=sigma + sigma=sigma, ) - predictions = convert_prices_to_time_format(simulations.tolist(), start_time, time_increment) + predictions = convert_prices_to_time_format( + simulations.tolist(), start_time, time_increment + ) return predictions -def generate_fixed_simulation(asset='BTC', start_time=None, time_increment=300, time_length=86400, num_simulations=1, sigma=0.01): +def generate_fixed_simulation( + asset="BTC", + start_time=None, + time_increment=300, + time_length=86400, + num_simulations=1, + sigma=0.01, +): """ Generate constant results. Method is used just for test. Don't use in a real simulation. @@ -54,8 +76,12 @@ def generate_fixed_simulation(asset='BTC', start_time=None, time_increment=300, numpy.ndarray: Simulated price paths. """ - simulations = [[1000, 2000, 3000, 4000, 5000, 6000, 7000, 8000, 9000, 10000]] + simulations = [ + [1000, 2000, 3000, 4000, 5000, 6000, 7000, 8000, 9000, 10000] + ] - predictions = convert_prices_to_time_format(simulations, start_time, time_increment) + predictions = convert_prices_to_time_format( + simulations, start_time, time_increment + ) return predictions diff --git a/simulation/protocol.py b/simulation/protocol.py index 548a30d4..283ce48e 100644 --- a/simulation/protocol.py +++ b/simulation/protocol.py @@ -58,7 +58,9 @@ class Simulation(bt.Synapse): simulation_input: SimulationInput # Optional request output, filled by receiving axon. - simulation_output: typing.Optional[typing.List[typing.List[typing.Dict[str, typing.Union[str, float]]]]] = None + simulation_output: typing.Optional[ + typing.List[typing.List[typing.Dict[str, typing.Union[str, float]]]] + ] = None def deserialize(self) -> []: """ diff --git a/simulation/simulation_input.py b/simulation/simulation_input.py index d79fad4c..16a688e1 100644 --- a/simulation/simulation_input.py +++ b/simulation/simulation_input.py @@ -5,10 +5,19 @@ class SimulationInput(BaseModel): asset: str = Field(default="BTC", description="The asset to simulate.") - start_time: str = Field(default=datetime.now().isoformat(), description="The start time of the simulation.") - time_increment: int = Field(default=300, description="Time increment in seconds.") - time_length: int = Field(default=86400, description="Total time length in seconds.") - num_simulations: int = Field(default=1, description="Number of simulation runs.") + start_time: str = Field( + default=datetime.now().isoformat(), + description="The start time of the simulation.", + ) + time_increment: int = Field( + default=300, description="Time increment in seconds." + ) + time_length: int = Field( + default=86400, description="Total time length in seconds." + ) + num_simulations: int = Field( + default=1, description="Number of simulation runs." + ) class Config: arbitrary_types_allowed = True diff --git a/simulation/simulations/price_simulation.py b/simulation/simulations/price_simulation.py index b7aad6f1..c29d9793 100644 --- a/simulation/simulations/price_simulation.py +++ b/simulation/simulations/price_simulation.py @@ -3,7 +3,7 @@ from properscoring import crps_ensemble -def get_asset_price(asset='BTC'): +def get_asset_price(asset="BTC"): """ Retrieves the current price of the specified asset. Currently, supports BTC via Pyth Network. @@ -11,9 +11,11 @@ def get_asset_price(asset='BTC'): Returns: float: Current asset price. """ - if asset == 'BTC': - btc_price_id = "e62df6c8b4a85fe1a67db44dc12de5db330f7ac66b72dc658afedf0f4a415b43" - endpoint = f"https://hermes.pyth.network/api/latest_price_feeds?ids[]={btc_price_id}" # TODO: this endpoint is deprecated + if asset == "BTC": + btc_price_id = ( + "e62df6c8b4a85fe1a67db44dc12de5db330f7ac66b72dc658afedf0f4a415b43" + ) + endpoint = f"https://hermes.pyth.network/api/latest_price_feeds?ids[]={btc_price_id}" # TODO: this endpoint is deprecated try: response = requests.get(endpoint) response.raise_for_status() @@ -21,7 +23,7 @@ def get_asset_price(asset='BTC'): if not data or len(data) == 0: raise ValueError("No price data received") price_feed = data[0] - price = float(price_feed['price']['price']) / (10**8) + price = float(price_feed["price"]["price"]) / (10**8) return price except Exception as e: print(f"Error fetching {asset} price: {str(e)}") @@ -32,7 +34,9 @@ def get_asset_price(asset='BTC'): return None -def simulate_single_price_path(current_price, time_increment, time_length, sigma): +def simulate_single_price_path( + current_price, time_increment, time_length, sigma +): """ Simulate a single crypto asset price path. """ @@ -47,23 +51,31 @@ def simulate_single_price_path(current_price, time_increment, time_length, sigma return price_path -def generate_real_price_path(current_price, time_increment, time_length, sigma): +def generate_real_price_path( + current_price, time_increment, time_length, sigma +): """ Generate a 'real' price path. """ # No random seed set to ensure independent random numbers - real_price_path = simulate_single_price_path(current_price, time_increment, time_length, sigma) + real_price_path = simulate_single_price_path( + current_price, time_increment, time_length, sigma + ) return real_price_path -def simulate_crypto_price_paths(current_price, time_increment, time_length, num_simulations, sigma): +def simulate_crypto_price_paths( + current_price, time_increment, time_length, num_simulations, sigma +): """ Simulate multiple crypto asset price paths. """ price_paths = [] for _ in range(num_simulations): - price_path = simulate_single_price_path(current_price, time_increment, time_length, sigma) + price_path = simulate_single_price_path( + current_price, time_increment, time_length, sigma + ) price_paths.append(price_path) return np.array(price_paths) @@ -94,7 +106,9 @@ def calculate_cumulative_price_changes(price_paths): Calculate the cumulative price changes from the start time to each time increment. """ initial_prices = price_paths[:, [0]] # Shape: (num_paths, 1) - cumulative_changes = price_paths - initial_prices # Broadcasting subtraction + cumulative_changes = ( + price_paths - initial_prices + ) # Broadcasting subtraction return cumulative_changes diff --git a/simulation/utils/config.py b/simulation/utils/config.py index 78a563f1..062395b1 100644 --- a/simulation/utils/config.py +++ b/simulation/utils/config.py @@ -173,7 +173,7 @@ def add_miner_args(cls, parser): "--miner_type", type=str, default="default", - help="Miner type to choose different implementations" + help="Miner type to choose different implementations", ) diff --git a/simulation/utils/helpers.py b/simulation/utils/helpers.py index ab480b30..7a7bf956 100644 --- a/simulation/utils/helpers.py +++ b/simulation/utils/helpers.py @@ -16,17 +16,18 @@ def convert_prices_to_time_format(prices, start_time, time_increment): :param time_increment: Time increment in seconds between consecutive prices. :return: List of dictionaries with 'time' and 'price' keys. """ - start_time = datetime.fromisoformat(start_time) # Convert start_time to a datetime object + start_time = datetime.fromisoformat( + start_time + ) # Convert start_time to a datetime object result = [] for price_item in prices: single_prediction = [] for i, price in enumerate(price_item): time_point = start_time + timedelta(seconds=i * time_increment) - single_prediction.append({ - "time": time_point.isoformat(), - "price": price - }) + single_prediction.append( + {"time": time_point.isoformat(), "price": price} + ) result.append(single_prediction) return result @@ -44,13 +45,17 @@ def get_intersecting_arrays(array1, array2): times_in_array2 = {entry["time"] for entry in array2} # Filter array1 to include only matching times - filtered_array1 = [entry for entry in array1 if entry["time"] in times_in_array2] + filtered_array1 = [ + entry for entry in array1 if entry["time"] in times_in_array2 + ] # Extract times from the first array as a set times_in_array1 = {entry["time"] for entry in array1} # Filter array2 to include only matching times - filtered_array2 = [entry for entry in array2 if entry["time"] in times_in_array1] + filtered_array2 = [ + entry for entry in array2 if entry["time"] in times_in_array1 + ] return filtered_array1, filtered_array2 @@ -63,13 +68,21 @@ def round_time_to_minutes(dt_str, in_seconds, extra_seconds=0): rounding_interval = timedelta(seconds=in_seconds) # Calculate the number of seconds since the start of the day - seconds = (dt - dt.replace(hour=0, minute=0, second=0, microsecond=0)).total_seconds() + seconds = ( + dt - dt.replace(hour=0, minute=0, second=0, microsecond=0) + ).total_seconds() # Calculate the next multiple of time_increment in seconds - next_interval_seconds = ((seconds // rounding_interval.total_seconds()) + 1) * rounding_interval.total_seconds() + next_interval_seconds = ( + (seconds // rounding_interval.total_seconds()) + 1 + ) * rounding_interval.total_seconds() # Get the rounded-up datetime - rounded_time = dt.replace(hour=0, minute=0, second=0, microsecond=0) + timedelta(seconds=next_interval_seconds) + timedelta(seconds=extra_seconds) + rounded_time = ( + dt.replace(hour=0, minute=0, second=0, microsecond=0) + + timedelta(seconds=next_interval_seconds) + + timedelta(seconds=extra_seconds) + ) return rounded_time.isoformat() diff --git a/simulation/validator/crps_calculation.py b/simulation/validator/crps_calculation.py index 3aa07edc..3275ead3 100644 --- a/simulation/validator/crps_calculation.py +++ b/simulation/validator/crps_calculation.py @@ -1,7 +1,9 @@ import numpy as np import pandas as pd from properscoring import crps_ensemble -from simulation.simulations.price_simulation import calculate_price_changes_over_intervals +from simulation.simulations.price_simulation import ( + calculate_price_changes_over_intervals, +) import os @@ -20,10 +22,10 @@ def calculate_crps_for_miner(simulation_runs, real_price_path, time_increment): """ # Define scoring intervals in seconds scoring_intervals = { - '5min': 300, # 5 minutes - '30min': 1800, # 30 minutes - '3hour': 10800, # 3 hours - '24hour': 86400 # 24 hours + "5min": 300, # 5 minutes + "30min": 1800, # 30 minutes + "3hour": 10800, # 3 hours + "24hour": 86400, # 24 hours } # Function to calculate interval steps @@ -40,8 +42,12 @@ def get_interval_steps(scoring_interval, time_increment): interval_steps = get_interval_steps(interval_seconds, time_increment) # Calculate price changes over intervals - simulated_changes = calculate_price_changes_over_intervals(simulation_runs, interval_steps) - real_changes = calculate_price_changes_over_intervals(real_price_path.reshape(1, -1), interval_steps)[0] + simulated_changes = calculate_price_changes_over_intervals( + simulation_runs, interval_steps + ) + real_changes = calculate_price_changes_over_intervals( + real_price_path.reshape(1, -1), interval_steps + )[0] # Calculate CRPS over intervals num_intervals = simulated_changes.shape[1] @@ -52,29 +58,31 @@ def get_interval_steps(scoring_interval, time_increment): crps_values[t] = crps_ensemble(observation, forecasts) # Append detailed data for this increment - detailed_crps_data.append({ - 'Interval': interval_name, - 'Increment': t + 1, - 'CRPS': crps_values[t] - }) + detailed_crps_data.append( + { + "Interval": interval_name, + "Increment": t + 1, + "CRPS": crps_values[t], + } + ) # Total CRPS for this interval total_crps_interval = np.sum(crps_values) sum_all_scores += total_crps_interval # Append total CRPS for this interval to detailed data - detailed_crps_data.append({ - 'Interval': interval_name, - 'Increment': 'Total', - 'CRPS': total_crps_interval - }) + detailed_crps_data.append( + { + "Interval": interval_name, + "Increment": "Total", + "CRPS": total_crps_interval, + } + ) # Append overall total CRPS to detailed data - detailed_crps_data.append({ - 'Interval': 'Overall', - 'Increment': 'Total', - 'CRPS': sum_all_scores - }) + detailed_crps_data.append( + {"Interval": "Overall", "Increment": "Total", "CRPS": sum_all_scores} + ) # Return the sum of all scores return sum_all_scores, detailed_crps_data diff --git a/simulation/validator/forward.py b/simulation/validator/forward.py index f7929961..ec197b46 100644 --- a/simulation/validator/forward.py +++ b/simulation/validator/forward.py @@ -18,7 +18,6 @@ # DEALINGS IN THE SOFTWARE. import time -from datetime import datetime import bittensor as bt @@ -33,9 +32,10 @@ async def forward( - self: BaseValidatorNeuron, - miner_data_handler: MinerDataHandler, - price_data_provider: PriceDataProvider): + self: BaseValidatorNeuron, + miner_data_handler: MinerDataHandler, + price_data_provider: PriceDataProvider, +): """ The forward function is called by the validator every time step. @@ -72,15 +72,13 @@ async def forward( start_time=start_time, time_increment=300, time_length=86400, - num_simulations=100 + num_simulations=100, ) # synapse - is a message that validator sends to miner to get results, i.e. simulation_input in our case # Simulation - is our protocol, i.e. input and output message of a miner (application that returns prediction of # prices for a chosen asset) - synapse = Simulation( - simulation_input=simulation_input - ) + synapse = Simulation(simulation_input=simulation_input) # The dendrite client queries the network: # it is the actual call to all the miners from validator @@ -130,7 +128,9 @@ async def forward( # Update the scores based on the rewards. # You may want to define your own update_scores function for custom behavior. - filtered_rewards, filtered_miner_uids = remove_zero_rewards(rewards, miner_uids) + filtered_rewards, filtered_miner_uids = remove_zero_rewards( + rewards, miner_uids + ) self.update_scores(filtered_rewards, filtered_miner_uids) time.sleep(3600) # wait for an hour diff --git a/simulation/validator/miner_data_handler.py b/simulation/validator/miner_data_handler.py index 0c1d265b..dc89eeae 100644 --- a/simulation/validator/miner_data_handler.py +++ b/simulation/validator/miner_data_handler.py @@ -19,13 +19,15 @@ def set_values(miner_uid, values, simulation_input: SimulationInput): "time_increment": simulation_input.time_increment, "time_length": simulation_input.time_length, "num_simulations": simulation_input.num_simulations, - "prediction": values + "prediction": values, } try: with engine.connect() as connection: with connection.begin(): # Begin a transaction - insert_stmt = miner_predictions.insert().values(row_to_insert) + insert_stmt = miner_predictions.insert().values( + row_to_insert + ) connection.execute(insert_stmt) except Exception as e: bt.logging.info(f"in set_values (got an exception): {e}") @@ -39,11 +41,11 @@ def set_reward_details(reward_details: [], start_time: str): "reward_details": { "score": row["score"], "softmax_score": row["softmax_score"], - "crps_data": row["crps_data"] + "crps_data": row["crps_data"], }, "reward": row["softmax_score"], "real_prices": row["real_prices"], - "prediction": row["predictions"] + "prediction": row["predictions"], } for row in reward_details ] @@ -54,7 +56,9 @@ def set_reward_details(reward_details: [], start_time: str): connection.execute(insert_stmt) except Exception as e: connection.rollback() - bt.logging.info(f"in set_reward_details (got an exception): {e}") + bt.logging.info( + f"in set_reward_details (got an exception): {e}" + ) @staticmethod def get_values(miner_uid: int, validation_time: str): @@ -66,21 +70,37 @@ def get_values(miner_uid: int, validation_time: str): query = ( select(miner_predictions.c.prediction) .where( - (miner_predictions.c.start_time + text("INTERVAL '1 second'") * miner_predictions.c.time_length) < validation_time, - miner_predictions.c.miner_uid == miner_uid + ( + miner_predictions.c.start_time + + text("INTERVAL '1 second'") + * miner_predictions.c.time_length + ) + < validation_time, + miner_predictions.c.miner_uid == miner_uid, + ) + .order_by( + ( + miner_predictions.c.start_time + + text("INTERVAL '1 second'") + * miner_predictions.c.time_length + ).desc() ) - .order_by((miner_predictions.c.start_time + text("INTERVAL '1 second'") * miner_predictions.c.time_length).desc()) .limit(1) ) result = connection.execute(query).fetchone() - bt.logging.info("in get_values, predictions fetched for miner_uid: " + str(miner_uid)) + bt.logging.info( + "in get_values, predictions fetched for miner_uid: " + + str(miner_uid) + ) if result is None: return [] - bt.logging.info("in get_values, predictions length:" + str(len(result[0]))) + bt.logging.info( + "in get_values, predictions length:" + str(len(result[0])) + ) # fetchone return a tuple, so we need to return the first element, which is the prediction return result[0] diff --git a/simulation/validator/price_data_provider.py b/simulation/validator/price_data_provider.py index b838260c..2be4031c 100644 --- a/simulation/validator/price_data_provider.py +++ b/simulation/validator/price_data_provider.py @@ -7,10 +7,7 @@ class PriceDataProvider: BASE_URL = "https://benchmarks.pyth.network/v1/shims/tradingview/history" - TOKEN_MAP = { - "BTC": "Crypto.BTC/USD", - "ETH": "Crypto.ETH/USD" - } + TOKEN_MAP = {"BTC": "Crypto.BTC/USD", "ETH": "Crypto.ETH/USD"} one_day_seconds = 24 * 60 * 60 @@ -32,7 +29,7 @@ def fetch_data(self, time_point: str): "symbol": self.token, "resolution": 1, "from": start_time, - "to": end_time + "to": end_time, } response = requests.get(self.BASE_URL, params=params) @@ -53,8 +50,10 @@ def _transform_data(data): transformed_data = [ { - "time": datetime.fromtimestamp(timestamps[i], timezone.utc).isoformat(), - "price": float(close_prices[i]) + "time": datetime.fromtimestamp( + timestamps[i], timezone.utc + ).isoformat(), + "price": float(close_prices[i]), } for i in range(len(timestamps) - 1, -1, -5) ][::-1] diff --git a/simulation/validator/reward.py b/simulation/validator/reward.py index a4e0d487..eb47b9e0 100644 --- a/simulation/validator/reward.py +++ b/simulation/validator/reward.py @@ -30,11 +30,11 @@ def reward( - miner_data_handler: MinerDataHandler, - price_data_provider: PriceDataProvider, - miner_uid: int, - simulation_input: SimulationInput, - validation_time: str, + miner_data_handler: MinerDataHandler, + price_data_provider: PriceDataProvider, + miner_uid: int, + simulation_input: SimulationInput, + validation_time: str, ): """ Reward the miner response to the simulation_input request. This method returns a reward @@ -60,27 +60,32 @@ def reward( intersecting_predictions = [] intersecting_real_price = real_prices for prediction in predictions: - intersecting_prediction, intersecting_real_price = get_intersecting_arrays(prediction, intersecting_real_price) + intersecting_prediction, intersecting_real_price = ( + get_intersecting_arrays(prediction, intersecting_real_price) + ) intersecting_predictions.append(intersecting_prediction) - predictions_path = [[entry["price"] for entry in sublist] for sublist in intersecting_predictions] + predictions_path = [ + [entry["price"] for entry in sublist] + for sublist in intersecting_predictions + ] real_price_path = [entry["price"] for entry in intersecting_real_price] score, detailed_crps_data = calculate_crps_for_miner( np.array(predictions_path), np.array(real_price_path), - simulation_input.time_increment + simulation_input.time_increment, ) return score, detailed_crps_data, real_prices, predictions def get_rewards( - miner_data_handler: MinerDataHandler, - price_data_provider: PriceDataProvider, - simulation_input: SimulationInput, - miner_uids: List[int], - validation_time: str, + miner_data_handler: MinerDataHandler, + price_data_provider: PriceDataProvider, + simulation_input: SimulationInput, + miner_uids: List[int], + validation_time: str, ) -> (np.ndarray, []): """ Returns an array of rewards for the given query and responses. @@ -102,12 +107,12 @@ def get_rewards( for i, miner_id in enumerate(miner_uids): # function that calculates a score for an individual miner score, detailed_crps_data, real_prices, predictions = reward( - miner_data_handler, - price_data_provider, - miner_id, - simulation_input, - validation_time - ) + miner_data_handler, + price_data_provider, + miner_id, + simulation_input, + validation_time, + ) scores.append(score) detailed_crps_data_list.append(detailed_crps_data) real_prices_list.append(real_prices) @@ -127,8 +132,14 @@ def get_rewards( "real_prices": real_prices, "predictions": predictions, } - for miner_uid, score, crps_data, softmax_score, real_prices, predictions in - zip(miner_uids, scores, detailed_crps_data_list, softmax_scores, real_prices_list, predictions_list) + for miner_uid, score, crps_data, softmax_score, real_prices, predictions in zip( + miner_uids, + scores, + detailed_crps_data_list, + softmax_scores, + real_prices_list, + predictions_list, + ) ] return softmax_scores, detailed_info @@ -154,7 +165,10 @@ def compute_softmax(score_values: np.ndarray) -> np.ndarray: def clean_numpy_in_crps_data(crps_data: []) -> []: cleaned_crps_data = [ - {key: (float(value) if isinstance(value, np.float64) else value) for key, value in item.items()} + { + key: (float(value) if isinstance(value, np.float64) else value) + for key, value in item.items() + } for item in crps_data ] return cleaned_crps_data diff --git a/tests/test_calculate_crps.py b/tests/test_calculate_crps.py index 2a1f5d9b..2d9efdca 100644 --- a/tests/test_calculate_crps.py +++ b/tests/test_calculate_crps.py @@ -20,7 +20,7 @@ def test_calculate_crps_for_miner_1(self): sum_all_scores, _ = calculate_crps_for_miner( np.array([predictions_path]), np.array(real_price_path), - time_increment + time_increment, ) self.assertEqual(sum_all_scores, 1100) @@ -33,7 +33,7 @@ def test_calculate_crps_for_miner_2(self): sum_all_scores, _ = calculate_crps_for_miner( np.array([predictions_path]), np.array(real_price_path), - time_increment + time_increment, ) self.assertEqual(sum_all_scores, 3500) @@ -46,7 +46,7 @@ def test_calculate_crps_for_miner_3(self): sum_all_scores, _ = calculate_crps_for_miner( np.array([predictions_path]), np.array(real_price_path), - time_increment + time_increment, ) self.assertEqual(sum_all_scores, 1100) diff --git a/tests/test_generate_simulation.py b/tests/test_generate_simulation.py index 645868f8..f61ba40e 100644 --- a/tests/test_generate_simulation.py +++ b/tests/test_generate_simulation.py @@ -12,6 +12,8 @@ def tearDown(self): pass def test_generate_simulation(self): - prediction_result = generate_simulations(start_time=datetime.now().isoformat()) + prediction_result = generate_simulations( + start_time=datetime.now().isoformat() + ) print(prediction_result) diff --git a/tests/test_helpers.py b/tests/test_helpers.py index 02f99a25..bd80d831 100644 --- a/tests/test_helpers.py +++ b/tests/test_helpers.py @@ -1,7 +1,12 @@ import unittest -from simulation.utils.helpers import convert_prices_to_time_format, get_intersecting_arrays, round_time_to_minutes, \ - from_iso_to_unix_time, get_current_time +from simulation.utils.helpers import ( + convert_prices_to_time_format, + get_intersecting_arrays, + round_time_to_minutes, + from_iso_to_unix_time, + get_current_time, +) class TestHelpers(unittest.TestCase): @@ -20,15 +25,21 @@ def test_convert_prices_to_time_format(self): start_time = "2024-11-20T00:00:00" time_increment = 300 # 5 minutes in seconds - formatted_data = convert_prices_to_time_format(prices, start_time, time_increment) - - self.assertEqual(formatted_data, [ - [ - {'time': '2024-11-20T00:00:00', 'price': 45.67}, - {'time': '2024-11-20T00:05:00', 'price': 56.78}, - {'time': '2024-11-20T00:10:00', 'price': 34.89}, - {'time': '2024-11-20T00:15:00', 'price': 62.15}] - ]) + formatted_data = convert_prices_to_time_format( + prices, start_time, time_increment + ) + + self.assertEqual( + formatted_data, + [ + [ + {"time": "2024-11-20T00:00:00", "price": 45.67}, + {"time": "2024-11-20T00:05:00", "price": 56.78}, + {"time": "2024-11-20T00:10:00", "price": 34.89}, + {"time": "2024-11-20T00:15:00", "price": 62.15}, + ] + ], + ) def test_get_intersecting_arrays(self): array1 = [ @@ -43,17 +54,25 @@ def test_get_intersecting_arrays(self): {"time": "2024-11-20T00:15:00", "price": 75.20}, ] - intersecting_array1, intersecting_array2 = get_intersecting_arrays(array1, array2) - - self.assertEqual(intersecting_array1, [ - {"time": "2024-11-20T00:05:00", "price": 56.78}, - {"time": "2024-11-20T00:10:00", "price": 34.89} - ]) - - self.assertEqual(intersecting_array2, [ - {"time": "2024-11-20T00:05:00", "price": 56.78}, - {"time": "2024-11-20T00:10:00", "price": 62.15} - ]) + intersecting_array1, intersecting_array2 = get_intersecting_arrays( + array1, array2 + ) + + self.assertEqual( + intersecting_array1, + [ + {"time": "2024-11-20T00:05:00", "price": 56.78}, + {"time": "2024-11-20T00:10:00", "price": 34.89}, + ], + ) + + self.assertEqual( + intersecting_array2, + [ + {"time": "2024-11-20T00:05:00", "price": 56.78}, + {"time": "2024-11-20T00:10:00", "price": 62.15}, + ], + ) def test_round_time_to_minutes(self): time_increment = 300 @@ -74,8 +93,12 @@ def test_round_time_to_two_minutes(self): dt_str_1 = "2024-11-25T19:01:59.940515" dt_str_2 = "2024-11-25T19:03:59.940515" - result_1 = round_time_to_minutes(dt_str_1, time_increment, extra_seconds) - result_2 = round_time_to_minutes(dt_str_2, time_increment, extra_seconds) + result_1 = round_time_to_minutes( + dt_str_1, time_increment, extra_seconds + ) + result_2 = round_time_to_minutes( + dt_str_2, time_increment, extra_seconds + ) self.assertEqual(result_1, "2024-11-25T19:03:00") self.assertEqual(result_2, "2024-11-25T19:05:00") diff --git a/tests/test_miner_data_handler.py b/tests/test_miner_data_handler.py index d9ba5c39..93b378d2 100644 --- a/tests/test_miner_data_handler.py +++ b/tests/test_miner_data_handler.py @@ -28,7 +28,7 @@ def test_get_values_within_range(self): start_time=start_time, time_increment=300, time_length=86400, - num_simulations=100 + num_simulations=100, ) values = generate_values(datetime.fromisoformat(start_time)) @@ -38,8 +38,12 @@ def test_get_values_within_range(self): self.assertEqual(1, len(result)) self.assertEqual(288, len(result[0])) # Half of the 288 intervals - self.assertEqual({"time": "2024-11-20T12:00:00", "price": 90000}, result[0][0]) - self.assertEqual({"time": "2024-11-21T11:55:00", "price": 233500}, result[0][287]) + self.assertEqual( + {"time": "2024-11-20T12:00:00", "price": 90000}, result[0][0] + ) + self.assertEqual( + {"time": "2024-11-21T11:55:00", "price": 233500}, result[0][287] + ) def test_get_values_exceeding_range(self): """ @@ -58,7 +62,7 @@ def test_get_values_exceeding_range(self): start_time=start_time, time_increment=300, time_length=86400, - num_simulations=100 + num_simulations=100, ) values = generate_values(datetime.fromisoformat(start_time)) @@ -85,7 +89,7 @@ def test_get_values_ongoing_range(self): start_time=start_time, time_increment=300, time_length=86400, - num_simulations=100 + num_simulations=100, ) values = generate_values(datetime.fromisoformat(start_time)) @@ -118,14 +122,14 @@ def test_multiple_records_for_same_miner(self): start_time=start_time_1, time_increment=300, time_length=86400, - num_simulations=100 + num_simulations=100, ) simulation_input2 = SimulationInput( asset="BTC", start_time=start_time_2, time_increment=300, time_length=86400, - num_simulations=100 + num_simulations=100, ) values = generate_values(datetime.fromisoformat(start_time_1)) @@ -138,8 +142,12 @@ def test_multiple_records_for_same_miner(self): self.assertEqual(1, len(result)) self.assertEqual(288, len(result[0])) # Half of the 288 intervals - self.assertEqual({"time": "2024-11-20T12:00:00", "price": 90000}, result[0][0]) - self.assertEqual({"time": "2024-11-21T11:55:00", "price": 233500}, result[0][287]) + self.assertEqual( + {"time": "2024-11-20T12:00:00", "price": 90000}, result[0][0] + ) + self.assertEqual( + {"time": "2024-11-21T11:55:00", "price": 233500}, result[0][287] + ) def test_multiple_records_for_same_miner_with_overlapping(self): """ @@ -164,7 +172,7 @@ def test_multiple_records_for_same_miner_with_overlapping(self): start_time=start_time_1, time_increment=300, time_length=86400, - num_simulations=100 + num_simulations=100, ) simulation_input2 = SimulationInput( @@ -172,7 +180,7 @@ def test_multiple_records_for_same_miner_with_overlapping(self): start_time=start_time_2, time_increment=300, time_length=86400, - num_simulations=100 + num_simulations=100, ) values = generate_values(datetime.fromisoformat(start_time_1)) @@ -185,8 +193,12 @@ def test_multiple_records_for_same_miner_with_overlapping(self): self.assertEqual(1, len(result)) self.assertEqual(288, len(result[0])) # Half of the 288 intervals - self.assertEqual({"time": "2024-11-20T00:00:00", "price": 90000}, result[0][0]) - self.assertEqual({"time": "2024-11-20T23:55:00", "price": 233500}, result[0][287]) + self.assertEqual( + {"time": "2024-11-20T00:00:00", "price": 90000}, result[0][0] + ) + self.assertEqual( + {"time": "2024-11-20T23:55:00", "price": 233500}, result[0][287] + ) def test_no_data_for_miner(self): """Test retrieving values for a miner that doesn't exist.""" diff --git a/tests/test_price_simulation.py b/tests/test_price_simulation.py index 5b0e7a5b..fb3ab1ec 100644 --- a/tests/test_price_simulation.py +++ b/tests/test_price_simulation.py @@ -22,11 +22,18 @@ def test_simulate_crypto_price_paths(self): sigma_real = 0.01 price_paths = simulate_crypto_price_paths( - current_price, time_increment, time_length, num_simulations, sigma_real + current_price, + time_increment, + time_length, + num_simulations, + sigma_real, ) # Test the shape of the result - self.assertEqual(price_paths.shape, (num_simulations, time_length / time_increment + 1)) + self.assertEqual( + price_paths.shape, + (num_simulations, time_length / time_increment + 1), + ) # Test that all values are finite (no NaN or Inf values) self.assertTrue(np.all(np.isfinite(price_paths))) diff --git a/tests/test_rewards.py b/tests/test_rewards.py index 55fd5706..248ef4e5 100644 --- a/tests/test_rewards.py +++ b/tests/test_rewards.py @@ -39,7 +39,9 @@ def test_remove_zero_rewards(self): rewards = np.array([0.0, 5.0, 0.0, 10.0]) miner_uids = [0, 1, 2, 3] - filtered_rewards, filtered_miner_uids = remove_zero_rewards(rewards, miner_uids) + filtered_rewards, filtered_miner_uids = remove_zero_rewards( + rewards, miner_uids + ) assert_equal(filtered_rewards, np.array([5.0, 10.0])) self.assertEqual(len(filtered_miner_uids), 2) @@ -55,7 +57,7 @@ def test_get_rewards(self): start_time=start_time, time_increment=300, time_length=86400, - num_simulations=100 + num_simulations=100, ) values = generate_values(datetime.fromisoformat(start_time)) @@ -69,10 +71,10 @@ def test_get_rewards(self): start_time=current_time, time_increment=60, # default: 5 mins time_length=3600, # default: 1 day - num_simulations=1 # default: 100 + num_simulations=1, # default: 100 ), [miner_id], # TODO: add another test with more miners - current_time + current_time, ) print(softmax_scores) # TODO: assert the scores @@ -93,7 +95,7 @@ def mock_get_values(miner_uid, mock_validation_time): {"time": "2024-11-25T20:30:00", "price": 92000}, {"time": "2024-11-25T20:35:00", "price": 92500}, {"time": "2024-11-25T20:40:00", "price": 92600}, - {"time": "2024-11-25T20:45:00", "price": 92500} + {"time": "2024-11-25T20:45:00", "price": 92500}, ], [ {"time": "2024-11-25T20:20:00", "price": 90500}, @@ -101,7 +103,7 @@ def mock_get_values(miner_uid, mock_validation_time): {"time": "2024-11-25T20:30:00", "price": 92500}, {"time": "2024-11-25T20:35:00", "price": 93500}, {"time": "2024-11-25T20:40:00", "price": 92900}, - {"time": "2024-11-25T20:45:00", "price": 92100} + {"time": "2024-11-25T20:45:00", "price": 92100}, ], [ {"time": "2024-11-25T20:20:00", "price": 91500}, @@ -109,8 +111,8 @@ def mock_get_values(miner_uid, mock_validation_time): {"time": "2024-11-25T20:30:00", "price": 94500}, {"time": "2024-11-25T20:35:00", "price": 90500}, {"time": "2024-11-25T20:40:00", "price": 90900}, - {"time": "2024-11-25T20:45:00", "price": 90100} - ] + {"time": "2024-11-25T20:45:00", "price": 90100}, + ], ] elif miner_uid == 2: return [ @@ -120,7 +122,7 @@ def mock_get_values(miner_uid, mock_validation_time): {"time": "2024-11-25T20:30:00", "price": 102000}, {"time": "2024-11-25T20:35:00", "price": 102500}, {"time": "2024-11-25T20:40:00", "price": 102600}, - {"time": "2024-11-25T20:45:00", "price": 102500} + {"time": "2024-11-25T20:45:00", "price": 102500}, ], [ {"time": "2024-11-25T20:20:00", "price": 100500}, @@ -128,7 +130,7 @@ def mock_get_values(miner_uid, mock_validation_time): {"time": "2024-11-25T20:30:00", "price": 102500}, {"time": "2024-11-25T20:35:00", "price": 103500}, {"time": "2024-11-25T20:40:00", "price": 102900}, - {"time": "2024-11-25T20:45:00", "price": 102100} + {"time": "2024-11-25T20:45:00", "price": 102100}, ], [ {"time": "2024-11-25T20:20:00", "price": 101500}, @@ -136,8 +138,8 @@ def mock_get_values(miner_uid, mock_validation_time): {"time": "2024-11-25T20:30:00", "price": 104500}, {"time": "2024-11-25T20:35:00", "price": 100500}, {"time": "2024-11-25T20:40:00", "price": 100900}, - {"time": "2024-11-25T20:45:00", "price": 100100} - ] + {"time": "2024-11-25T20:45:00", "price": 100100}, + ], ] elif miner_uid == 3: return [ @@ -147,7 +149,7 @@ def mock_get_values(miner_uid, mock_validation_time): {"time": "2024-11-25T20:30:00", "price": 52000}, {"time": "2024-11-25T20:35:00", "price": 52500}, {"time": "2024-11-25T20:40:00", "price": 52600}, - {"time": "2024-11-25T20:45:00", "price": 52500} + {"time": "2024-11-25T20:45:00", "price": 52500}, ], [ {"time": "2024-11-25T20:20:00", "price": 60000}, @@ -155,7 +157,7 @@ def mock_get_values(miner_uid, mock_validation_time): {"time": "2024-11-25T20:30:00", "price": 62000}, {"time": "2024-11-25T20:35:00", "price": 62500}, {"time": "2024-11-25T20:40:00", "price": 62600}, - {"time": "2024-11-25T20:45:00", "price": 62500} + {"time": "2024-11-25T20:45:00", "price": 62500}, ], [ {"time": "2024-11-25T20:20:00", "price": 70000}, @@ -163,8 +165,8 @@ def mock_get_values(miner_uid, mock_validation_time): {"time": "2024-11-25T20:30:00", "price": 72000}, {"time": "2024-11-25T20:35:00", "price": 72500}, {"time": "2024-11-25T20:40:00", "price": 72600}, - {"time": "2024-11-25T20:45:00", "price": 72500} - ] + {"time": "2024-11-25T20:45:00", "price": 72500}, + ], ] mock_miner_data_handler.get_values.side_effect = mock_get_values @@ -176,7 +178,7 @@ def mock_get_values(miner_uid, mock_validation_time): {"time": "2024-11-25T20:15:00", "price": 92500}, {"time": "2024-11-25T20:20:00", "price": 92600}, {"time": "2024-11-25T20:25:00", "price": 92500}, - {"time": "2024-11-25T20:30:00", "price": 93500} + {"time": "2024-11-25T20:30:00", "price": 93500}, ] simulation_input = SimulationInput( @@ -184,7 +186,7 @@ def mock_get_values(miner_uid, mock_validation_time): start_time=validation_time, time_increment=300, # 5 mins time_length=86400, # 1 day - num_simulations=3 + num_simulations=3, ) result = get_rewards( @@ -212,7 +214,7 @@ def mock_get_values(miner_uid, mock_validation_time): {"time": "2024-11-25T20:30:00", "price": 92000}, {"time": "2024-11-25T20:35:00", "price": 92500}, {"time": "2024-11-25T20:40:00", "price": 92600}, - {"time": "2024-11-25T20:45:00", "price": 92500} + {"time": "2024-11-25T20:45:00", "price": 92500}, ] elif miner_uid == 2: return [] @@ -223,7 +225,7 @@ def mock_get_values(miner_uid, mock_validation_time): {"time": "2024-11-25T20:30:00", "price": 52000}, {"time": "2024-11-25T20:35:00", "price": 52500}, {"time": "2024-11-25T20:40:00", "price": 52600}, - {"time": "2024-11-25T20:45:00", "price": 52500} + {"time": "2024-11-25T20:45:00", "price": 52500}, ] mock_miner_data_handler.get_values.side_effect = mock_get_values @@ -235,7 +237,7 @@ def mock_get_values(miner_uid, mock_validation_time): {"time": "2024-11-25T20:15:00", "price": 92500}, {"time": "2024-11-25T20:20:00", "price": 92600}, {"time": "2024-11-25T20:25:00", "price": 92500}, - {"time": "2024-11-25T20:30:00", "price": 93500} + {"time": "2024-11-25T20:30:00", "price": 93500}, ] simulation_input = SimulationInput( @@ -243,7 +245,7 @@ def mock_get_values(miner_uid, mock_validation_time): start_time=validation_time, time_increment=300, # default: 5 mins time_length=86400, # default: 1 day - num_simulations=1 # default: 100 + num_simulations=1, # default: 100 ) result = get_rewards(