summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorDanglewood <85772166+deeleeramone@users.noreply.github.com>2024-04-23 01:38:40 -0700
committerGitHub <noreply@github.com>2024-04-23 08:38:40 +0000
commit6b3d6fbe67687579689aa760a312b692ef4b609a (patch)
treeecd6318f922f77e27b27dd94e28f5d70d89144c0
parent2ccf39d8cc3e05eaf91ceeb6edd77cf8d30fdec4 (diff)
add currency snapshots from polygon (#6333)
-rw-r--r--openbb_platform/extensions/currency/integration/test_currency_api.py8
-rw-r--r--openbb_platform/extensions/currency/integration/test_currency_python.py8
-rw-r--r--openbb_platform/openbb/assets/reference.json151
-rw-r--r--openbb_platform/openbb/package/currency.py55
-rw-r--r--openbb_platform/providers/polygon/openbb_polygon/__init__.py2
-rw-r--r--openbb_platform/providers/polygon/openbb_polygon/models/currency_snapshots.py234
-rw-r--r--openbb_platform/providers/polygon/tests/record/http/test_polygon_fetchers/test_polygon_currency_snapshots_fetcher.yaml2310
-rw-r--r--openbb_platform/providers/polygon/tests/test_polygon_fetchers.py11
8 files changed, 2766 insertions, 13 deletions
diff --git a/openbb_platform/extensions/currency/integration/test_currency_api.py b/openbb_platform/extensions/currency/integration/test_currency_api.py
index f2f5d56e34e..e85f2e931e9 100644
--- a/openbb_platform/extensions/currency/integration/test_currency_api.py
+++ b/openbb_platform/extensions/currency/integration/test_currency_api.py
@@ -180,6 +180,14 @@ def test_currency_reference_rates(params, headers):
"quote_type": "indirect",
}
),
+ (
+ {
+ "provider": "polygon",
+ "base": "USD,XAU",
+ "counter_currencies": "EUR,JPY,GBP",
+ "quote_type": "indirect",
+ }
+ ),
],
)
@pytest.mark.integration
diff --git a/openbb_platform/extensions/currency/integration/test_currency_python.py b/openbb_platform/extensions/currency/integration/test_currency_python.py
index f2352a1669c..2c81518ab41 100644
--- a/openbb_platform/extensions/currency/integration/test_currency_python.py
+++ b/openbb_platform/extensions/currency/integration/test_currency_python.py
@@ -168,6 +168,14 @@ def test_currency_reference_rates(params, obb):
"quote_type": "indirect",
}
),
+ (
+ {
+ "provider": "polygon",
+ "base": "USD,XAU",
+ "counter_currencies": "EUR,JPY,GBP",
+ "quote_type": "indirect",
+ }
+ ),
],
)
@pytest.mark.integration
diff --git a/openbb_platform/openbb/assets/reference.json b/openbb_platform/openbb/assets/reference.json
index d9f6ebca19d..279f31fc8cd 100644
--- a/openbb_platform/openbb/assets/reference.json
+++ b/openbb_platform/openbb/assets/reference.json
@@ -806,7 +806,7 @@
{
"name": "base",
"type": "Union[str, List[str]]",
- "description": "The base currency symbol. Multiple items allowed for provider(s): fmp.",
+ "description": "The base currency symbol. Multiple items allowed for provider(s): fmp, polygon.",
"default": "usd",
"optional": true
},
@@ -826,13 +826,14 @@
},
{
"name": "provider",
- "type": "Literal['fmp']",
+ "type": "Literal['fmp', 'polygon']",
"description": "The provider to use for the query, by default None. If None, the provider specified in defaults is selected or 'fmp' if there is no default.",
"default": "fmp",
"optional": true
}
],
- "fmp": []
+ "fmp": [],
+ "polygon": []
},
"returns": {
"OBBject": [
@@ -843,7 +844,7 @@
},
{
"name": "provider",
- "type": "Optional[Literal['fmp']]",
+ "type": "Optional[Literal['fmp', 'polygon']]",
"description": "Provider name."
},
{
@@ -979,6 +980,148 @@
"default": null,
"optional": true
}
+ ],
+ "polygon": [
+ {
+ "name": "vwap",
+ "type": "float",
+ "description": "The volume-weighted average price.",
+ "default": null,
+ "optional": true
+ },
+ {
+ "name": "change",
+ "type": "float",
+ "description": "The change in price from the previous day.",
+ "default": null,
+ "optional": true
+ },
+ {
+ "name": "change_percent",
+ "type": "float",
+ "description": "The percentage change in price from the previous day.",
+ "default": null,
+ "optional": true
+ },
+ {
+ "name": "prev_open",
+ "type": "float",
+ "description": "The previous day's opening price.",
+ "default": null,
+ "optional": true
+ },
+ {
+ "name": "prev_high",
+ "type": "float",
+ "description": "The previous day's high price.",
+ "default": null,
+ "optional": true
+ },
+ {
+ "name": "prev_low",
+ "type": "float",
+ "description": "The previous day's low price.",
+ "default": null,
+ "optional": true
+ },
+ {
+ "name": "prev_volume",
+ "type": "float",
+ "description": "The previous day's volume.",
+ "default": null,
+ "optional": true
+ },
+ {
+ "name": "prev_vwap",
+ "type": "float",
+ "description": "The previous day's VWAP.",
+ "default": null,
+ "optional": true
+ },
+ {
+ "name": "bid",
+ "type": "float",
+ "description": "The current bid price.",
+ "default": null,
+ "optional": true
+ },
+ {
+ "name": "ask",
+ "type": "float",
+ "description": "The current ask price.",
+ "default": null,
+ "optional": true
+ },
+ {
+ "name": "minute_open",
+ "type": "float",
+ "description": "The open price from the most recent minute bar.",
+ "default": null,
+ "optional": true
+ },
+ {
+ "name": "minute_high",
+ "type": "float",
+ "description": "The high price from the most recent minute bar.",
+ "default": null,
+ "optional": true
+ },
+ {
+ "name": "minute_low",
+ "type": "float",
+ "description": "The low price from the most recent minute bar.",
+ "default": null,
+ "optional": true
+ },
+ {
+ "name": "minute_close",
+ "type": "float",
+ "description": "The close price from the most recent minute bar.",
+ "default": null,
+ "optional": true
+ },
+ {
+ "name": "minute_volume",
+ "type": "float",
+ "description": "The volume from the most recent minute bar.",
+ "default": null,
+ "optional": true
+ },
+ {
+ "name": "minute_vwap",
+ "type": "float",
+ "description": "The VWAP from the most recent minute bar.",
+ "default": null,
+ "optional": true
+ },
+ {
+ "name": "minute_transactions",
+ "type": "float",
+ "description": "The number of transactions in the most recent minute bar.",
+ "default": null,
+ "optional": true
+ },
+ {
+ "name": "quote_timestamp",
+ "type": "datetime",
+ "description": "The timestamp of the last quote.",
+ "default": null,
+ "optional": true
+ },
+ {
+ "name": "minute_timestamp",
+ "type": "datetime",
+ "description": "The timestamp for the start of the most recent minute bar.",
+ "default": null,
+ "optional": true
+ },
+ {
+ "name": "last_updated",
+ "type": "datetime",
+ "description": "The last time the data was updated.",
+ "default": "",
+ "optional": false
+ }
]
},
"model": "CurrencySnapshots"
diff --git a/openbb_platform/openbb/package/currency.py b/openbb_platform/openbb/package/currency.py
index e27529d8c14..b2a90cfa9bc 100644
--- a/openbb_platform/openbb/package/currency.py
+++ b/openbb_platform/openbb/package/currency.py
@@ -154,7 +154,7 @@ class ROUTER_currency(Container):
base: Annotated[
Union[str, List[str]],
OpenBBField(
- description="The base currency symbol. Multiple comma separated items allowed for provider(s): fmp."
+ description="The base currency symbol. Multiple comma separated items allowed for provider(s): fmp, polygon."
),
] = "usd",
quote_type: Annotated[
@@ -170,7 +170,7 @@ class ROUTER_currency(Container):
),
] = None,
provider: Annotated[
- Optional[Literal["fmp"]],
+ Optional[Literal["fmp", "polygon"]],
OpenBBField(
description="The provider to use for the query, by default None.\n If None, the provider specified in defaults is selected or 'fmp' if there is\n no default."
),
@@ -182,12 +182,12 @@ class ROUTER_currency(Container):
Parameters
----------
base : Union[str, List[str]]
- The base currency symbol. Multiple comma separated items allowed for provider(s): fmp.
+ The base currency symbol. Multiple comma separated items allowed for provider(s): fmp, polygon.
quote_type : Literal['direct', 'indirect']
Whether the quote is direct or indirect. Selecting 'direct' will return the exchange rate as the amount of domestic currency required to buy one unit of the foreign currency. Selecting 'indirect' (default) will return the exchange rate as the amount of foreign currency required to buy one unit of the domestic currency.
counter_currencies : Union[List[str], str, None]
An optional list of counter currency symbols to filter for. None returns all.
- provider : Optional[Literal['fmp']]
+ provider : Optional[Literal['fmp', 'polygon']]
The provider to use for the query, by default None.
If None, the provider specified in defaults is selected or 'fmp' if there is
no default.
@@ -197,7 +197,7 @@ class ROUTER_currency(Container):
OBBject
results : List[CurrencySnapshots]
Serializable results.
- provider : Optional[Literal['fmp']]
+ provider : Optional[Literal['fmp', 'polygon']]
Provider name.
warnings : Optional[List[Warning_]]
List of warnings.
@@ -227,9 +227,10 @@ class ROUTER_currency(Container):
prev_close : Optional[float]
The previous close price.
change : Optional[float]
- The change in the price from the previous close. (provider: fmp)
+ The change in the price from the previous close. (provider: fmp, polygon)
change_percent : Optional[float]
- The change in the price from the previous close, as a normalized percent. (provider: fmp)
+ The change in the price from the previous close, as a normalized percent. (provider: fmp);
+ The percentage change in price from the previous day. (provider: polygon)
ma50 : Optional[float]
The 50-day moving average. (provider: fmp)
ma200 : Optional[float]
@@ -240,6 +241,42 @@ class ROUTER_currency(Container):
The 52-week low. (provider: fmp)
last_rate_timestamp : Optional[datetime]
The timestamp of the last rate. (provider: fmp)
+ vwap : Optional[float]
+ The volume-weighted average price. (provider: polygon)
+ prev_open : Optional[float]
+ The previous day's opening price. (provider: polygon)
+ prev_high : Optional[float]
+ The previous day's high price. (provider: polygon)
+ prev_low : Optional[float]
+ The previous day's low price. (provider: polygon)
+ prev_volume : Optional[float]
+ The previous day's volume. (provider: polygon)
+ prev_vwap : Optional[float]
+ The previous day's VWAP. (provider: polygon)
+ bid : Optional[float]
+ The current bid price. (provider: polygon)
+ ask : Optional[float]
+ The current ask price. (provider: polygon)
+ minute_open : Optional[float]
+ The open price from the most recent minute bar. (provider: polygon)
+ minute_high : Optional[float]
+ The high price from the most recent minute bar. (provider: polygon)
+ minute_low : Optional[float]
+ The low price from the most recent minute bar. (provider: polygon)
+ minute_close : Optional[float]
+ The close price from the most recent minute bar. (provider: polygon)
+ minute_volume : Optional[float]
+ The volume from the most recent minute bar. (provider: polygon)
+ minute_vwap : Optional[float]
+ The VWAP from the most recent minute bar. (provider: polygon)
+ minute_transactions : Optional[float]
+ The number of transactions in the most recent minute bar. (provider: polygon)
+ quote_timestamp : Optional[datetime]
+ The timestamp of the last quote. (provider: polygon)
+ minute_timestamp : Optional[datetime]
+ The timestamp for the start of the most recent minute bar. (provider: polygon)
+ last_updated : Optional[datetime]
+ The last time the data was updated. (provider: polygon)
Examples
--------
@@ -256,7 +293,7 @@ class ROUTER_currency(Container):
"provider": self._get_provider(
provider,
"/currency/snapshots",
- ("fmp",),
+ ("fmp", "polygon"),
)
},
standard_params={
@@ -265,6 +302,6 @@ class ROUTER_currency(Container):
"counter_currencies": counter_currencies,
},
extra_params=kwargs,
- info={"base": {"multiple_items_allowed": ["fmp"]}},
+ info={"base": {"multiple_items_allowed": ["fmp", "polygon"]}},
)
)
diff --git a/openbb_platform/providers/polygon/openbb_polygon/__init__.py b/openbb_platform/providers/polygon/openbb_polygon/__init__.py
index 2ec5e7a4109..f19dc944ec4 100644
--- a/openbb_platform/providers/polygon/openbb_polygon/__init__.py
+++ b/openbb_platform/providers/polygon/openbb_polygon/__init__.py
@@ -7,6 +7,7 @@ from openbb_polygon.models.company_news import PolygonCompanyNewsFetcher
from openbb_polygon.models.crypto_historical import PolygonCryptoHistoricalFetcher
from openbb_polygon.models.currency_historical import PolygonCurrencyHistoricalFetcher
from openbb_polygon.models.currency_pairs import PolygonCurrencyPairsFetcher
+from openbb_polygon.models.currency_snapshots import PolygonCurrencySnapshotsFetcher
from openbb_polygon.models.equity_historical import PolygonEquityHistoricalFetcher
from openbb_polygon.models.equity_nbbo import PolygonEquityNBBOFetcher
from openbb_polygon.models.income_statement import PolygonIncomeStatementFetcher
@@ -29,6 +30,7 @@ polygon_provider = Provider(
"CryptoHistorical": PolygonCryptoHistoricalFetcher,
"CurrencyHistorical": PolygonCurrencyHistoricalFetcher,
"CurrencyPairs": PolygonCurrencyPairsFetcher,
+ "CurrencySnapshots": PolygonCurrencySnapshotsFetcher,
"EquityHistorical": PolygonEquityHistoricalFetcher,
"EquityNBBO": PolygonEquityNBBOFetcher,
"EtfHistorical": PolygonEquityHistoricalFetcher,
diff --git a/openbb_platform/providers/polygon/openbb_polygon/models/currency_snapshots.py b/openbb_platform/providers/polygon/openbb_polygon/models/currency_snapshots.py
new file mode 100644
index 00000000000..2ac7c3280e5
--- /dev/null
+++ b/openbb_platform/providers/polygon/openbb_polygon/models/currency_snapshots.py
@@ -0,0 +1,234 @@
+"""Polygon Currency Snapshots"""
+
+# pylint: disable=unused-argument
+
+from datetime import datetime, timezone
+from typing import Any, Dict, List, Optional
+
+from openbb_core.provider.abstract.fetcher import Fetcher
+from openbb_core.provider.standard_models.currency_snapshots import (
+ CurrencySnapshotsData,
+ CurrencySnapshotsQueryParams,
+)
+from openbb_core.provider.utils.errors import EmptyDataError
+from openbb_core.provider.utils.helpers import amake_request, safe_fromtimestamp
+from pandas import DataFrame, concat
+from pydantic import Field
+
+
+class PolygonCurrencySnapshotsQueryParams(CurrencySnapshotsQueryParams):
+ """Polygon Currency Snapshots Query Parameters.
+
+
+ Source: https://polygon.io/docs/forex/get_v2_snapshot_locale_global_markets_forex_tickers
+ """
+
+ __json_schema_extra__ = {"base": ["multiple_items_allowed"]}
+
+
+class PolygonCurrencySnapshotsData(CurrencySnapshotsData):
+ """Polygon Currency Snapshots Data."""
+
+ vwap: Optional[float] = Field(
+ description="The volume-weighted average price.", default=None
+ )
+ change: Optional[float] = Field(
+ description="The change in price from the previous day.",
+ default=None,
+ )
+ change_percent: Optional[float] = Field(
+ description="The percentage change in price from the previous day.",
+ default=None,
+ json_schema_extra={"x-unit_measurement": "percent", "x-frontend_multiply": 100},
+ )
+ prev_open: Optional[float] = Field(
+ description="The previous day's opening price.", default=None
+ )
+ prev_high: Optional[float] = Field(
+ description="The previous day's high price.", default=None
+ )
+ prev_low: Optional[float] = Field(
+ description="The previous day's low price.", default=None
+ )
+ prev_volume: Optional[float] = Field(
+ description="The previous day's volume.", default=None
+ )
+ prev_vwap: Optional[float] = Field(
+ description="The previous day's VWAP.", default=None
+ )
+ bid: Optional[float] = Field(description="The current bid price.", default=None)
+ ask: Optional[float] = Field(description="The current ask price.", default=None)
+ minute_open: Optional[float] = Field(
+ description="The open price from the most recent minute bar.", default=None
+ )
+ minute_high: Optional[float] = Field(
+ description="The high price from the most recent minute bar.", default=None
+ )
+ minute_low: Optional[float] = Field(
+ description="The low price from the most recent minute bar.", default=None
+ )
+ minute_close: Optional[float] = Field(
+ description="The close price from the most recent minute bar.", default=None
+ )
+ minute_volume: Optional[float] = Field(
+ description="The volume from the most recent minute bar.", default=None
+ )
+ minute_vwap: Optional[float] = Field(
+ description="The VWAP from the most recent minute bar.", default=None
+ )
+ minute_transactions: Optional[float] = Field(
+ description="The number of transactions in the most recent minute bar.",
+ default=None,
+ )
+ quote_timestamp: Optional[datetime] = Field(
+ description="The timestamp of the last quote.", default=None
+ )
+ minute_timestamp: Optional[datetime] = Field(
+ description="The timestamp for the start of the most recent minute bar.",
+ default=None,
+ )
+ last_updated: Optional[datetime] = Field(
+ description="The last time the data was updated."
+ )
+
+
+class PolygonCurrencySnapshotsFetcher(
+ Fetcher[
+ PolygonCurrencySnapshotsQueryParams,
+ List[PolygonCurrencySnapshotsData],
+ ]
+):
+ """Polygon Currency Snapshots Fetcher."""
+
+ @staticmethod
+ def transform_query(params: Dict[str, Any]) -> PolygonCurrencySnapshotsQueryParams:
+ """Transform the query params."""
+ return PolygonCurrencySnapshotsQueryParams(**params)
+
+ @staticmethod
+ async def aextract_data(
+ query: PolygonCurrencySnapshotsQueryParams,
+ credentials: Optional[Dict[str, str]],
+ **kwargs: Any,
+ ) -> List[Dict]:
+ """Extract the raw data."""
+ api_key = credentials.get("polygon_api_key") if credentials else ""
+ url = f"https://api.polygon.io/v2/snapshot/locale/global/markets/forex/tickers?apiKey={api_key}"
+ results = await amake_request(url, **kwargs)
+ if results.get("status") != "OK":
+ raise RuntimeError(f"Error: {results.get('status')}")
+ return results.get("tickers", [])
+
+ @staticmethod
+ def transform_data( # pylint: disable=too-many-locals, too-many-statements
+ query: PolygonCurrencySnapshotsQueryParams,
+ data: List[Dict],
+ **kwargs: Any,
+ ) -> List[PolygonCurrencySnapshotsData]:
+ """Transform the data."""
+ if not data:
+ raise EmptyDataError("No data returned.")
+ counter_currencies = (
+ query.counter_currencies.upper().split(",")
+ if query.counter_currencies
+ else []
+ )
+
+ # Filter the data only for the symbols requested.
+ df = DataFrame(data)
+ df.ticker = df.ticker.str.replace("C:", "")
+ new_df = DataFrame()
+ for symbol in query.base.split(","):
+ temp = (
+ df.loc[df["ticker"].str.startswith(symbol)].copy()
+ if query.quote_type == "indirect"
+ else df.loc[df["ticker"].str.endswith(symbol)].copy()
+ )
+ temp["base_currency"] = symbol
+ temp["counter_currency"] = (
+ [d[3:] for d in temp["ticker"]]
+ if query.quote_type == "indirect"
+ else [d[:3] for d in temp["ticker"]]
+ )
+ # Filter for the counter currencies, if requested.
+ if query.counter_currencies is not None:
+ counter_currencies = ( # noqa: F841 # pylint: disable=unused-variable
+ query.counter_currencies
+ if isinstance(query.counter_currencies, list)
+ else query.counter_currencies.split(",")
+ )
+ temp = (
+ temp.query("`counter_currency`.isin(@counter_currencies)")
+ .set_index("counter_currency")
+ # Sets the counter currencies in the order they were requested.
+ .filter(items=counter_currencies, axis=0)
+ .reset_index()
+ )
+ # If there are no records, don't concatenate.
+ if len(temp) > 0:
+ new_df = concat([new_df, temp])
+ filtered_data = new_df.to_dict(orient="records")
+
+ if len(filtered_data) == 0 or not filtered_data:
+ raise EmptyDataError("No results were found with the parameters requested.")
+
+ results: List[PolygonCurrencySnapshotsData] = []
+ # Now unpack the nested object for the filtered results only.
+ for item in filtered_data:
+ new_item = {}
+ new_item["base_currency"] = item.get("base_currency")
+ new_item["counter_currency"] = item.get("counter_currency")
+ new_item["change"] = item.get("todaysChange", None)
+ change_percent = item.get("todaysChangePerc", None)
+ new_item["change_percent"] = (
+ change_percent / 100 if change_percent else None
+ )
+ updated = item.get("updated")
+ new_item["last_updated"] = (
+ safe_fromtimestamp(updated / 1e9, tz=timezone.utc) if updated else None
+ )
+ day = item.get("day", {})
+ if day:
+ new_item["last_rate"] = day.get("c", None)
+ new_item["open"] = day.get("o", None)
+ new_item["high"] = day.get("h", None)
+ new_item["low"] = day.get("l", None)
+ new_item["volume"] = day.get("v", None)
+ new_item["vwap"] = day.get("vw", None)
+ prev_day = item.get("prevDay", {})
+ if prev_day:
+ new_item["prev_open"] = prev_day.get("o", None)
+ new_item["prev_high"] = prev_day.get("h", None)
+ new_item["prev_low"] = prev_day.get("l", None)
+ new_item["prev_close"] = prev_day.get("c", None)
+ new_item["prev_volume"] = prev_day.get("v", None)
+ new_item["prev_vwap"] = prev_day.get("vw", None)
+ minute = item.get("min", {})
+ if minute:
+ new_item["minute_open"] = minute.get("o", None)
+ new_item["minute_high"] = minute.get("h", None)
+ new_item["minute_low"] = minute.get("l", None)
+ new_item["minute_close"] = minute.get("c", None)
+ new_item["minute_volume"] = minute.get("v", None)
+ new_item["minute_transactions"] = minute.get("n", None)
+ new_item["minute_vwap"] = minute.get("vw", None)
+ min_updated = minute.get("t")
+ new_item["minute_timestamp"] = (
+ safe_fromtimestamp(min_updated / 1000, tz=timezone.utc)
+ if min_updated
+ else None
+ )
+ quote = item.get("lastQuote", {})
+ if quote:
+ new_item["bid"] = quote.get("b", None)
+ new_item["ask"] = quote.get("a", None)
+ quote_time = quote.get("t")
+ new_item["quote_timestamp"] = (
+ safe_fromtimestamp(quote_time / 1000, tz=timezone.utc)
+ if quote_time
+ else None
+ )
+ if new_item:
+ results.append(PolygonCurrencySnapshotsData.model_validate(new_item))
+
+ return results
diff --git a/openbb_platform/providers/polygon/tests/record/http/test_polygon_fetchers/test_polygon_currency_snapshots_fetcher.yaml b/openbb_platform/providers/polygon/tests/record/http/test_polygon_fetchers/test_polygon_currency_snapshots_fetcher.yaml
new file mode 100644
index 00000000000..8223ec18230
--- /dev/null
+++ b/openbb_platform/providers/polygon/tests/record/http/test_polygon_fetchers/test_polygon_currency_snapshots_fetcher.yaml
@@ -0,0 +1,2310 @@
+interactions:
+- request:
+ body: null
+ headers:
+ Accept:
+ - application/json
+ Accept-Encoding:
+ - gzip, deflate
+ Connection:
+ - keep-alive
+ method: GET
+ uri: https://api.polygon.io/v2/snapshot/locale/global/markets/forex/tickers?apiKey=MOCK_API_KEY
+ response:
+ body:
+ string: !!binary |
+ H4sIAAAAAAAA/+T9W7PeNpImCv8XXcuMPB98J1kquUo+aKTSVNnfVXl1xbcm9trTE90ez56Y6P++
+ I0GA5HoJqiX5ytqOsIIriRdAgjhkJjKf/D9Pfv1vd//XP//t3598/f8bz0++fvLN1z/8/OKbn18/
+ efrk13/9l3/873//5v4f//3//88nX38FC0DA4b9IfVzozT//7W4tqJZJxBIQgOzx9Mn//B//8o9f
+ //kvT75GRw4xC9kqevrkX/7xv598/X+e/OuTr1EW4Hz65H59lJCnTx7WZ+KnT+76oz198tuTr8mr
+ 6G//q/+M/+Pp