Skip to content

Commit

Permalink
Merge pull request #7 from rhettre/feature/aggregate-functions
Browse files Browse the repository at this point in the history
caching and new methods
  • Loading branch information
rhettre authored Aug 25, 2024
2 parents fae1528 + 8473f26 commit d14c114
Show file tree
Hide file tree
Showing 4 changed files with 187 additions and 4 deletions.
33 changes: 33 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,10 @@ This is an unofficial Python client for the AlphaSquared API. It allows users to
- Implements rate limiting to comply with API usage rules
- Provides methods to retrieve asset information, strategy values, and hypothetical data
- Includes error handling and logging functionality
- Fetch comprehensive asset data (price, risk, market cap, etc.)
- Get custom strategy values
- Built-in caching to reduce API calls
- Automatic rate limiting to comply with API rules

## Installation

Expand Down Expand Up @@ -51,6 +55,27 @@ eth_hypotheticals = api.get_hypotheticals("ETH")
print(eth_hypotheticals)
```

### Fetching Comprehensive Asset Data

```python
btc_comprehensive = api.get_comprehensive_asset_data("BTC")
print(btc_comprehensive)
```

### Getting Strategy Value for a Specific Risk Level

```python
strategy_value = api.get_strategy_value_for_risk("My Custom Strat", 50, "buy")
print(strategy_value)
```

### Getting Current Risk Level

```python
current_risk = api.get_current_risk("BTC")
print(current_risk)
```

## Error Handling

The client includes built-in error handling. You can check for errors in the API responses:
Expand All @@ -65,6 +90,14 @@ if api.has_error(result):

The client automatically handles rate limiting to ensure compliance with the API's usage rules (6 requests per minute).

## Caching

The client uses caching to reduce the number of API calls. You can set the cache TTL (time-to-live) when initializing the client. The default cache TTL is 5 minutes.

```python
api = AlphaSquared("YOUR_API_TOKEN", cache_ttl=300) # 5 minutes
```

## Documentation

For more information about the AlphaSquared API, consult the [official API documentation](https://alphasquared.io/api-docs).
Expand Down
57 changes: 56 additions & 1 deletion alphasquared/alphasquared.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@
import logging
import json
from typing import Dict, Any
from functools import lru_cache
from datetime import datetime, timedelta

class AlphaSquaredAPIException(Exception):
"""Custom exception for AlphaSquared API errors."""
Expand All @@ -12,12 +14,14 @@ class AlphaSquared:

BASE_URL = "https://alphasquared.io/wp-json/as/v1"
RATE_LIMIT = 6 # requests per minute
DEFAULT_CACHE_TTL = 300 # 5 minutes

def __init__(self, api_token: str):
def __init__(self, api_token: str, cache_ttl: int = None):
self.api_token = api_token
self.last_request_time = 0
self.request_count = 0
self.logger = self._setup_logging()
self.cache_ttl = cache_ttl if cache_ttl is not None else self.DEFAULT_CACHE_TTL

@staticmethod
def _setup_logging():
Expand Down Expand Up @@ -98,6 +102,57 @@ def get_hypotheticals(self, asset_symbol: str) -> Dict[str, Any]:
except AlphaSquaredAPIException as e:
return self._handle_api_exception(e, f"getting hypotheticals for {asset_symbol}")

@lru_cache(maxsize=32)
def _cached_comprehensive_asset_data(self, asset: str, timestamp: int) -> Dict[str, Any]:
return self._get_comprehensive_asset_data_uncached(asset)

def get_comprehensive_asset_data(self, asset: str) -> Dict[str, Any]:
timestamp = int(datetime.now().timestamp() // self.cache_ttl)
return self._cached_comprehensive_asset_data(asset, timestamp)

def _get_comprehensive_asset_data_uncached(self, asset: str) -> Dict[str, Any]:
"""
Fetch comprehensive data for a given asset, including asset info and hypotheticals.
:param asset: The asset symbol (e.g., 'BTC', 'ETH')
:return: A dictionary containing asset info and hypotheticals
"""
asset_info = self.get_asset_info(asset)
hypotheticals = self.get_hypotheticals(asset)

return {
"asset_info": asset_info,
"hypotheticals": hypotheticals
}

def get_current_risk(self, asset: str) -> float:
"""
Get the current risk value for a given asset.
:param asset: The asset symbol (e.g., 'BTC', 'ETH')
:return: The current risk value as a float
"""
asset_info = self.get_asset_info(asset)
return float(asset_info.get("current_risk", 0))

def get_strategy_value_for_risk(self, strategy_name: str, risk_level: int, action: str = "buy") -> float:
"""
Get the strategy value for a specific risk level.
:param strategy_name: The name of the strategy
:param risk_level: The risk level (0-100)
:param action: Either "buy" or "sell" (default: "buy")
:return: The strategy value as a float
"""
strategy_values = self.get_strategy_values(strategy_name)
action_values = strategy_values.get(f"{action}_values", {})
value = action_values.get(f"risk_{risk_level}", "0")
return float(value) if value else 0.0

def force_refresh_asset_data(self, asset: str) -> Dict[str, Any]:
self._cached_comprehensive_asset_data.cache_clear()
return self.get_comprehensive_asset_data(asset)

@staticmethod
def has_error(result: Dict[str, Any]) -> bool:
"""Check if the result contains an error."""
Expand Down
4 changes: 2 additions & 2 deletions setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,9 @@
with open("requirements.txt") as f:
requirements = f.read().splitlines()

setup(
setup(
name='alphasquared-py',
version='0.1.0',
version='0.2.0',
description='The unofficial Python client for the AlphaSquared API',
long_description=long_description,
long_description_content_type="text/markdown",
Expand Down
97 changes: 96 additions & 1 deletion tests/test_alphasquared.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,12 @@
import json
import logging
from alphasquared.alphasquared import AlphaSquared
from time import sleep

class TestAlphaSquared(unittest.TestCase):
def setUp(self):
self.api = AlphaSquared("test_token")
self.cache_ttl = 1 # 1 second for faster testing
self.api = AlphaSquared("test_token", cache_ttl=self.cache_ttl)
# Disable logging for tests
logging.disable(logging.CRITICAL)

Expand Down Expand Up @@ -89,5 +91,98 @@ def test_get_api_error(self):
def test_rate_limiting(self):
self.assertTrue(hasattr(self.api, '_check_rate_limit'))

@patch('alphasquared.alphasquared.requests.get')
def test_get_comprehensive_asset_data(self, mock_get):
mock_response = Mock()
mock_response.status_code = 200
mock_response.json.side_effect = [
{"symbol": "BTC", "price": "50000", "current_risk": "40.5"},
{"asset": "BTC", "hypotheticals": [{"price": "50000", "risk": "0.5"}]}
]
mock_get.return_value = mock_response

result = self.api.get_comprehensive_asset_data("BTC")
self.assertEqual(result["asset_info"]["symbol"], "BTC")
self.assertEqual(result["hypotheticals"]["asset"], "BTC")

@patch('alphasquared.alphasquared.requests.get')
def test_caching(self, mock_get):
mock_response = Mock()
mock_response.status_code = 200
mock_response.json.side_effect = [
{"symbol": "BTC", "price": "50000", "current_risk": "40.5"},
{"asset": "BTC", "hypotheticals": [{"price": "50000", "risk": "0.5"}]}
]
mock_get.return_value = mock_response

# First call
self.api.get_comprehensive_asset_data("BTC")

# Second call (should use cache)
self.api.get_comprehensive_asset_data("BTC")

# Assert that the API was only called once for each endpoint
self.assertEqual(mock_get.call_count, 2)

@patch('alphasquared.alphasquared.requests.get')
def test_cache_expiration(self, mock_get):
mock_response = Mock()
mock_response.status_code = 200
mock_response.json.side_effect = [
{"symbol": "BTC", "price": "50000", "current_risk": "40.5"},
{"asset": "BTC", "hypotheticals": [{"price": "50000", "risk": "0.5"}]},
{"symbol": "BTC", "price": "51000", "current_risk": "41.0"},
{"asset": "BTC", "hypotheticals": [{"price": "51000", "risk": "0.6"}]}
]
mock_get.return_value = mock_response

# First call
result1 = self.api.get_comprehensive_asset_data("BTC")

# Wait for cache to expire
sleep(self.cache_ttl + 0.1)

# Second call (should not use cache)
result2 = self.api.get_comprehensive_asset_data("BTC")

self.assertNotEqual(result1["asset_info"]["price"], result2["asset_info"]["price"])
self.assertEqual(mock_get.call_count, 4)

@patch('alphasquared.alphasquared.requests.get')
def test_force_refresh_asset_data(self, mock_get):
mock_response = Mock()
mock_response.status_code = 200
mock_response.json.side_effect = [
{"symbol": "BTC", "price": "50000", "current_risk": "40.5"},
{"asset": "BTC", "hypotheticals": [{"price": "50000", "risk": "0.5"}]},
{"symbol": "BTC", "price": "51000", "current_risk": "41.0"},
{"asset": "BTC", "hypotheticals": [{"price": "51000", "risk": "0.6"}]}
]
mock_get.return_value = mock_response

# First call
result1 = self.api.get_comprehensive_asset_data("BTC")

# Force refresh
result2 = self.api.force_refresh_asset_data("BTC")

self.assertNotEqual(result1["asset_info"]["price"], result2["asset_info"]["price"])
self.assertEqual(mock_get.call_count, 4)

def test_get_current_risk(self):
self.api.get_asset_info = Mock(return_value={"current_risk": "42.5"})
risk = self.api.get_current_risk("BTC")
self.assertEqual(risk, 42.5)

def test_get_strategy_value_for_risk(self):
self.api.get_strategy_values = Mock(return_value={
"buy_values": {"risk_50": "100"},
"sell_values": {"risk_50": "90"}
})
buy_value = self.api.get_strategy_value_for_risk("Test Strategy", 50, "buy")
sell_value = self.api.get_strategy_value_for_risk("Test Strategy", 50, "sell")
self.assertEqual(buy_value, 100.0)
self.assertEqual(sell_value, 90.0)

if __name__ == '__main__':
unittest.main()

0 comments on commit d14c114

Please sign in to comment.