Skip to content

Commit

Permalink
Improved comments
Browse files Browse the repository at this point in the history
  • Loading branch information
jbaron committed Feb 21, 2024
1 parent aba3970 commit 19528e9
Show file tree
Hide file tree
Showing 14 changed files with 52 additions and 41 deletions.
4 changes: 2 additions & 2 deletions .vscode/settings.json
Original file line number Diff line number Diff line change
Expand Up @@ -12,12 +12,12 @@
"Tiingo",
"timeframes"
],
"python.testing.unittestArgs": [
"__python.testing.unittestArgs": [
"-v",
"-p",
"test_*.py"
],
"__python.testing.unittestArgs": [
"python.testing.unittestArgs": [
"-v",
"-s",
"./tests/unit",
Expand Down
3 changes: 2 additions & 1 deletion roboquant/feeds/csvfeed.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@


class CSVFeed(HistoricFeed):
"""Parse CSV files with historic data"""
"""Use CSV files with historic data as a feed."""

def __init__(
self,
Expand Down Expand Up @@ -50,6 +50,7 @@ def _get_files(self, path):
return files

def _get_symbol(self, filename: str):
"""Return the symbol based on the filename"""
return pathlib.Path(filename).stem.upper()

def _parse_csvfile(self, filename: str):
Expand Down
2 changes: 1 addition & 1 deletion roboquant/feeds/randomwalk.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@


class RandomWalk(HistoricFeed):
"""Randomwalk of stock prices"""
"""This feed simulates the random-walk of stock prices."""

def __init__(
self,
Expand Down
12 changes: 6 additions & 6 deletions roboquant/feeds/tiingolivefeed.py
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ def __init__(self, key: str | None = None, market: Literal["crypto", "iex", "fx"
wst.daemon = True
wst.start()
time.sleep(3)
self.last_time = datetime.fromisoformat("1900-01-01T00:00:00+00:00")
self._last_time = datetime.fromisoformat("1900-01-01T00:00:00+00:00")

def play(self, channel: EventChannel):
self.channel = channel
Expand All @@ -50,10 +50,10 @@ def _deliver(self, price, now: datetime):
t = now.astimezone(timezone.utc)

# required for crypto times
if t < self.last_time:
t = self.last_time
if t < self._last_time:
t = self._last_time
else:
self.last_time = t
self._last_time = t

event = Event(t, [price])
self.channel.put(event)
Expand Down Expand Up @@ -112,7 +112,7 @@ def _handle_error(self, ws, msg):
logger.error(msg)

def _handle_close(self, ws, close_status_code, close_msg):
logger.info(f"Webchannel closed code={close_status_code} msg={close_msg}")
logger.info("Webchannel closed code=%s msg=%s", close_status_code, close_msg)

def subscribe(self, *symbols: str, threshold_level=5):
logger.info("Subscribing to %s", symbols or "all symbols")
Expand All @@ -125,5 +125,5 @@ def subscribe(self, *symbols: str, threshold_level=5):
self.ws.send(json_str)

def close(self):
self.last_time = datetime.fromisoformat("1900-01-01").astimezone(timezone.utc)
self._last_time = datetime.fromisoformat("1900-01-01").astimezone(timezone.utc)
self.ws.close()
2 changes: 1 addition & 1 deletion roboquant/feeds/yahoofeed.py
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ def __init__(self, *symbols: str, start_date="2010-01-01", end_date: str | None
df.dropna(inplace=True)

if len(df) == 0:
logger.warning(f"no data retrieved for symbol={symbol}")
logger.warning("no data retrieved for symbol=%s", symbol)
continue

# yFinance one doesn't correct volume, so use this one instead
Expand Down
20 changes: 14 additions & 6 deletions roboquant/signal.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,8 @@


class SignalType(Flag):
"""Type of signal, either EXTRY, EXIT or BOTH"""
"""Type of signal, either ENTRY, EXIT or BOTH"""

ENTRY = auto()
EXIT = auto()
BOTH = ENTRY | EXIT
Expand All @@ -17,20 +18,27 @@ class Signal:
type: SignalType = SignalType.BOTH

@staticmethod
def BUY(signal_type=SignalType.BOTH):
def buy(signal_type=SignalType.BOTH):
"""Create a BUY signal with rating of 1.0"""
return Signal(1.0, signal_type)

@staticmethod
def SELL(signal_type=SignalType.BOTH):
def sell(signal_type=SignalType.BOTH):
"""Create a SELL signal with rating of -1.0"""
return Signal(-1.0, signal_type)

@property
def is_buy(self):
return self.rating > 0.0

@property
def is_sell(self):
return self.rating < 0.0

@property
def is_entry(self):
return SignalType.ENTRY in self.type

if __name__ == "__main__":
s = Signal.BUY()
print(s)
@property
def is_exit(self):
return SignalType.EXIT in self.type
3 changes: 1 addition & 2 deletions roboquant/strategies/buffer.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@

class NumpyBuffer:
"""A FIFO (first-in-first-out) buffer of a fixed capacity.
It uses a single Numpy array to store the data.
It uses a single Numpy array to store its data.
"""

__slots__ = "_data", "_idx", "capacity"
Expand Down Expand Up @@ -50,7 +50,6 @@ def reset(self):

class OHLCVBuffer(NumpyBuffer):
"""A OHLCV buffer (first-in-first-out) of a fixed capacity.
It uses NumpyBuffer to store the data.
"""

def __init__(self, capacity: int, dtype="float64") -> None:
Expand Down
10 changes: 5 additions & 5 deletions roboquant/strategies/emacrossover.py
Original file line number Diff line number Diff line change
Expand Up @@ -26,11 +26,11 @@ def create_signals(self, event: Event) -> dict[str, Signal]:
elif not calculator.step >= self.min_steps:
calculator.add_price(price)
else:
old_direction = calculator.get_direction()
old_rating = calculator.get_rating()
calculator.add_price(price)
new_direction = calculator.get_direction()
if old_direction != new_direction:
signals[symbol] = Signal(new_direction)
new_rating = calculator.get_rating()
if old_rating != new_rating:
signals[symbol] = Signal(new_rating)

return signals

Expand All @@ -54,5 +54,5 @@ def add_price(self, price: float):
self.emaSlow = self.emaSlow * slow + (1.0 - slow) * price
self.step += 1

def get_direction(self) -> float:
def get_rating(self) -> float:
return 1.0 if self.emaFast > self.emaSlow else -1.0
12 changes: 6 additions & 6 deletions roboquant/strategies/smacrossover.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ class SMACrossover(Strategy):
def __init__(self, min_period: int = 13, max_period: int = 26):
super().__init__()
self._history: dict[str, collections.deque] = {}
self._last_rating: dict[str, bool] = {}
self._prev_ratings: dict[str, bool] = {}
self.min_period = min_period
self.max_period = max_period

Expand All @@ -21,12 +21,12 @@ def _check_condition(self, symbol: str) -> None | Signal:
# SMA(MIN) > SMA(MAX)
new_rating: bool = prices[-self.min_period:].mean() > prices[-self.max_period:].mean()
result = None
if symbol in self._last_rating:
last_rating = self._last_rating[symbol]
if last_rating != new_rating:
result = Signal.BUY() if last_rating else Signal.SELL()
if symbol in self._prev_ratings:
prev_rating = self._prev_ratings[symbol]
if prev_rating != new_rating:
result = Signal.buy() if new_rating else Signal.sell()

self._last_rating[symbol] = new_rating
self._prev_ratings[symbol] = new_rating
return result

def create_signals(self, event: Event) -> dict[str, Signal]:
Expand Down
7 changes: 5 additions & 2 deletions roboquant/strategies/strategy.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,13 @@


class Strategy(Protocol):
"""A strategy creates signals based on incoming events and the items these events contain."""
"""A strategy creates signals based on incoming events and the items these events contain.
Often these items represent market data, but other type of items are also possible.
"""

def create_signals(self, event: Event) -> dict[str, Signal]:
"""Create a signal for zero or more symbols. Signals are returned as a dictionary with key being the symbol name and
"""Create a signal for zero or more symbols. Signals are returned as a dictionary with key being the symbol and
the value being the Signal.
"""
...
4 changes: 2 additions & 2 deletions tests/common.py
Original file line number Diff line number Diff line change
Expand Up @@ -33,14 +33,14 @@ def run_priceaction_feed(feed: Feed, symbols: list[str], testCase: TestCase, tim
play_background(feed, channel)

last = None
while event := channel.get(10.0):
while event := channel.get(30.0):

testCase.assertIsInstance(event.time, datetime)
testCase.assertEqual("UTC", event.time.tzname())

if last is not None:
# testCase.assertLessEqual(event.time - last, timedelta(minutes=1))
testCase.assertGreaterEqual(event.time, last)
testCase.assertGreaterEqual(event.time, last, f"{event} < {last}, items={event.items}")

last = event.time

Expand Down
2 changes: 1 addition & 1 deletion tests/performance/profiling_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
from roboquant import Roboquant, CSVFeed, BasicTracker, EMACrossover

if __name__ == "__main__":
path = os.path.expanduser("~/data/stooq_old/daily/us/nasdaq stocks/1")
path = os.path.expanduser("~/data/nasdaq_stocks/1")
feed = CSVFeed.stooq_us_daily(path)
print("timeframe =", feed.timeframe(), " symbols =", len(feed.symbols))
rq = Roboquant(EMACrossover(13, 26))
Expand Down
8 changes: 4 additions & 4 deletions tests/performance/tiingodelay_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,16 +10,16 @@
import logging
import time

from roboquant import Config, EventChannel, Timeframe, TiingoLiveFeed, feedutil
from roboquant import EventChannel, Timeframe, TiingoLiveFeed, feedutil
from statistics import mean, stdev

if __name__ == "__main__":
config = Config()
key = config.get("tiingo.key")

logging.basicConfig(level=logging.INFO)

feed = TiingoLiveFeed(key, "iex")
feed = TiingoLiveFeed(market="iex")

# subscribe to all IEX stocks for TOP of order book changes and Trades.
feed.subscribe(threshold_level=5)

timeframe = Timeframe.next(minutes=1)
Expand Down
4 changes: 2 additions & 2 deletions tests/unit/test_candlestrategy.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,9 +12,9 @@ def _create_signal(self, _, ohlcv: OHLCVBuffer) -> Signal | None:
sma12 = close[-12:].mean()
sma26 = close[-26:].mean() # type: ignore
if sma12 > sma26:
return Signal.BUY()
return Signal.buy()
if sma12 < sma26:
return Signal.SELL()
return Signal.sell()


class TestCandleStrategy(unittest.TestCase):
Expand Down

0 comments on commit 19528e9

Please sign in to comment.