Skip to content

Commit

Permalink
Implementation of the starter system from Leveraged Trading by Rob Ca…
Browse files Browse the repository at this point in the history
…rver
  • Loading branch information
markns committed Aug 24, 2022
0 parents commit ec51c55
Show file tree
Hide file tree
Showing 28 changed files with 5,399 additions and 0 deletions.
Binary file added .DS_Store
Binary file not shown.
144 changes: 144 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,144 @@
# Quant connect
data/
starter_system/backtests

### Python template
# Byte-compiled / optimized / DLL files
__pycache__/
*.py[cod]
*$py.class

# C extensions
*.so

# Distribution / packaging
.Python
build/
develop-eggs/
dist/
downloads/
eggs/
.eggs/
lib/
lib64/
parts/
sdist/
var/
wheels/
share/python-wheels/
*.egg-info/
.installed.cfg
*.egg
MANIFEST

# PyInstaller
# Usually these files are written by a python script from a template
# before PyInstaller builds the exe, so as to inject date/other infos into it.
*.manifest
*.spec

# Installer logs
pip-log.txt
pip-delete-this-directory.txt

# Unit test / coverage reports
htmlcov/
.tox/
.nox/
.coverage
.coverage.*
.cache
nosetests.xml
coverage.xml
*.cover
*.py,cover
.hypothesis/
.pytest_cache/
cover/

# Translations
*.mo
*.pot

# Django stuff:
*.log
local_settings.py
db.sqlite3
db.sqlite3-journal

# Flask stuff:
instance/
.webassets-cache

# Scrapy stuff:
.scrapy

# Sphinx documentation
docs/_build/

# PyBuilder
.pybuilder/
target/

# Jupyter Notebook
.ipynb_checkpoints

# IPython
profile_default/
ipython_config.py

# pyenv
# For a library or package, you might want to ignore these files since the code is
# intended to run in multiple environments; otherwise, check them in:
# .python-version

# pipenv
# According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control.
# However, in case of collaboration, if having platform-specific dependencies or dependencies
# having no cross-platform support, pipenv may install dependencies that don't work, or not
# install all needed dependencies.
#Pipfile.lock

# PEP 582; used by e.g. github.com/David-OConnor/pyflow
__pypackages__/

# Celery stuff
celerybeat-schedule
celerybeat.pid

# SageMath parsed files
*.sage.py

# Environments
.env
.venv
env/
venv/
ENV/
env.bak/
venv.bak/

# Spyder project settings
.spyderproject
.spyproject

# Rope project settings
.ropeproject

# mkdocs documentation
/site

# mypy
.mypy_cache/
.dmypy.json
dmypy.json

# Pyre type checker
.pyre/

# pytype static type analyzer
.pytype/

# Cython debug symbols
cython_debug/

Empty file added Library/acorn/__init__.py
Empty file.
6 changes: 6 additions & 0 deletions Library/acorn/constants.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
import math

CALENDAR_DAYS_IN_YEAR = 365.25
BUSINESS_DAYS_IN_YEAR = 256
# Assume 256 business days in a year. Assume no returns are iid. Therefore, we can divide by sqrt(256)=16.
ROOT_BDAYS_INYEAR = math.sqrt(BUSINESS_DAYS_IN_YEAR)
43 changes: 43 additions & 0 deletions Library/acorn/datavalidation.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
from datetime import timedelta

from QuantConnect.Algorithm import QCAlgorithm
from QuantConnect.Data import Slice
from System import Exception


class DataValidator:
def __init__(self, api: QCAlgorithm, tolerance=0.25):
self.api = api
self.high_tolerance = 1 + tolerance
self.low_tolerance = 1 - tolerance
self.last_data = None

def validate(self, data: Slice):
if self.last_data is None:
self.last_data = data
return

for symbol, bar in data.items():
if symbol in self.last_data:
last_bar = self.last_data[symbol]

pairs = ((bar.Bid.Open, last_bar.Bid.Open),
# (bar.Bid.High, last_bar.Bid.High),
# (bar.Bid.Low, last_bar.Bid.Low),
(bar.Bid.Close, last_bar.Bid.Close),
(bar.Ask.Open, last_bar.Ask.Open),
# (bar.Ask.High, last_bar.Ask.High),
# (bar.Ask.Low, last_bar.Ask.Low),
(bar.Ask.Close, last_bar.Ask.Close))
for val, last_val in pairs:
if val > last_val * self.high_tolerance or val < last_val * self.low_tolerance:
raise Exception(f'bad data for symbol {symbol}\n'
f'last_bar: {last_bar.EndTime} {last_bar}\n'
f' new_bar: {bar.EndTime} {bar}')

if bar.EndTime - last_bar.EndTime > timedelta(days=5):
self.api.Error(f"bad data for symbol {symbol} - gap between bars greater than 5 days\n"
f'last_bar: {last_bar.EndTime} {last_bar}\n'
f' new_bar: {bar.EndTime} {bar}')

self.last_data = data
8 changes: 8 additions & 0 deletions Library/acorn/enums.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
from System import Enum


class CapitalCorrection(Enum):
FIXED = 0
FULL_COMPOUNDING = 1
# TODO implement half compounding
HALF_COMPOUNDING = 2
38 changes: 38 additions & 0 deletions Library/acorn/forecast.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
from collections import namedtuple
from typing import Dict, Tuple, List

from QuantConnect.Data import Slice

from acorn.rules import Rule

FORECAST_CAP = 20


def capped_forecast(raw_forecast) -> float:
if raw_forecast > 0:
return min(FORECAST_CAP, raw_forecast)
elif raw_forecast < 0:
return max(-FORECAST_CAP, raw_forecast)
else:
return 0.0


Forecast = namedtuple('Forecast', 'name weight forecast')

class Forecaster:
def __init__(self, rules: List[Tuple[float, Rule]]):
self.rules = rules

def forecast(self, data: Slice) -> (float, List[Forecast]):
forecasts = []
for weight, rule in self.rules:
rule_forecast = rule.forecast(data)

forecast = capped_forecast(rule_forecast)

forecasts.append(Forecast(rule.name, weight, forecast))

return sum([f.weight * f.forecast for f in forecasts]), forecasts

def ready(self):
return all([rule.ready() for _, rule in self.rules])
121 changes: 121 additions & 0 deletions Library/acorn/instrument_data.csv
Original file line number Diff line number Diff line change
@@ -0,0 +1,121 @@
market,symbol,description,leverage,asset_class
oanda,USDCHF,USD/CHF,25,FX
oanda,GBPPLN,GBP/PLN,20,FX
oanda,CHFJPY,CHF/JPY,25,FX
oanda,EURHUF,EUR/HUF,20,FX
oanda,ZARJPY,ZAR/JPY,20,FX
oanda,AUDCHF,AUD/CHF,20,FX
oanda,NZDSGD,NZD/SGD,20,FX
oanda,EURAUD,EUR/AUD,20,FX
oanda,GBPNZD,GBP/NZD,20,FX
oanda,AUDNZD,AUD/NZD,20,FX
oanda,EURCHF,EUR/CHF,25,FX
oanda,EURSEK,EUR/SEK,20,FX
oanda,USDSGD,USD/SGD,20,FX
oanda,USDCNH,USD/CNH,20,FX
oanda,GBPZAR,GBP/ZAR,20,FX
oanda,USDCZK,USD/CZK,20,FX
oanda,SGDCHF,SGD/CHF,20,FX
oanda,AUDUSD,AUD/USD,20,FX
oanda,AUDJPY,AUD/JPY,20,FX
oanda,EURSGD,EUR/SGD,20,FX
oanda,AUDCAD,AUD/CAD,20,FX
oanda,NZDHKD,NZD/HKD,10,FX
oanda,GBPHKD,GBP/HKD,10,FX
oanda,USDHKD,USD/HKD,10,FX
oanda,USDHUF,USD/HUF,20,FX
oanda,NZDCHF,NZD/CHF,20,FX
oanda,EURNOK,EUR/NOK,20,FX
oanda,NZDUSD,NZD/USD,20,FX
oanda,USDTHB,USD/THB,20,FX
oanda,GBPCHF,GBP/CHF,25,FX
oanda,EURJPY,EUR/JPY,30,FX
oanda,USDPLN,USD/PLN,20,FX
oanda,CHFZAR,CHF/ZAR,20,FX
oanda,EURNZD,EUR/NZD,20,FX
oanda,EURHKD,EUR/HKD,10,FX
oanda,EURGBP,EUR/GBP,30,FX
oanda,EURCAD,EUR/CAD,30,FX
oanda,SGDJPY,SGD/JPY,20,FX
oanda,CADHKD,CAD/HKD,10,FX
oanda,AUDSGD,AUD/SGD,20,FX
oanda,USDZAR,USD/ZAR,20,FX
oanda,USDINR,USD/INR,20,FX
oanda,TRYJPY,TRY/JPY,4, FX
oanda,EURPLN,EUR/PLN,20,FX
oanda,HKDJPY,HKD/JPY,10,FX
oanda,CADSGD,CAD/SGD,20,FX
oanda,EURCZK,EUR/CZK,20,FX
oanda,EURTRY,EUR/TRY,4, FX
oanda,USDJPY,USD/JPY,30,FX
oanda,GBPUSD,GBP/USD,30,FX
oanda,USDMXN,USD/MXN,20,FX
oanda,EURZAR,EUR/ZAR,20,FX
oanda,GBPJPY,GBP/JPY,30,FX
oanda,USDTRY,USD/TRY,4, FX
oanda,CADJPY,CAD/JPY,30,FX
oanda,USDDKK,USD/DKK,20,FX
oanda,USDCAD,USD/CAD,30,FX
oanda,USDNOK,USD/NOK,20,FX
oanda,GBPSGD,GBP/SGD,20,FX
oanda,USDSEK,USD/SEK,20,FX
oanda,AUDHKD,AUD/HKD,10,FX
oanda,CHFHKD,CHF/HKD,10,FX
oanda,GBPCAD,GBP/CAD,30,FX
oanda,EURDKK,EUR/DKK,10,FX
oanda,CADCHF,CAD/CHF,25,FX
oanda,NZDJPY,NZD/JPY,20,FX
oanda,EURUSD,EUR/USD,30,FX
oanda,GBPAUD,GBP/AUD,20,FX
oanda,NZDCAD,NZD/CAD,20,FX
oanda,USB05YUSD,US 5Y T-Note,5,Bond
oanda,USB02YUSD,US 2Y T-Note,5,Bond
oanda,USB10YUSD,US 10Y T-Note,5,Bond
oanda,DE10YBEUR,Bund,5,Bond
oanda,UK10YBGBP,UK 10Y Gilt,5,Bond
oanda,USB30YUSD,US T-Bond,5,Bond
oanda,XAGHKD,Silver/HKD,10,Metals
oanda,XAUSGD,Gold/SGD,20,Metals
oanda,XAGAUD,Silver/AUD,10,Metals
oanda,XAGUSD,Silver,10,Metals
oanda,XAUAUD,Gold/AUD,20,Metals
oanda,XAUNZD,Gold/NZD,20,Metals
oanda,XAUUSD,Gold,20,Metals
oanda,XAGEUR,Silver/EUR,10,Metals
oanda,XAUGBP,Gold/GBP,20,Metals
oanda,XAUCAD,Gold/CAD,20,Metals
oanda,XAUCHF,Gold/CHF,20,Metals
oanda,XAUXAG,Gold/Silver,10,Metals
oanda,XAUEUR,Gold/EUR,20,Metals
oanda,XAGCHF,Silver/CHF,10,Metals
oanda,XAGCAD,Silver/CAD,10,Metals
oanda,XAGGBP,Silver/GBP,10,Metals
oanda,XAUHKD,Gold/HKD,20,Metals
oanda,XAUJPY,Gold/JPY,20,Metals
oanda,XAGSGD,Silver/SGD,10,Metals
oanda,XAGNZD,Silver/NZD,10,Metals
oanda,XAGJPY,Silver/JPY,10,Metals
oanda,XCUUSD,Copper,10,Metals
oanda,XPTUSD,Platinum,10,Metals
oanda,XPDUSD,Palladium,10,Metals
oanda,SUGARUSD,Sugar,10,Ags
oanda,CORNUSD,Corn,10,Ags
oanda,WHEATUSD,Wheat,10,Ags
oanda,SOYBNUSD,Soybeans,10,Ags
oanda,BCOUSD,Brent Crude Oil,10,OilGas
oanda,NATGASUSD,Natural Gas,10,OilGas
oanda,WTICOUSD,West Texas Oil,10,OilGas
oanda,NAS100USD,US Nas 100,20,Equity
oanda,US30USD,US Wall St 30,20,Equity
oanda,SPX500USD,US SPX 500,20,Equity
oanda,CH20CHF,Swiss 20,10,Equity
oanda,UK100GBP,UK 100,20,Equity
oanda,SG30SGD,Singapore 30,10,Equity
oanda,AU200AUD,Australia 200,20,Equity
oanda,NL25EUR,Netherlands 25,10,Equity
oanda,FR40EUR,France 40,20,Equity
oanda,EU50EUR,Europe 50,20,Equity
oanda,HK33HKD,Hong Kong 33,10,Equity
oanda,US2000USD,US Russ 2000,10,Equity
oanda,DE30EUR,Germany 30,20,Equity
oanda,JP225USD,Japan 225,20,Equity
25 changes: 25 additions & 0 deletions Library/acorn/reporting.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
import json
import re
from pathlib import Path

scatter_marker_map = {
'circle': 'circle',
'square': 'square',
'diamond': 'diamond',
'triangle': 'triangle',
'triangle-down': 'inverted_triangle'
}


def latest_backtest_results_path(project) -> Path:
path = Path(project)
list_of_paths = (path / 'backtests').glob('*-*-*_*-*-*');
return max(list_of_paths, key=lambda p: p.stat().st_ctime)


def results_json(results_path: Path) -> dict:
results_file = [p for p in results_path.glob("*.json") if re.match(r'\d+.json', p.name)]
assert len(results_file) == 1
return json.load(open(results_file[0]))


Loading

0 comments on commit ec51c55

Please sign in to comment.