Skip to content

Commit

Permalink
Merge pull request #296 from tfukaza/refactor
Browse files Browse the repository at this point in the history
Refactor
  • Loading branch information
tfukaza authored May 22, 2024
2 parents a73feed + cbf710a commit 2f4e20d
Show file tree
Hide file tree
Showing 66 changed files with 1,817 additions and 3,272 deletions.
1 change: 1 addition & 0 deletions .github/workflows/run-tests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -44,3 +44,4 @@ jobs:
name: harvest-code-coverage
fail_ci_if_error: true
verbose: true
token: ${{ secrets.CODECOV_TOKEN }}
2 changes: 1 addition & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -27,5 +27,5 @@ package-lock.json
.DS_Store

*.log
env
.env
save
2 changes: 1 addition & 1 deletion .python-version
Original file line number Diff line number Diff line change
@@ -1 +1 @@
3.9.9
3.9.0
2 changes: 2 additions & 0 deletions .trunk/configs/.isort.cfg
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
[settings]
profile=black
3 changes: 3 additions & 0 deletions .vscode/settings.json
Original file line number Diff line number Diff line change
Expand Up @@ -3,4 +3,7 @@
"files.trimTrailingWhitespace": true,
"editor.formatOnSave": true,
"editor.defaultFormatter": "trunk.io",
"cSpell.words": [
"Robinhood"
],
}
24 changes: 12 additions & 12 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
@@ -1,50 +1,50 @@
# Contributing
Thank you for helping out coding Harvest :). Your help is greatly appreciated.
Thank you for helping out coding Harvest :). Your help is greatly appreciated.

## Workflow
The coding process is relatively straight-forward:
1. Choose a task to work on from [open issues](https://github.com/tfukaza/harvest/issues). Alternatively, you can create your own task by [filing a bug report](https://github.com/tfukaza/harvest/issues/new?assignees=&labels=bug&template=bug_report.md&title=%5B%F0%9F%AA%B0BUG%5D) or [submitting a feature suggestion](https://github.com/tfukaza/harvest/issues/new?assignees=&labels=enhancement%2C+question&template=feature-request.md&title=%5B%F0%9F%92%A1Feature+Request%5D).
2. When working on an issue, notify others you are doing so, so other people are aware of who is working on what.
3. Clone the repo, and write your code in your own branch.
4. Run unit tests (as described in a following section).
4. Run unit tests (as described in a following section).
5. Lint your code using [Black](https://github.com/psf/black)
6. Push your code and make a PR to merge your code to main branch. Currently this project requires the approval of at least one contributor to merge the code.
6. Push your code and make a PR to merge your code to main branch. Currently this project requires the approval of at least one contributor to merge the code.

# Developer Guide
Read through the following guides to understand how to properly set up your development environment.
Read through the following guides to understand how to properly set up your development environment.

## Harvest
Harvest requires a Python version of 3.9 or greater, and has a lot of dependencies, so it is highly recommended you use tools like Anaconda or VirtualEnv.

### Installing a Local Build
Run the following in the root of the project directory to install local changes you made.
Run the following in the root of the project directory to install local changes you made.
```bash
pip install .
```
### Unit Testing
After any modifications to the code, conduct unit tests by running:
```bash
python -m unittest discover -s tests
python -m unittest discover -s tests/unittest
```
from the project's root directory. This will run the tests defined in the `tests` directory.

### Real-Time Testing
Unit testing does not cover all possible situations Harvest might encounter. Whenever possible, run the program as if you are a user on your own machine to test the code in real-life environments. This is especially true for codes for specific brokerages, which automated unit tests cannot cover.
Unit testing does not cover all possible situations Harvest might encounter. Whenever possible, run the program as if you are a user on your own machine to test the code in real-life environments. This is especially true for codes for specific brokerages, which automated unit tests cannot cover.

**Make sure you don't accidentally `git push` secret keys of the brokerage you are using.**

## Web Interface
The web interface of Harvest is made with the Svelte framework.
The web interface of Harvest is made with the Svelte framework.

### Running a Dev Server
Move to the `/gui` directory (not `/harvest/gui`) and run:
```bash
npm run dev
```
This will start the dev server. Any edits you make in `/gui/src` will automatically be built and saved to `/harvest/gui`.
This will start the dev server. Any edits you make in `/gui/src` will automatically be built and saved to `/harvest/gui`.

# Coding Practices
We want to make sure our code is stable and reliable - a good way to do that is to write clean, well-documented code.
We want to make sure our code is stable and reliable - a good way to do that is to write clean, well-documented code.

### Linting
This project uses the [Black](https://github.com/psf/black) linter to format the code. Before pushing any code, run the linter on every file you edited. This can usually be done by running:
Expand All @@ -69,7 +69,7 @@ Good logs and debug messages can not only help users, but other developers under
* Log error if an API call failed.
* `raise Exception`: Something really bad happened and the entire system must be shutdown because there is no way to recover. The main difference between raising an exception and logging an error is because if the logged error is not addressed by the user the entire program will still be able to run while raising an exception requires the user to edit their code. For example:
* Errors if a call to a function that should return something without a solid null case. For example returning an empty list is a fine null case but an empty dictionary or None isn't (since no one checks for the None case).
* Errors if the user tried to get a particular stock position from a broker that only supports crypto. The user expects a dictionary but Harvest has no way of providing this.
* Errors if the user tried to get a particular stock position from a broker that only supports crypto. The user expects a dictionary but Harvest has no way of providing this.

### Documenting
Every method, no matter how trivial, should be documented. This project uses the [reST format](https://stackabuse.com/python-docstrings/)
Every method, no matter how trivial, should be documented. This project uses the [reST format](https://stackabuse.com/python-docstrings/)
4 changes: 2 additions & 2 deletions examples/crossover.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# HARVEST_SKIP
from harvest.algo import BaseAlgo
from harvest.trader import LiveTrader
from harvest.trader import BrokerHub


class Crossover(BaseAlgo):
Expand All @@ -21,6 +21,6 @@ def main(self):


if __name__ == "__main__":
t = LiveTrader()
t = BrokerHub()
t.set_algo(Crossover())
t.start()
8 changes: 4 additions & 4 deletions examples/crypto.py
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
# HARVEST_SKIP
from harvest.algo import BaseAlgo
from harvest.trader import LiveTrader
from harvest.api.robinhood import Robinhood
from harvest.api.paper import PaperBroker
from harvest.broker.paper import PaperBroker
from harvest.broker.robinhood import RobinhoodBroker
from harvest.trader import BrokerHub

"""This algorithm trades Dogecoin.
It also demonstrates some built-in functions.
Expand Down Expand Up @@ -100,6 +100,6 @@ def sell_eval(self, ret):


if __name__ == "__main__":
t = LiveTrader(Robinhood(), PaperBroker())
t = BrokerHub(RobinhoodBroker(), PaperBroker())
t.set_algo(Crypto())
t.start()
27 changes: 12 additions & 15 deletions examples/em_alpaca.py
Original file line number Diff line number Diff line change
@@ -1,17 +1,18 @@
# HARVEST_SKIP
# Builtin imports
import logging
import datetime as dt
import logging

# Harvest imports
from harvest.algo import BaseAlgo
from harvest.trader import LiveTrader
from harvest.api.alpaca import Alpaca
from harvest.storage.csv_storage import CSVStorage
import mplfinance as mpf

# Third-party imports
import pandas as pd
import mplfinance as mpf

# Harvest imports
from harvest.algo import BaseAlgo
from harvest.broker.alpaca import AlpacaBroker
from harvest.storage.csv_storage import CSVStorage
from harvest.trader import BrokerHub


class EMAlgo(BaseAlgo):
Expand All @@ -30,10 +31,8 @@ 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!")
if now - now.replace(hour=0, minute=0, second=0, microsecond=0) <= dt.timedelta(seconds=60):
logging.info("It's a new day! Cleaning OHLC caches!")
for ticker_value in self.tickers.values():
ticker_value["ohlc"] = pd.DataFrame()

Expand Down Expand Up @@ -66,11 +65,9 @@ def process_ticker(self, ticker, ticker_data, current_price, current_ohlc):
# 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`
alpaca = Alpaca(
path="accounts/alpaca-secret.yaml", is_basic_account=True, paper_trader=True
)
alpaca = AlpacaBroker(path="accounts/alpaca-secret.yaml", is_basic_account=True, paper_trader=True)
em_algo = EMAlgo()
trader = LiveTrader(streamer=alpaca, broker=alpaca, storage=csv_storage, debug=True)
trader = BrokerHub(streamer=alpaca, broker=alpaca, storage=csv_storage, debug=True)

# Watch for Apple and Microsoft
trader.set_symbol("AAPL")
Expand Down
101 changes: 0 additions & 101 deletions examples/em_kraken.py

This file was deleted.

35 changes: 15 additions & 20 deletions examples/em_polygon.py
Original file line number Diff line number Diff line change
@@ -1,19 +1,20 @@
# HARVEST_SKIP
# Builtin imports
import logging
import datetime as dt
import logging

# Harvest imports
from harvest.algo import BaseAlgo
from harvest.trader import LiveTrader
from harvest.api.polygon import PolygonStreamer
from harvest.api.paper import PaperBroker
from harvest.storage.csv_storage import CSVStorage
import matplotlib.pyplot as plt
import mplfinance as mpf

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

# Harvest imports
from harvest.algo import BaseAlgo
from harvest.broker.paper import PaperBroker
from harvest.broker.polygon import PolygonBroker
from harvest.storage.csv_storage import CSVStorage
from harvest.trader import BrokerHub


class EMAlgo(BaseAlgo):
Expand All @@ -37,10 +38,8 @@ def main(self):
logging.info("*" * 20)
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!")
if now - now.replace(hour=0, minute=0, second=0, microsecond=0) <= dt.timedelta(seconds=60):
logging.info("It's a new day! Cleaning OHLC caches!")
for ticker_value in self.tickers.values():
ticker_value["ohlc"] = pd.DataFrame(
columns=["open", "high", "low", "close", "volume"],
Expand All @@ -54,9 +53,7 @@ def main(self):
logging.warn("No ohlc returned!")
return
ticker_value["ohlc"] = ticker_value["ohlc"].append(current_ohlc)
ticker_value["ohlc"] = ticker_value["ohlc"][
~ticker_value["ohlc"].index.duplicated(keep="first")
]
ticker_value["ohlc"] = ticker_value["ohlc"][~ticker_value["ohlc"].index.duplicated(keep="first")]

if ticker_value["initial_price"] is None:
ticker_value["initial_price"] = current_price
Expand Down Expand Up @@ -90,12 +87,10 @@ def process_ticker(self, ticker, ticker_data, current_price):
# Store the OHLC data in a folder called `em_storage` with each file stored as a csv document
csv_storage = CSVStorage(save_dir="em-polygon-storage")
# Our streamer will be Polygon and the broker will be Harvest's paper trader. My secret keys are stored in `polygon-secret.yaml`
polygon = PolygonStreamer(
path="accounts/polygon-secret.yaml", is_basic_account=True
)
polygon = PolygonBroker(path="accounts/polygon-secret.yaml", is_basic_account=True)
paper = PaperBroker()
em_algo = EMAlgo()
trader = LiveTrader(streamer=polygon, broker=paper, storage=csv_storage, debug=True)
trader = BrokerHub(streamer=polygon, broker=paper, storage=csv_storage, debug=True)

trader.set_algo(em_algo)

Expand Down
13 changes: 5 additions & 8 deletions examples/options.py
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
# HARVEST_SKIP
import datetime as dt

from harvest.algo import BaseAlgo
from harvest.broker.robinhood import RobinhoodBroker
from harvest.trader import Trader
from harvest.api.robinhood import Robinhood

import datetime as dt

"""This algorithm trades options every 5 minutes.
To keep things simple, the logic is very basic, with emphasis on
Expand All @@ -22,7 +22,6 @@ def setup(self):
self.buy_qty = 0

def main(self):

price = self.get_asset_price()

if not self.hold:
Expand All @@ -40,9 +39,7 @@ def eval_buy(self, price):
# Sort so the earliest expiration date is first
dates.sort()
# Filter out expiration dates that within 5 days (since they are VERY risky)
dates = filter(
lambda x: x > self.timestamp.date() + dt.timedelta(days=5), dates
)
dates = filter(lambda x: x > self.timestamp.date() + dt.timedelta(days=5), dates)
# Get the option chain
chain = self.get_option_chain("TWTR", dates[0])
# Strike price should be greater than current price
Expand All @@ -67,7 +64,7 @@ def eval_buy(self, price):


if __name__ == "__main__":
t = Trader(Robinhood())
t = Trader(RobinhoodBroker())
t.set_algo(Option())

t.start()
Loading

0 comments on commit 2f4e20d

Please sign in to comment.