Skip to content

Commit f3724f9

Browse files
authored
Merge pull request #14 from globophobe/feature/release-v0.1.6
Feature/release v0.1.6
2 parents 5537b6d + ec87948 commit f3724f9

25 files changed

+1666
-1703
lines changed

.env.sample

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,5 @@ GOOGLE_APPLICATION_CREDENTIALS=google-application-credentials.json
33
FIREBASE_ADMIN_CREDENTIALS=firebase-admin-credentials.json
44
PROJECT_ID=gcp-project-id
55
SERVICE_ACCOUNT=service-account
6-
BIGQUERY_LOCATION=gcp-region
7-
BIGQUERY_DATASET=cryptofeed-werks
86
SENTRY_DSN=optional
97
BINANCE_API_KEY=optional

Dockerfile

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2,13 +2,9 @@ FROM python:3.9-slim-buster
22

33
ARG POETRY_EXPORT
44
ARG PROJECT_ID
5-
ARG BIGQUERY_LOCATION
6-
ARG BIGQUERY_DATASET
75
ARG SENTRY_DSN
86

97
ENV PROJECT_ID $PROJECT_ID
10-
ENV BIGQUERY_LOCATION $BIGQUERY_LOCATION
11-
ENV BIGQUERY_DATASET $BIGQUERY_DATASET
128
ENV SENTRY_DSN $SENTRY_DSN
139

1410
ADD cryptofeed_werks /cryptofeed_werks/

cryptofeed_werks/constants.py

Lines changed: 0 additions & 27 deletions
This file was deleted.

cryptofeed_werks/feed.py

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,18 +5,20 @@
55

66

77
class Feed(BaseFeed):
8+
"""Feed."""
9+
810
def std_symbol_to_exchange_symbol(self, symbol: str) -> str:
911
"""Standard symbol to exchange symbol.
1012
1113
There are certainly valid reasons to standardize symbols,
12-
but there are also reasons to not care
14+
but there are also reasons to not care.
1315
"""
1416
return symbol
1517

1618
def exchange_symbol_to_std_symbol(self, symbol: str) -> str:
1719
"""Exchange symbol to standard symbol.
1820
19-
Ditto
21+
Ditto.
2022
"""
2123
return symbol
2224

cryptofeed_werks/trades/__init__.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
from .candles import CandleCallback
2+
from .trade_cluster import TradeClusterCallback
23
from .significant_trades import SignificantTradeCallback
34
from .trades import (
45
NonSequentialIntegerTradeCallback,
@@ -16,6 +17,7 @@
1617
"GCPPubSubTradeCallback",
1718
"CandleCallback",
1819
"TradeCallback",
20+
"TradeClusterCallback",
1921
"SignificantTradeCallback",
2022
"SequentialIntegerTradeCallback",
2123
"NonSequentialIntegerTradeCallback",

cryptofeed_werks/trades/candles.py

Lines changed: 10 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -1,28 +1,27 @@
11
from decimal import Decimal
2-
from typing import List, Optional, Tuple
2+
from typing import Callable, List, Optional, Tuple
33

4-
from cryptofeed.backends.aggregate import AggregateCallback
5-
6-
from .constants import NOTIONAL
74
from .window import WindowMixin
85

96

10-
class CandleCallback(WindowMixin, AggregateCallback):
11-
def __init__(
12-
self, *args, window_seconds: int = 60, top_n: Optional[int] = None, **kwargs
13-
) -> None:
14-
super().__init__(*args, **kwargs)
7+
class CandleCallback(WindowMixin):
8+
"""Candle callback."""
9+
10+
def __init__(self, handler: Callable, window_seconds: int = 60) -> None:
11+
"""Init."""
12+
self.handler = handler
1513
self.window_seconds = window_seconds
1614
self.window = {}
17-
self.top_n = top_n
1815
self.trades = {}
1916

2017
async def __call__(self, trade: dict, timestamp: float) -> Tuple[dict, float]:
18+
"""Call."""
2119
candle = self.main(trade)
2220
if candle is not None:
2321
await self.handler(candle, timestamp)
2422

2523
def main(self, trade: dict) -> Optional[dict]:
24+
"""Main."""
2625
symbol = trade["symbol"]
2726
timestamp = trade["timestamp"]
2827
self.trades.setdefault(symbol, [])
@@ -47,7 +46,7 @@ def aggregate(self, trades: List[dict], is_late: bool = False) -> Optional[dict]
4746
"""Aggregate."""
4847
first_trade = trades[0]
4948
prices = self.get_prices(trades)
50-
candle = {
49+
return {
5150
"exchange": first_trade["exchange"],
5251
"symbol": first_trade["symbol"],
5352
"timestamp": self.get_start(first_trade["timestamp"]),
@@ -62,9 +61,6 @@ def aggregate(self, trades: List[dict], is_late: bool = False) -> Optional[dict]
6261
"totalBuyTicks": sum([t["totalBuyTicks"] for t in trades]),
6362
"totalTicks": sum([t["totalTicks"] for t in trades]),
6463
}
65-
if self.top_n:
66-
candle["topN"] = self.get_top_n(trades)
67-
return candle
6864

6965
def get_prices(self, trades: List[dict]) -> List[Decimal]:
7066
"""Get prices."""
@@ -75,22 +71,3 @@ def get_prices(self, trades: List[dict]) -> List[Decimal]:
7571
if value:
7672
prices.append(value)
7773
return prices
78-
79-
def get_top_n(self, trades: List[dict]) -> List[dict]:
80-
"""Get top N."""
81-
filtered = [t for t in trades if "uid" in t]
82-
filtered.sort(key=lambda t: t[NOTIONAL], reverse=True)
83-
top_n = filtered[: self.top_n]
84-
for trade in top_n:
85-
for key in list(trade):
86-
if key not in (
87-
"timestamp",
88-
"price",
89-
"volume",
90-
"notional",
91-
"tickRule",
92-
"ticks",
93-
):
94-
del trade[key]
95-
top_n.sort(key=lambda t: t["timestamp"])
96-
return top_n
Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,3 @@
11
VOLUME = "volume"
22
NOTIONAL = "notional"
33
TICKS = "ticks"
4-
5-
THRESH_ATTRS = (VOLUME, NOTIONAL, TICKS)

cryptofeed_werks/trades/gcppubsub.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,9 +11,12 @@
1111

1212

1313
class GCPPubSubTradeCallback(GCPPubSubCallback):
14+
"""GCP Pub/Sub trade callback."""
15+
1416
default_key = TRADES
1517

1618
def __init__(self, *args, **kwargs) -> None:
19+
"""Initialize."""
1720
super().__init__(*args, **kwargs)
1821
self.topics = {}
1922

@@ -23,6 +26,7 @@ def get_topic(self) -> str:
2326
pass
2427

2528
async def __call__(self, trade: dict, timestamp: float) -> None:
29+
"""Call."""
2630
topic = self.get_topic_path(trade)
2731
message = self.get_message(trade)
2832
await self.write(topic, message)

cryptofeed_werks/trades/significant_trades.py

Lines changed: 9 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,35 +1,37 @@
11
from decimal import Decimal
22
from typing import List, Optional, Tuple
33

4-
from cryptofeed.backends.aggregate import AggregateCallback
5-
64
from .constants import NOTIONAL, TICKS, VOLUME
75
from .window import WindowMixin
86

97

10-
class SignificantTradeCallback(WindowMixin, AggregateCallback):
8+
class SignificantTradeCallback(WindowMixin):
9+
"""Significant trade callback."""
10+
1111
def __init__(
1212
self,
13-
*args,
13+
handler,
1414
significant_trade_filter: int = 1000,
1515
window_seconds: Optional[int] = None,
16-
**kwargs
1716
) -> None:
18-
super().__init__(*args, **kwargs)
17+
"""Initialize."""
18+
self.handler = handler
1919
self.significant_trade_filter = Decimal(significant_trade_filter)
2020
self.trades = {}
2121
self.window_seconds = window_seconds
2222
self.window = {}
2323

2424
async def __call__(self, trade: dict, timestamp: float) -> Tuple[dict, float]:
25+
"""Call."""
2526
result = self.main(trade)
2627
if isinstance(result, list):
2728
for t in result:
2829
await self.handler(t, timestamp)
2930
elif isinstance(result, dict):
3031
await self.handler(result, timestamp)
3132

32-
def main(self, trade: dict) -> dict:
33+
def main(self, trade: dict) -> Optional[dict]:
34+
"""Main."""
3335
symbol = trade["symbol"]
3436
timestamp = trade["timestamp"]
3537
self.trades.setdefault(symbol, [])
Lines changed: 96 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,96 @@
1+
from decimal import Decimal
2+
from statistics import median
3+
from typing import List, Optional, Tuple
4+
5+
from .constants import VOLUME
6+
from .window import WindowMixin
7+
8+
9+
class TradeClusterCallback(WindowMixin):
10+
"""Trade cluter callback."""
11+
12+
def __init__(
13+
self,
14+
handler,
15+
window_seconds: Optional[int] = None,
16+
) -> None:
17+
"""Init."""
18+
self.handler = handler
19+
self.tick_rule = None
20+
self.trades = {}
21+
self.window_seconds = window_seconds
22+
self.window = {}
23+
24+
async def __call__(self, trade: dict, timestamp: float) -> Tuple[dict, float]:
25+
"""Call."""
26+
result = self.main(trade)
27+
if isinstance(result, dict):
28+
await self.handler(result, timestamp)
29+
30+
def main(self, trade: dict) -> Optional[dict]:
31+
"""Main."""
32+
symbol = trade["symbol"]
33+
trades = self.trades.setdefault(symbol, [])
34+
timestamp = trade["timestamp"]
35+
window = self.get_window(symbol, timestamp)
36+
if window is not None:
37+
# Was message received late?
38+
if window["start"] is not None and timestamp < window["start"]:
39+
# FUBAR
40+
return self.aggregate([trade], is_late=True)
41+
# Is window exceeded?
42+
elif window["stop"] is not None and timestamp >= window["stop"]:
43+
ticks = []
44+
# Get tick
45+
tick = self.get_tick(symbol)
46+
if tick is not None:
47+
ticks.append(tick)
48+
# Append trade
49+
self.trades[symbol].append(trade)
50+
# Set window
51+
self.set_window(symbol, timestamp)
52+
# Finally, return ticks
53+
return self.get_tick(symbol, trade)
54+
else:
55+
return self.get_trade_cluster_or_tick(symbol, trade)
56+
57+
def get_trade_cluster_or_tick(self, symbol: str, trade: dict) -> Optional[dict]:
58+
"""Get trade cluster or tick."""
59+
trades = self.trades.setdefault(symbol, [])
60+
is_sequential = True
61+
is_same_direction = True
62+
tick_rule = trade.get("tickRule")
63+
if self.tick_rule:
64+
is_same_direction = self.tick_rule == tick_rule
65+
if is_same_direction:
66+
self.trades[symbol].append(trade)
67+
self.tick_rule = tick_rule
68+
elif len(trades):
69+
tick = self.aggregate(trades)
70+
# Reset
71+
self.trades[symbol] = [trade]
72+
self.tick_rule = tick_rule
73+
return tick
74+
75+
def aggregate(self, trades: List[dict], is_late: bool = False) -> None:
76+
"""Aggregate."""
77+
first_trade = trades[0]
78+
last_trade = trades[-1]
79+
data = {
80+
"exchange": first_trade["exchange"],
81+
"symbol": first_trade["symbol"],
82+
"timestamp": first_trade["timestamp"],
83+
"price": last_trade["price"],
84+
"high": max(t.get("high", t["price"]) for t in trades),
85+
"low": min(t.get("low", t["price"]) for t in trades),
86+
"tickRule": self.tick_rule,
87+
}
88+
volume = ["volume", "totalBuyVolume", "totalVolume"]
89+
notional = ["notional", "totalBuyNotional", "totalNotional"]
90+
ticks = ["ticks", "totalBuyTicks", "totalTicks"]
91+
for sample_type in volume + notional + ticks:
92+
value = sum([t.get(sample_type, 0) for t in trades])
93+
if sample_type in ticks:
94+
value = int(value)
95+
data[sample_type] = value
96+
return data

0 commit comments

Comments
 (0)