Skip to content

Commit

Permalink
Merge branch 'main' of github.com:tfukaza/harvest
Browse files Browse the repository at this point in the history
  • Loading branch information
tfukaza committed Nov 15, 2021
2 parents 6010130 + c47049c commit add50a6
Show file tree
Hide file tree
Showing 36 changed files with 578 additions and 372 deletions.
5 changes: 4 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
![Header](docs/banner.png)<br />
Harvest is a Python framework providing a **simple** and **flexible** framework for algorithmic trading. Visit Harvest's [**website**](https://tfukaza.github.io/harvest-website/) for details, tutorials, and documentation.
Harvest is a simple yet flexible Python framework for algorithmic trading. Paper trade and live trade stocks, cryptos, and options![^1][^2] Visit Harvest's [**website**](https://tfukaza.github.io/harvest-website/) for details, tutorials, and documentation.

<br />

Expand Down Expand Up @@ -77,3 +77,6 @@ Currently looking for...
- Many of the brokers were also not designed to be used for algo-trading. Excessive access to their API can result in your account getting locked.
- Tutorials and documentation solely exist to provide technical references of the code. They are not recommendations of any specific securities or strategies.
- Use Harvest at your own responsibility. Developers of Harvest take no responsibility for any financial losses you incur by using Harvest. By using Harvest, you certify you understand that Harvest is a software in early development and may contain bugs and unexpected behaviors.

[^1]: What assets you can trade depends on the broker you are using.
[^2]: Backtesting is also available, but it is not supported for options.
25 changes: 20 additions & 5 deletions docs/dev.md
Original file line number Diff line number Diff line change
@@ -1,7 +1,22 @@
# Overview
## The Harvest Workflow

### Harvest architecture
![Harvest architecture](harvest_architecture.png)
Because Harvest is an extensive framework it can be hard to understand how the system works at times. This document server to provide a high-level overview of just how Harvests works after a user starts the trader.

### Harvest Flow on Trader `start()`
![Harvest start flow](harvest_start_flow.png)
## Fetching Data

After the user starts the trader Harvest will fetch data from the streamer and update its storage on interval.

![Fetching Data Workflow](fetch-data.png)

1. First Harvest will run the streamer on the specified interval. Once the data has been collected, the streamer will call a callback or hook function that will pass operation back to the trader. In this callback function the streamer will return the latest OHLC data for the assets specified by the trader.
2. In this callback, the trader will update the storage with the latest data and the will run each algorithm.

## Running Algorithms

After data is fetched, the algorithms are run linearly.

![Running Algorithm Workflow](run-algo.png)

1. The algorithm the user created will user functions provided in the `BaseAlgo` class which communicate with the Trader.
2. Typically the user's algorithms will first ask for data on the assets they specified which will be stored in the Storage.
3. After that the user's algoirthms will decided when to buy or sell assets based on the data they got from the Storage. This will leverage the Broker.
Binary file added docs/fetch-data.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file removed docs/harvest_architecture.png
Binary file not shown.
Binary file removed docs/harvest_start_flow.png
Binary file not shown.
Binary file added docs/run-algo.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
1 change: 1 addition & 0 deletions examples/em_alpaca.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
# HARVEST_SKIP
# Builtin imports
import logging
import datetime as dt
Expand Down
100 changes: 100 additions & 0 deletions examples/em_kraken.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
# HARVEST_SKIP
# Builtin imports
import logging
import datetime as dt

# Harvest imports
from harvest.algo import BaseAlgo
from harvest.trader import LiveTrader
from harvest.api.kraken import Kraken
from harvest.storage.csv_storage import CSVStorage

# Third-party imports
import pandas as pd
import matplotlib.pyplot as plt
import mplfinance as mpf


class EMAlgo(BaseAlgo):
def setup(self):
now = dt.datetime.now()
logging.info(f"EMAlgo.setup ran at: {now}")

def init_ticker(ticker):
fig = mpf.figure()
ax1 = fig.add_subplot(2, 1, 1)
ax2 = fig.add_subplot(3, 1, 3)

return {
ticker: {
"initial_price": None, "ohlc": pd.DataFrame(),
"fig": fig,
"ax1": ax1,
"ax2": ax2
}
}

self.tickers = {}
# self.tickers.update(init_ticker("@BTC"))
self.tickers.update(init_ticker("@DOGE"))

def main(self):
now = dt.datetime.now()
logging.info(f"EMAlgo.main ran at: {now}")

if now - now.replace(hour=0, minute=0, second=0, microsecond=0) <= dt.timedelta(
seconds=60
):
logger.info(f"It's a new day! Clearning OHLC caches!")
for ticker_value in self.tickers.values():
ticker_value["ohlc"] = pd.DataFrame()

for ticker, ticker_value in self.tickers.items():
current_price = self.get_asset_price(ticker)
current_ohlc = self.get_asset_candle(ticker)
if ticker_value["initial_price"] is None:
ticker_value["initial_price"] = current_price

if current_ohlc.empty:
logging.warn(f"{ticker}'s get_asset_candle_list returned an empty list.")
return

ticker_value["ohlc"] = ticker_value["ohlc"].append(current_ohlc)

self.process_ticker(ticker, ticker_value, current_price)

def process_ticker(self, ticker, ticker_data, current_price):
initial_price = ticker_data["initial_price"]
ohlc = ticker_data["ohlc"]

# Calculate the price change
delta_price = current_price - initial_price

# Print stock info
logging.info(f"{ticker} current price: ${current_price}")
logging.info(f"{ticker} price change: ${delta_price}")

# Update the OHLC graph
ticker_data['ax1'].clear()
ticker_data['ax2'].clear()
mpf.plot(ohlc, ax=ticker_data['ax1'], volume=ticker_data['ax2'], type="candle")
plt.pause(3)


if __name__ == "__main__":
# Store the OHLC data in a folder called `em_storage` with each file stored as a csv document
csv_storage = CSVStorage(save_dir="em_storage")
# Our streamer and broker will be Alpaca. My secret keys are stored in `alpaca_secret.yaml`
kraken = Kraken(
path="accounts/kraken-secret.yaml"
)
em_algo = EMAlgo()
trader = LiveTrader(streamer=kraken, broker=kraken, storage=csv_storage, debug=True)

# trader.set_symbol("@BTC")
trader.set_symbol("@DOGE")
trader.set_algo(em_algo)
mpf.show()

# Update every minute
trader.start("1MIN", all_history=False)
1 change: 1 addition & 0 deletions examples/em_polygon.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
# HARVEST_SKIP
# Builtin imports
import logging
import datetime as dt
Expand Down
13 changes: 7 additions & 6 deletions harvest/algo.py
Original file line number Diff line number Diff line change
Expand Up @@ -141,7 +141,7 @@ def sell(

debugger.debug(f"Algo SELL: {symbol}, {quantity}")
return self.trader.sell(symbol, quantity, in_force, extended)

def sell_all_options(self, symbol: str = None, in_force: str = "gtc"):
"""Sells all options of a stock
Expand All @@ -163,8 +163,8 @@ def sell_all_options(self, symbol: str = None, in_force: str = "gtc"):
for s in symbols:
debugger.debug(f"Algo SELL OPTION: {s}")
quantity = self.get_asset_quantity(s)
ret.append(self.trader.sell_option(s, quantity, in_force))
ret.append(self.trader.sell(s, quantity, in_force, True))

return ret

# def buy_option(self, symbol: str, quantity: int = None, in_force: str = "gtc"):
Expand Down Expand Up @@ -522,9 +522,8 @@ def get_asset_quantity(self, symbol: str = None) -> float:
"""
if symbol is None:
symbol = self.watchlist[0]

return self.trader.get_asset_quantity(symbol, exclude_pending_sell=True)

return self.trader.get_asset_quantity(symbol, exclude_pending_sell=True)

def get_asset_cost(self, symbol: str = None) -> float:
"""Returns the average cost of a specified asset.
Expand Down Expand Up @@ -771,7 +770,9 @@ def get_datetime(self):
:returns: The current date and time as a datetime object
"""
return datetime_utc_to_local(self.trader.timestamp, self.trader.timezone)
return datetime_utc_to_local(
self.trader.streamer.timestamp, self.trader.timezone
)

def get_option_position_quantity(self, symbol: str = None) -> bool:
"""Returns the number of types of options held for a stock.
Expand Down
Loading

0 comments on commit add50a6

Please sign in to comment.