Skip to content

Commit

Permalink
Merge pull request #612 from Lumiwealth/dev
Browse files Browse the repository at this point in the history
Update docs
  • Loading branch information
grzesir authored Nov 5, 2024
2 parents 44f5ad8 + 899df67 commit 974a8f0
Show file tree
Hide file tree
Showing 11 changed files with 870 additions and 40 deletions.
7 changes: 6 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -178,13 +178,18 @@ git commit -m "my changes"
git push -u origin my-feature
```

If work on main progressed while you were in another branch, this is how you rebase it into your branch:
If work on main progressed while you were in another branch, this is how you rebase it into your branch. Note that
since you've rebased your local branch, you'll need to force push your changes to update the remote branch.
The --force-with-lease option is a safer alternative to --force as it will abort the push if there are any new
commits on the remote that you haven't incorporated into your local branch
```shell
git checkout dev
git fetch origin
git merge origin/dev
git checkout my-feature
git rebase dev
git checkout my-feature
git push --force-with-lease origin my-feature
```

When ready to merge the branch into main, go into github, create a pull request, and await review. When your PR is approved it will automatically be merged into the dev branch remotely. Now, you can delete your local branch and the remote branch.
Expand Down
4 changes: 4 additions & 0 deletions lumibot/brokers/interactive_brokers_rest.py
Original file line number Diff line number Diff line change
Expand Up @@ -399,8 +399,11 @@ def _pull_positions(self, strategy) -> list[Position]:
for position in positions:
# Create the Asset object for the position
symbol = position["contractDesc"]
if symbol.startswith("C"):
symbol = symbol[1:].replace(" ", "")
asset_class = ASSET_CLASS_MAPPING[position["assetClass"]]


# If asset class is stock, create a stock asset
if asset_class == Asset.AssetType.STOCK:
asset = Asset(symbol=symbol, asset_type=asset_class)
Expand All @@ -417,6 +420,7 @@ def _pull_positions(self, strategy) -> list[Position]:
right=right,
)
elif asset_class == Asset.AssetType.FUTURE:
#contract_details = self.data_source.get_contract_details(position['conid'])
expiry = position["expiry"]
multiplier = position["multiplier"]
asset = Asset(
Expand Down
56 changes: 28 additions & 28 deletions lumibot/data_sources/interactive_brokers_rest_data.py
Original file line number Diff line number Diff line change
Expand Up @@ -40,8 +40,6 @@ def __init__(self, config):
self.base_url = f"{self.api_url}/v1/api"

self.account_id = config["ACCOUNT_ID"] if "ACCOUNT_ID" in config else None
self.ib_username = config["IB_USERNAME"]
self.ib_password = config["IB_PASSWORD"]

# Check if we are running on a server
running_on_server = (
Expand All @@ -54,9 +52,9 @@ def __init__(self, config):
else:
self.running_on_server = False

self.start()
self.start(config["IB_USERNAME"], config["IB_PASSWORD"])

def start(self):
def start(self, ib_username, ib_password):
if not self.running_on_server:
# Run the Docker image with the specified environment variables and port mapping
if (
Expand All @@ -76,8 +74,8 @@ def start(self):

inputs_dir = "/srv/clientportal.gw/root/conf.yaml"
env_variables = {
"IBEAM_ACCOUNT": self.ib_username,
"IBEAM_PASSWORD": self.ib_password,
"IBEAM_ACCOUNT": ib_username,
"IBEAM_PASSWORD": ib_password,
"IBEAM_GATEWAY_BASE_URL": f"https://localhost:{self.port}",
"IBEAM_LOG_TO_FILE": False,
"IBEAM_REQUEST_RETRIES": 1,
Expand Down Expand Up @@ -466,21 +464,22 @@ def post_to_endpoint(self, url, json: dict, allow_fail=True):
return self.post_to_endpoint(url, json, allow_fail=allow_fail)

else:
if "error" in response.json():
logging.error(
colored(
f"Task '{url}' Failed. Error: {response.json()['error']}",
"red",
if allow_fail:
if "error" in response.json():
logging.error(
colored(
f"Task '{url}' Failed. Error: {response.json()['error']}",
"red",
)
)
)
else:
logging.error(
colored(
f"Task '{url}' Failed. Status code: {response.status_code}, "
f"Response: {response.text}",
"red",
else:
logging.error(
colored(
f"Task '{url}' Failed. Status code: {response.status_code}, "
f"Response: {response.text}",
"red",
)
)
)
to_return = None

except requests.exceptions.RequestException as e:
Expand Down Expand Up @@ -528,12 +527,13 @@ def delete_to_endpoint(self, url, allow_fail=True):
return self.delete_to_endpoint(url)

else:
logging.error(
colored(
f"Task '{url}' Failed. Status code: {response.status_code}, Response: {response.text}",
"red",
if allow_fail:
logging.error(
colored(
f"Task '{url}' Failed. Status code: {response.status_code}, Response: {response.text}",
"red",
)
)
)
to_return = None

except requests.exceptions.RequestException as e:
Expand Down Expand Up @@ -958,18 +958,18 @@ def get_historical_prices(

return bars

def get_last_price(self, asset, quote=None, exchange=None) -> float:
def get_last_price(self, asset, quote=None, exchange=None) -> float | None:
field = "last_price"
response = self.get_market_snapshot(asset, [field]) # TODO add exchange

if response is None or field not in response:
if asset.asset_type in ["option", "future"]:
logging.error(
logging.debug(
f"Failed to get {field} for asset {asset.symbol} with strike {asset.strike} and expiration date {asset.expiration}"
)
else:
logging.error(f"Failed to get {field} for asset {asset.symbol} of type {asset.asset_type}")
return -1
logging.debug(f"Failed to get {field} for asset {asset.symbol} of type {asset.asset_type}")
return None

price = response[field]

Expand Down
6 changes: 5 additions & 1 deletion lumibot/entities/data.py
Original file line number Diff line number Diff line change
Expand Up @@ -570,6 +570,10 @@ def get_bars(self, dt, length=1, timestep=MIN_TIMESTEP, timeshift=0):
unit = "D"
data = self._get_bars_dict(dt, length=length, timestep="minute", timeshift=timeshift)

elif timestep == 'day' and self.timestep == 'day':
unit = "D"
data = self._get_bars_dict(dt, length=length, timestep=timestep, timeshift=timeshift)

else:
unit = "min" # Guaranteed to be minute timestep at this point
length = length * quantity
Expand All @@ -587,7 +591,7 @@ def get_bars(self, dt, length=1, timestep=MIN_TIMESTEP, timeshift=0):
df_result = df_result.dropna()

# Remove partial day data from the current day, which can happen if the data is in minute timestep.
if timestep == "day":
if timestep == "day" and self.timestep == "minute":
df_result = df_result[df_result.index < dt.replace(hour=0, minute=0, second=0, microsecond=0)]

# The original df_result may include more rows when timestep is day and self.timestep is minute.
Expand Down
60 changes: 60 additions & 0 deletions lumibot/example_strategies/classic_60_40.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
from datetime import datetime

from lumibot.strategies.drift_rebalancer import DriftRebalancer

"""
Strategy Description
This strategy rebalances a portfolio of assets to a target weight every time the asset drifts
by a certain threshold. The strategy will sell the assets that has drifted the most and buy the
assets that has drifted the least to bring the portfolio back to the target weights.
"""


if __name__ == "__main__":
is_live = False

parameters = {
"market": "NYSE",
"sleeptime": "1D",
"absolute_drift_threshold": "0.15",
"acceptable_slippage": "0.0005",
"fill_sleeptime": 15,
"target_weights": {
"SPY": "0.60",
"TLT": "0.40"
}
}

if is_live:
from credentials import ALPACA_CONFIG
from lumibot.brokers import Alpaca
from lumibot.traders import Trader

trader = Trader()
broker = Alpaca(ALPACA_CONFIG)
strategy = DriftRebalancer(broker=broker, parameters=parameters)
trader.add_strategy(strategy)
strategy_executors = trader.run_all()

else:
from lumibot.backtesting import YahooDataBacktesting
backtesting_start = datetime(2023, 1, 2)
backtesting_end = datetime(2024, 10, 31)

results = DriftRebalancer.backtest(
YahooDataBacktesting,
backtesting_start,
backtesting_end,
benchmark_asset="SPY",
parameters=parameters,
show_plot=False,
show_tearsheet=False,
save_tearsheet=False,
show_indicators=False,
save_logfile=False,
# show_progress_bar=False,
# quiet_logs=False
)

print(results)
Loading

0 comments on commit 974a8f0

Please sign in to comment.