Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Enable multi-feed requests #2

Merged
merged 8 commits into from
Aug 28, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
25 changes: 25 additions & 0 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
name: pyreleaser
on:
push:
tags:
- '*'
jobs:
test:
runs-on: ubuntu-latest
steps:
- name: "checkout"
uses: actions/checkout@v4
- name: "fetch unshallow"
run: git fetch --prune --unshallow
- name: "list"
run: pwd && ls -la
- name: "deps"
run: python -m pip install -r requirements/local.txt
- name: "package"
run: make package-source
- name: "list"
run: ls -la ./dist/
- name: "release"
uses: ncipollo/[email protected]
with:
artifacts: "dist/*"
8 changes: 4 additions & 4 deletions .github/workflows/unit-tests-all.yml
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,8 @@ jobs:
continue-on-error: ${{ matrix.experimental }}
strategy:
matrix:
os: [ubuntu-latest, windows-latest, macos-latest]
python-version: ["3.9", "3.10"]
os: [ubuntu-latest, windows-latest]
python-version: ["3.9", "3.10", "3.11"]
experimental: [false]
# Include experimental or bleeding-edge releases.
# Windows is not included as it can be unreliable, e.g.
Expand All @@ -28,10 +28,10 @@ jobs:
# Example formatting: 3.11.0-alpha.1, 3.9.0-beta.8, 3.10.0-rc.3
#
- os: ubuntu-latest
python-version: "3.11.0"
python-version: "3.12.0"
experimental: true
- os: macos-latest
python-version: "3.11.0"
python-version: "3.12.0"
experimental: true
steps:
- name: "check out repository"
Expand Down
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -132,3 +132,6 @@ dmypy.json

# Pyre type checker
.pyre/

# Custom
*.bk
2 changes: 2 additions & 0 deletions .pylintrc
Original file line number Diff line number Diff line change
Expand Up @@ -33,3 +33,5 @@ ignore-paths=
disable =
C0301, # line-length too long, see Black documented recommendations.
C0115, # No docstring for Class.
# Pylint incorrectly picking up the below.
R0401, # Cyclic import.
3 changes: 3 additions & 0 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -30,5 +30,8 @@ clean: ## Clean the package
rm -rf dist/
rm -rf tar-src/

upgrade: ## Upgrade project dependencies.
pip-upgrade

help: ## Print this help message
@grep -E '^[a-zA-Z_-]+:.*?## .*$$' $(MAKEFILE_LIST) | sort | awk 'BEGIN {FS = ":.*?## "}; {printf "\033[36m%-30s\033[0m %s\n", $$1, $$2}'
69 changes: 69 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,3 +2,72 @@

Monitor prices for a given feed identifier and potentially trigger an action,
e.g. price update on-chain.

## Environment

The script needs a single environment variable set so that it can connect to
the validator node.

```env
export ORCFAX_VALIDATOR=ws://<ip>:<port>/ws/
```

The price monitor will connect to the `/price_monitor/' websocket of the
validator.

## Connecting

The price monitor will need to connec to `ssl` in production. If the monitor
is being used locally, a `--local` flag can be used.

Other command line arguments can be viewed using `--help`.

## Running

The script can be run from the repository, e.g.:

```sh
python price_monitor.py --help
```

or once installed via the package with:

```sh
price-monitor --help
```

## Polling

Polling is hard-coded at 60 seconds. At time of writing the validator is only
receiving updated prices from sources every 60 seconds.

## Persitence

The price-monitor script uses Python tenacity for persisttence. This could be
replaced with a service if desired. Tenacity provides us with a very robust
reconnection approach in any regard.

## Output

Logging will be visible to the user as follows:

<!-- markdownlint-disable -->

```log
2024-07-30 17:13:23 INFO :: price_monitor.py:113:connect_to_websocket() :: {"feed_ids": ["ADA-USD", "ADA-IUSD", "ADA-USDM", "ADA-DJED", "SHEN-ADA", "MIN-ADA", "FACT-ADA", "LQ-ADA", "SNEK-ADA", "LENFI-ADA", "HUNT-ADA", "IBTC-ADA", "IETH-ADA"]}
2024-07-30 17:13:23 INFO :: price_monitor.py:190:price_monitor() :: 'ADA-USD' deviation calculated as: '0.0673535061241779' from [0.4006, 0.40087]
2024-07-30 17:13:23 INFO :: price_monitor.py:190:price_monitor() :: 'ADA-USDM' deviation calculated as: '0.22193163625698276' from [0.408227, 0.409135]
2024-07-30 17:13:23 INFO :: price_monitor.py:190:price_monitor() :: 'ADA-DJED' deviation calculated as: '0.4586888495850445' from [0.411608, 0.40972]
2024-07-30 17:13:23 INFO :: price_monitor.py:190:price_monitor() :: 'LQ-ADA' deviation calculated as: '0.14010614670573318' from [2.131955, 2.128968]
2024-07-30 17:13:23 INFO :: price_monitor.py:190:price_monitor() :: 'HUNT-ADA' deviation calculated as: '0.002714113389629347' from [0.331591, 0.3316]
2024-07-30 17:13:23 INFO :: price_monitor.py:206:price_monitor() :: not requesting any updated pairs... polling in '60' seconds
```

<!-- markdownlint-enable -->

All configured feeds will be requested from the validator and those with
upgraded prices are returned.

If the deviation is worked out to be greater than or equal to a given
threshold a new price will be requested from the validator via the
`validate_on_demand/` endpoint of the validator.
36 changes: 8 additions & 28 deletions pyproject.toml
Original file line number Diff line number Diff line change
@@ -1,51 +1,31 @@
[project]
# For more information on package metadata see:
#
# * https://packaging.python.org/specifications/core-metadata/
#
# For even greater information on this file, see the pypa/sampleproject:
#
# * https://github.com/pypa/sampleproject/blob/fc8d83efd5749853507be959d858629d0aaf5c6e/pyproject.toml
#

# Required.

name = "price-monitor"
version = "0.0.1"

# Optional.
dynamic = ["version", "dependencies"]

description = "Enable monitoring of Orcfax price-feeds"

# Optional.
readme = "README.md"

# Supported python versions. Optional, but helpful.
requires-python = ">=3.9"

# Optional.
authors = [
{name = "R. Spencer", email = "[email protected]" },
]

# Optional.
#
# Packages installed by pip when the project is installed.
dependencies = [
"certifi==2023.7.22",
"tenacity==8.2.3",
"websockets==11.0.3",
[tool.setuptools.dynamic]
dependencies = {file = ["requirements/requirements.txt"]}

]

# Optional.
#
[project.urls]
"Homepage" = "https://orcfax.io"
"Source" = "https://github.com/orcfax/price-monitor"

# Command-line executables to create.
[project.scripts]
price-monitor = "price_monitor.price_monitor:main"

[build-system]
requires = ["setuptools>=67.8.0", "wheel"]
requires = ["setuptools>=67.8.0", "wheel", "setuptools_scm[toml]>=7.1.0"]
build-backend = "setuptools.build_meta"

[tool.setuptools_scm]
9 changes: 5 additions & 4 deletions requirements/local.txt
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
# requirements to run locally and for development.
-r requirements.txt

pre-commit==3.3.2
pylint==2.17.4
pytest==7.3.1
tox==4.5.2
pip-upgrader==1.4.15
pre-commit==3.8.0
pylint==3.2.6
pytest==8.3.2
tox==4.16.0
8 changes: 5 additions & 3 deletions requirements/requirements.txt
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
# requirements for the production project.
certifi==2023.7.22
tenacity==8.2.3
websockets==11.0.3

certifi==2024.7.4
pydantic==2.8.2
tenacity==9.0.0
websockets==12.0
45 changes: 45 additions & 0 deletions src/price_monitor/feed_helper.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
"""Helpers for processing feed specification data."""

# pylint: disable=E0611,R0902

import json
import logging

from pydantic.dataclasses import dataclass
from pydantic.tools import parse_obj_as

logger = logging.getLogger(__name__)


@dataclass
class FeedSpec:
pair: str
label: str
interval: int
deviation: int
source: str
calculation: str
status: str
type: str = "CER"


async def read_feed_data(feed_data: str) -> list[FeedSpec]:
""" "Read feed data into memory for use in the script."""
feed_dict = None
with open(feed_data, "r", encoding="utf-8") as json_feeds:
feed_dict = json.loads(json_feeds.read())
logger.info("cer-feeds version: %s", feed_dict["meta"]["version"])
logger.info("number of feeds: %s", len(feed_dict["feeds"]))
feeds = []
for item in feed_dict["feeds"]:
feed = parse_obj_as(FeedSpec, item)
feeds.append(feed)
return feeds


def get_deviation(feed_id: str, feeds: list[FeedSpec]):
"""Retrieve deviation for a given price pair."""
for feed in feeds:
if feed.pair != feed_id:
continue
return feed.deviation
Loading
Loading