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

Refactor #296

Merged
merged 8 commits into from
May 22, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
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]
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

can remove

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
Loading