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

added kafka exporter #72

Open
wants to merge 73 commits into
base: master
Choose a base branch
from
Open
Changes from 1 commit
Commits
Show all changes
73 commits
Select commit Hold shift + click to select a range
f009d4c
fix
Oct 29, 2019
269926f
remove ide files
Oct 30, 2019
8bafe10
add new fields to blocks and transactions export_blocks job
Oct 30, 2019
6ffd226
update txn mapper, adds transaction_id
Oct 30, 2019
24c6693
syncs legacy export schema with current schema
Nov 4, 2019
ecd1bfa
Merge pull request #1 from merklescience/dev-add-coin
aknirmal90 Nov 4, 2019
2c6367f
removing unused fields
Nov 11, 2019
8fb7260
Merge pull request #2 from merklescience/feature/fix-export-schema
aknirmal90 Nov 11, 2019
03dbcad
fix for tranasction ids
Nov 12, 2019
f4b0802
handle case of lower verbosity
Nov 12, 2019
7ef0fdb
Merge pull request #3 from merklescience/feature/fix-export-schema
aknirmal90 Nov 12, 2019
899bea6
try caching cryptocompare prices
Nov 12, 2019
09f661e
reading from cache for daily prices
Nov 12, 2019
0ef5f9f
Merge pull request #4 from merklescience/feature/fix-export-schema
aknirmal90 Nov 12, 2019
e009deb
increase tiemout - bitcoincash failures
Nov 13, 2019
a718ec3
Merge pull request #5 from merklescience/feature/fix-export-schema
aknirmal90 Nov 13, 2019
63aa436
stream
Mar 9, 2020
a62b8a7
countainer command
Mar 9, 2020
ca85867
fix streaming
saurabhdaga-merkle Feb 25, 2021
e12dbbb
add bsv
saurabhdaga-merkle Mar 8, 2021
81943de
add bsv
saurabhdaga-merkle Mar 8, 2021
9c175cf
add bsv
saurabhdaga-merkle Mar 8, 2021
fd10f2d
debug api key
saurabhdaga-merkle Mar 22, 2021
3e9d8f6
add BSV
saurabhdaga-merkle Mar 22, 2021
391b3d4
BSV changes
saurabhdaga-merkle May 3, 2021
79b0eee
Merge branch 'blockchain-etl:master' into master
saurabhdaga-merkle Jun 14, 2021
a0508e7
fixes
saurabhdaga-merkle Jul 6, 2021
8952da7
merged master
saurabhdaga-merkle Jul 6, 2021
6b8a8cf
fix decimal places
saurabhdaga-merkle Jul 7, 2021
c77ba34
Fix for bsv (#9)
saurabhdaga-merkle Aug 11, 2021
63cce73
Fix for bsv (#10)
saurabhdaga-merkle Aug 11, 2021
b109ee2
updated readme
saurabhdaga-merkle Aug 26, 2021
21537da
updated values
saurabhdaga-merkle Aug 26, 2021
a9be6f2
taproot testing from upstream
Dec 16, 2021
e78acb9
Merge pull request #13 from merklescience/streaming-taproot
prassee Dec 16, 2021
2940637
chunking publish to pubsub (#11)
akshay-ghy Dec 16, 2021
56d4ad0
Merge branch 'blockchain-etl:master' into master
saurabhdaga-merkle Dec 16, 2021
bcc931b
hot fix for taproot
Dec 16, 2021
6c63de3
Merge pull request #14 from merklescience/streaming-taproot
prassee Dec 16, 2021
cc746b8
Merge branch 'blockchain-etl:master' into master
saurabhdaga-merkle Jun 26, 2022
a37aefa
Remove coin_price_usd (#7)
akshay-ghy Jun 26, 2022
a6d645e
Remove coin price (#18)
saurabhdaga-merkle Jun 26, 2022
bf8bf26
Remove coin price (#19)
saurabhdaga-merkle Jun 26, 2022
831a083
merge branch streaming
saurabhdaga-merkle Jun 26, 2022
e2fd993
Adds cloudbuild config
Aug 30, 2022
37fa609
Adds correct dockerfile
Aug 30, 2022
08057c3
Merge pull request #20 from merklescience/feature/adds-continous-inte…
aknirmal90 Aug 30, 2022
3dbbf74
Fix filename
Aug 30, 2022
69fd838
Merge pull request #21 from merklescience/master
aknirmal90 Aug 30, 2022
8c8d363
Merge pull request #22 from merklescience/feature/adds-continous-inte…
aknirmal90 Aug 30, 2022
68b68f0
Merge pull request #23 from merklescience/develop
aknirmal90 Aug 30, 2022
1cc2312
Fix typo
Aug 30, 2022
fc62d39
Merge pull request #24 from merklescience/feature/adds-continous-inte…
aknirmal90 Aug 30, 2022
0259a26
Merge branch 'master' into develop
aknirmal90 Aug 30, 2022
81283f3
Merge pull request #25 from merklescience/develop
aknirmal90 Aug 30, 2022
68880a3
resolve conflicts with master
akshay-ghy Sep 2, 2022
d2ea670
Merge pull request #6 from merklescience/streaming
aknirmal90 Sep 2, 2022
42dd40d
added kafka exporter
Aug 17, 2023
5255388
added test file
Aug 17, 2023
0dbabec
test file created for bitcoin flatten logic
Aug 18, 2023
1581d95
updated the code
Aug 18, 2023
3b35718
added bitcoin flatten transformation
Aug 18, 2023
2b33c58
confluent conf added
Aug 19, 2023
4260518
new changes
Aug 19, 2023
7b128dc
added key in kafka producer
Aug 20, 2023
8c37eca
new changes
Aug 21, 2023
de69224
new changes
Aug 21, 2023
f92e88a
new changes
Aug 21, 2023
4c521e5
added topic mapping params in cli
Aug 22, 2023
f7b9cba
new changes
Aug 29, 2023
65fca90
Merge branch 'master' into latest
aknirmal90 Sep 5, 2023
69ba9f2
updated the flatten logic
Sep 7, 2023
9512608
merge with latest branchMerge branch 'latest' of github.com:merklesci…
Sep 8, 2023
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
Next Next commit
fix
Saurabh Daga authored and Saurabh Daga committed Oct 29, 2019

Verified

This commit was created on GitHub.com and signed with GitHub’s verified signature.
commit f009d4cce05c0b2e5ab84ac69bf546bc11953963
3 changes: 3 additions & 0 deletions .idea/.gitignore

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

9 changes: 9 additions & 0 deletions .idea/bitcoin-etl.iml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

5 changes: 5 additions & 0 deletions .idea/codeStyles/codeStyleConfig.xml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 4 additions & 0 deletions .idea/misc.xml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

8 changes: 8 additions & 0 deletions .idea/modules.xml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

6 changes: 6 additions & 0 deletions .idea/vcs.xml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

9 changes: 6 additions & 3 deletions bitcoinetl/cli/export_all.py
Original file line number Diff line number Diff line change
@@ -25,7 +25,7 @@
import re

from datetime import datetime, timedelta
from bitcoinetl.enumeration.chain import Chain
from bitcoinetl.enumeration.chain import Chain, CoinPriceType
from bitcoinetl.jobs.export_all import export_all as do_export_all
from bitcoinetl.service.btc_block_range_service import BtcBlockRangeService
from bitcoinetl.rpc.bitcoin_rpc import BitcoinRpc
@@ -96,7 +96,10 @@ def get_partitions(start, end, partition_batch_size, provider_uri):
@click.option('-c', '--chain', default=Chain.BITCOIN, type=click.Choice(Chain.ALL),
help='The type of chain.')
@click.option('--enrich', default=False, type=bool, help='Enable filling in transactions inputs fields.')
def export_all(start, end, partition_batch_size, provider_uri, output_dir, max_workers, export_batch_size, chain, enrich):
@click.option('--coin-price-type', default=CoinPriceType.empty, type=int,
help='Enable querying CryptoCompare for coin prices. 0 for no price, 1 for daily price, 2 for hourly price.')
def export_all(start, end, partition_batch_size, provider_uri, output_dir, max_workers, export_batch_size, chain, enrich, coin_price_type):
"""Exports all data for a range of blocks."""
do_export_all(chain, get_partitions(start, end, partition_batch_size, provider_uri),
output_dir, provider_uri, max_workers, export_batch_size, enrich)
output_dir, provider_uri, max_workers, export_batch_size, enrich,
coin_price_type)
11 changes: 8 additions & 3 deletions bitcoinetl/cli/export_blocks_and_transactions.py
Original file line number Diff line number Diff line change
@@ -23,7 +23,7 @@

import click

from bitcoinetl.enumeration.chain import Chain
from bitcoinetl.enumeration.chain import Chain, CoinPriceType
from bitcoinetl.jobs.export_blocks_job import ExportBlocksJob
from bitcoinetl.jobs.exporters.blocks_and_transactions_item_exporter import blocks_and_transactions_item_exporter
from bitcoinetl.rpc.bitcoin_rpc import BitcoinRpc
@@ -48,8 +48,11 @@
'If not provided transactions will not be exported. Use "-" for stdout')
@click.option('-c', '--chain', default=Chain.BITCOIN, type=click.Choice(Chain.ALL),
help='The type of chain')
@click.option('--coin-price-type', default=CoinPriceType.empty, type=int,
help='Enable querying CryptoCompare for coin prices. 0 for no price, 1 for daily price, 2 for hourly price.')
def export_blocks_and_transactions(start_block, end_block, batch_size, provider_uri,
max_workers, blocks_output, transactions_output, chain):
max_workers, blocks_output, transactions_output, chain,
coin_price_type):
"""Export blocks and transactions."""
if blocks_output is None and transactions_output is None:
raise ValueError('Either --blocks-output or --transactions-output options must be provided')
@@ -63,5 +66,7 @@ def export_blocks_and_transactions(start_block, end_block, batch_size, provider_
item_exporter=blocks_and_transactions_item_exporter(blocks_output, transactions_output),
chain=chain,
export_blocks=blocks_output is not None,
export_transactions=transactions_output is not None)
export_transactions=transactions_output is not None,
coin_price_type=coin_price_type
)
job.run()
1 change: 1 addition & 0 deletions bitcoinetl/domain/block.py
Original file line number Diff line number Diff line change
@@ -38,6 +38,7 @@ def __init__(self):
self.coinbase_param = None

self.transactions = []
self.coin_price_usd = None

def has_full_transactions(self):
return len(self.transactions) > 0 and isinstance(self.transactions[0], BtcTransaction)
3 changes: 3 additions & 0 deletions bitcoinetl/domain/transaction.py
Original file line number Diff line number Diff line change
@@ -43,6 +43,9 @@ def __init__(self):
self.join_splits = []
self.value_balance = 0

# New fields
self.coin_price_usd = None

def add_input(self, input):
if len(self.inputs) > 0:
input.index = self.inputs[len(self.inputs) - 1].index + 1
20 changes: 20 additions & 0 deletions bitcoinetl/enumeration/chain.py
Original file line number Diff line number Diff line change
@@ -10,3 +10,23 @@ class Chain:
ALL = [BITCOIN, BITCOIN_CASH, DOGECOIN, LITECOIN, DASH, ZCASH, MONACOIN]
# Old API doesn't support verbosity for getblock which doesn't allow querying all transactions in a block in 1 go.
HAVE_OLD_API = [BITCOIN_CASH, DOGECOIN, DASH, MONACOIN]

@classmethod
def ticker_symbol(cls, chain):
symbols = {
'bitcoin': 'BTC',
'bitcoin_cash': 'BCH',
'dogecoin': 'DOGE',
'litecoin': 'LTC',
'dash': 'DASH',
'zcash': 'ZEC',
'monacoin': 'MONA',
}
return symbols.get(chain, None)


class CoinPriceType:

empty = 0
daily = 1
hourly = 2
9 changes: 7 additions & 2 deletions bitcoinetl/jobs/export_all.py
Original file line number Diff line number Diff line change
@@ -40,7 +40,10 @@
logger = logging.getLogger('export_all')


def export_all(chain, partitions, output_dir, provider_uri, max_workers, batch_size, enrich):
def export_all(
chain, partitions, output_dir, provider_uri, max_workers, batch_size, enrich,
coin_price_type
):
for batch_start_block, batch_end_block, partition_dir, *args in partitions:
# # # start # # #

@@ -101,7 +104,9 @@ def export_all(chain, partitions, output_dir, provider_uri, max_workers, batch_s
max_workers=max_workers,
item_exporter=blocks_and_transactions_item_exporter(blocks_file, transactions_file),
export_blocks=blocks_file is not None,
export_transactions=transactions_file is not None)
export_transactions=transactions_file is not None,
coin_price_type=coin_price_type,
)
job.run()

if enrich == True:
7 changes: 5 additions & 2 deletions bitcoinetl/jobs/export_blocks_job.py
Original file line number Diff line number Diff line change
@@ -27,6 +27,7 @@
from blockchainetl.executors.batch_work_executor import BatchWorkExecutor
from blockchainetl.jobs.base_job import BaseJob
from blockchainetl.utils import validate_range
from bitcoinetl.enumeration.chain import CoinPriceType


# Exports blocks and transactions
@@ -41,8 +42,10 @@ def __init__(
item_exporter,
chain,
export_blocks=True,
export_transactions=True):
export_transactions=True,
coin_price_type=CoinPriceType.empty):
validate_range(start_block, end_block)

self.start_block = start_block
self.end_block = end_block

@@ -54,7 +57,7 @@ def __init__(
if not self.export_blocks and not self.export_transactions:
raise ValueError('At least one of export_blocks or export_transactions must be True')

self.btc_service = BtcService(bitcoin_rpc, chain)
self.btc_service = BtcService(bitcoin_rpc, chain, coin_price_type)
self.block_mapper = BtcBlockMapper()
self.transaction_mapper = BtcTransactionMapper()

Original file line number Diff line number Diff line change
@@ -35,7 +35,8 @@
'nonce',
'bits',
'coinbase_param',
'transaction_count'
'transaction_count',
"coin_price_usd",
]

TRANSACTION_FIELDS_TO_EXPORT = [
@@ -57,7 +58,8 @@
'output_count',
'input_value',
'output_value',
'fee'
'fee',
'coin_price_usd',
]


4 changes: 3 additions & 1 deletion bitcoinetl/mappers/block_mapper.py
Original file line number Diff line number Diff line change
@@ -58,6 +58,7 @@ def json_dict_to_block(self, json_dict):

block.transaction_count = len(raw_transactions)

block.coin_price_usd = json_dict.get('coin_price_usd')
return block

def block_to_dict(self, block):
@@ -74,7 +75,8 @@ def block_to_dict(self, block):
'nonce': block.nonce,
'bits': block.bits,
'coinbase_param': block.coinbase_param,
'transaction_count': len(block.transactions)
'transaction_count': len(block.transactions),
"coin_price_usd": block.coin_price_usd,
}


2 changes: 2 additions & 0 deletions bitcoinetl/mappers/transaction_mapper.py
Original file line number Diff line number Diff line change
@@ -89,6 +89,7 @@ def transaction_to_dict(self, transaction):
'input_value': transaction.calculate_input_value(),
'output_value': transaction.calculate_output_value(),
'fee': transaction.calculate_fee(),
'coin_price_usd': transaction.coin_price_usd,
}
return result

@@ -104,6 +105,7 @@ def dict_to_transaction(self, dict):
transaction.block_timestamp = dict.get('block_timestamp')
transaction.is_coinbase = dict.get('is_coinbase')
transaction.index = dict.get('index')
transaction.coin_price_usd = dict.get('coin_price_usd')

transaction.inputs = self.transaction_input_mapper.dicts_to_inputs(dict.get('inputs'))
transaction.outputs = self.transaction_output_mapper.dicts_to_outputs(dict.get('outputs'))
65 changes: 63 additions & 2 deletions bitcoinetl/service/btc_service.py
Original file line number Diff line number Diff line change
@@ -22,22 +22,30 @@

from bitcoinetl.domain.transaction_input import BtcTransactionInput
from bitcoinetl.domain.transaction_output import BtcTransactionOutput
from bitcoinetl.enumeration.chain import Chain
from bitcoinetl.enumeration.chain import Chain, CoinPriceType
from bitcoinetl.json_rpc_requests import generate_get_block_hash_by_number_json_rpc, \
generate_get_block_by_hash_json_rpc, generate_get_transaction_by_id_json_rpc
from bitcoinetl.mappers.block_mapper import BtcBlockMapper
from bitcoinetl.mappers.transaction_mapper import BtcTransactionMapper
from bitcoinetl.service.btc_script_service import script_hex_to_non_standard_address
from bitcoinetl.service.genesis_transactions import GENESIS_TRANSACTIONS
from blockchainetl.utils import rpc_response_batch_to_results, dynamic_batch_iterator
from blockchainetl.cryptocompare import (
get_coin_price,
get_hour_id_from_ts,
get_day_id_from_ts,
get_ts_from_hour_id,
get_ts_from_day_id
)


class BtcService(object):
def __init__(self, bitcoin_rpc, chain=Chain.BITCOIN):
def __init__(self, bitcoin_rpc, chain=Chain.BITCOIN, coin_price_type=CoinPriceType.empty):
self.bitcoin_rpc = bitcoin_rpc
self.block_mapper = BtcBlockMapper()
self.transaction_mapper = BtcTransactionMapper()
self.chain = chain
self.coin_price_type = coin_price_type

def get_block(self, block_number, with_transactions=False):
block_hashes = self.get_block_hashes([block_number])
@@ -73,10 +81,14 @@ def get_blocks_by_hashes(self, block_hash_batch, with_transactions=True):
if self.chain in Chain.HAVE_OLD_API and with_transactions:
self._fetch_transactions(blocks)

self._add_coin_price_to_blocks(blocks, self.coin_price_type)

for block in blocks:
self._remove_coinbase_input(block)

if block.has_full_transactions():
for transaction in block.transactions:
self._add_coin_price_to_transaction(transaction, block.coin_price_usd)
self._add_non_standard_addresses(transaction)
if self.chain == Chain.ZCASH:
self._add_shielded_inputs_and_outputs(transaction)
@@ -186,5 +198,54 @@ def _add_shielded_inputs_and_outputs(self, transaction):
output.value = -transaction.value_balance
transaction.add_output(output)

def non_coinbase_txs(self, block):
return [transaction
for transaction in block.transactions
if not transaction.transaction_id != block.coinbase_tx
]

def get_transaction_ids(self, block):
return [tx.transaction_id for tx in block.transactions]

def get_block_reward(self, block):
return block.coinbase_tx.calculate_output_value()

def get_input_value(self, block):
non_coinbase_txs = self.non_coinbase_txs(block)
return sum([tx.calculate_input_value() for tx in non_coinbase_txs])

def _add_coin_price_to_blocks(self, blocks, coin_price_type):
from_currency_code = Chain.ticker_symbol(self.chain)

if not from_currency_code or coin_price_type == CoinPriceType.empty:
return

elif coin_price_type == CoinPriceType.hourly:
block_hour_ids = list(set([get_hour_id_from_ts(block.timestamp) for block in blocks]))
block_hours_ts = {hour_id: get_ts_from_hour_id(hour_id) for hour_id in block_hour_ids}
coin_price_hours = {
hour_id: get_coin_price(from_currency_code=from_currency_code, timestamp=hour_ts, resource="histohour")
for hour_id, hour_ts in block_hours_ts.items()
}

for block in blocks:
block_hour_id = get_hour_id_from_ts(block.timestamp)
block.coin_price_usd = coin_price_hours[block_hour_id]

elif coin_price_type == CoinPriceType.daily:
block_day_ids = list(set([get_day_id_from_ts(block.timestamp) for block in blocks]))
block_days_ts = {day_id: get_ts_from_day_id(day_id) for day_id in block_day_ids}
coin_price_days = {
day_id: get_coin_price(from_currency_code=from_currency_code, timestamp=day_ts, resource="histoday")
for day_id, day_ts in block_days_ts.items()
}

for block in blocks:
block_day_id = get_day_id_from_ts(block.timestamp)
block.coin_price_usd = coin_price_days[block_day_id]

def _add_coin_price_to_transaction(self, transaction, coin_price_usd):
transaction.coin_price_usd = coin_price_usd


ADDRESS_TYPE_SHIELDED = 'shielded'
125 changes: 125 additions & 0 deletions blockchainetl/cryptocompare.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,125 @@
# MIT License
#
# Copyright (c) 2019 Nirmal AK, nirmal@merklescience.com
#
# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documentation files (the "Software"), to deal
# in the Software without restriction, including without limitation the rights
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
# copies of the Software, and to permit persons to whom the Software is
# furnished to do so, subject to the following conditions:
#
# The above copyright notice and this permission notice shall be included in all
# copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
# SOFTWARE.

import os
import requests
from time import time
from math import floor
from datetime import datetime, timedelta


CRYPTOCOMPARE_API_KEY = os.getenv("CRYPTOCOMPARE_API_KEY", "")


class CryptoCompareRequestException(Exception):
pass


def get_hour_id_from_ts(timestamp: int) -> int:
"""
returns the number of hours elapsed since 1st Jan 2000
"""
base_ts = datetime(2000, 1, 1).timestamp()
seconds_to_hour = 60 * 60
return floor((int(timestamp) - base_ts) / seconds_to_hour)


def get_day_id_from_ts(timestamp: int) -> int:
"""
returns the number of days elapsed since 1st Jan 2000
"""
base_ts = datetime(2000, 1, 1).timestamp()
seconds_to_day = 60 * 60 * 24
return floor((int(timestamp) - base_ts) / seconds_to_day)


def get_ts_from_hour_id(hour_id: int) -> int:
base_date = datetime(2000, 1, 1)
reference_date = base_date + timedelta(hours=hour_id)
return floor(reference_date.timestamp())


def get_ts_from_day_id(day_id: int) -> int:
base_date = datetime(2000, 1, 1)
reference_date = base_date + timedelta(days=day_id)
return floor(reference_date.timestamp())


def _make_request(
resource: str,
from_currency_code: str,
to_currency_code: str,
timestamp: int,
access_token: str,
exchange_code: str,
num_records: int,
api_version: str
) -> requests.Response:
"""
API documentation for cryptocompare can be found at https://min-api.cryptocompare.com/documentation
"""
base_url = f"https://min-api.cryptocompare.com/data/{api_version}/{resource}"
params = {
"fsym": from_currency_code,
"tsym": to_currency_code,
"e": exchange_code,
"limit": num_records,
"toTs": timestamp,
"api_key": access_token
}
return requests.get(base_url, params=params)


def get_coin_price(
from_currency_code: str,
timestamp: int,
resource="histohour",
to_currency_code: str="USD",
exchange_code: str="CCCAGG",
num_records: int=1,
api_version: str ="v2",
access_token: str=CRYPTOCOMPARE_API_KEY,
):
"""
Prices are retrieved from hourly price resource as prices
are available for historical data from when available
"""
response = _make_request(
resource=resource,
from_currency_code=from_currency_code,
to_currency_code=to_currency_code,
timestamp=int(timestamp),
access_token=access_token,
exchange_code=exchange_code,
num_records=num_records,
api_version=api_version,
)
if not response.status_code == 200:
raise CryptoCompareRequestException

payload = response.json()
if payload["Type"] != 100:
raise CryptoCompareRequestException(payload.get("Message", ""))

data = payload["Data"]["Data"]
avg_price = sum(item["open"] for item in data) / len(data)
return round(avg_price, 2)