You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
195 lines
5.9 KiB
195 lines
5.9 KiB
import datetime
|
|
import math
|
|
import numpy as np
|
|
import pandas as pd
|
|
import matplotlib.pyplot as plt
|
|
from pandas_datareader import data as pdr
|
|
import backtrader as bt
|
|
|
|
reserve = 5 # usd for comms etc cause I cant math
|
|
|
|
"""
|
|
class LSI(bt.Strategy):
|
|
def start(self):
|
|
self.val_start = self.broker.get_cash()
|
|
|
|
def nextstart(self):
|
|
size = math.floor((self.broker.get_cash() - 10) / self.data[0])
|
|
self.buy(size=size)
|
|
|
|
def stop(self):
|
|
# calculate actual returns
|
|
self.roi = (self.broker.get_value() / self.val_start) - 1
|
|
print("Starting Value: ${:,.2f}".format(self.val_start))
|
|
print("ROI: {:.2f}%".format(self.roi * 100.0))
|
|
print(
|
|
"Annualised: {:.2f}%".format(
|
|
100 * ((1 + self.roi) ** (365 / (endDate - actualStart).days) - 1)
|
|
)
|
|
)
|
|
print(
|
|
"Gross Return: ${:,.2f}".format(self.broker.get_value() - self.val_start)
|
|
)
|
|
"""
|
|
|
|
|
|
class PercentageCommisionScheme(bt.CommInfoBase):
|
|
params = (
|
|
("commission", 0.0004),
|
|
("stocklike", True),
|
|
("commtype", bt.CommInfoBase.COMM_PERC),
|
|
)
|
|
|
|
def _getcommission(self, size, price, pseudoexec):
|
|
return abs(size * price * self.p.commission) + 4 # 290rub/month + 0.04%
|
|
|
|
|
|
class Investing(bt.Strategy):
|
|
def __init__(self, params={"sum": 500, "coef": 1}):
|
|
self.monthly_params = params
|
|
self.order = None
|
|
self.cost = 0 # no comms
|
|
self.comms = 0
|
|
self.units = 0
|
|
self.times = 0
|
|
self.months = 0
|
|
|
|
def log(self, txt, dt=None):
|
|
dt = dt or self.datas[0].datetime.date(0)
|
|
# print( "%s, V:%.2f, C:%.2f, %s" % (dt.isoformat(), self.broker.get_value(), self.broker.get_cash(), txt))
|
|
|
|
def start(self):
|
|
self.broker.set_fundmode(fundmode=True, fundstartval=100.0)
|
|
|
|
self.cash_start = self.broker.get_cash()
|
|
self.val_start = 100.0
|
|
|
|
# ADD A TIMER
|
|
self.add_timer(
|
|
when=bt.timer.SESSION_START,
|
|
monthdays=[1],
|
|
monthcarry=True
|
|
# timername='buytimer',
|
|
)
|
|
|
|
def notify_order(self, order):
|
|
if order.status in [order.Submitted, order.Accepted]:
|
|
return
|
|
else:
|
|
self.log(
|
|
"%s Price %.2f, Units %.0f, Value %.2f, Comm %.2f, "
|
|
% (
|
|
"BUY" if order.isbuy() else "SELL",
|
|
order.executed.price,
|
|
order.executed.size,
|
|
order.executed.value,
|
|
order.executed.comm,
|
|
)
|
|
)
|
|
|
|
if order.status in [order.Completed]:
|
|
if order.isbuy():
|
|
self.units += order.executed.size
|
|
self.cost += order.executed.value
|
|
|
|
elif order.issell():
|
|
self.units -= order.executed.size
|
|
|
|
self.comms += order.executed.comm
|
|
self.times += 1
|
|
|
|
elif order.status in [order.Canceled, order.Margin, order.Rejected]:
|
|
self.log("Order Canceled/Margin/Rejected")
|
|
print(order.status, [order.Canceled, order.Margin, order.Rejected])
|
|
|
|
self.order = None
|
|
|
|
@property
|
|
def monthly_cash(self):
|
|
return self.monthly_params["sum"] * self.monthly_params["coef"] ** (self.months)
|
|
|
|
def calc_params(self):
|
|
# calculate actual returns
|
|
self.froi = self.broker.get_fundvalue() - self.val_start
|
|
return [
|
|
self.froi,
|
|
self.cost,
|
|
self.broker.get_value(),
|
|
self.times,
|
|
self.units,
|
|
self.comms,
|
|
self.monthly_cash,
|
|
]
|
|
|
|
def notify_timer(self, timer, when, *args):
|
|
self.months += 1
|
|
self.broker.add_cash(self.monthly_cash)
|
|
|
|
|
|
class FormulaInvesting(Investing):
|
|
def notify_timer(self, timer, when, *args):
|
|
super().notify_timer(timer, when, *args)
|
|
self.formula()
|
|
|
|
|
|
class VA(FormulaInvesting):
|
|
def formula(self):
|
|
target_value = (
|
|
min(
|
|
self.monthly_cash * self.months,
|
|
self.broker.get_value(),
|
|
)
|
|
- reserve
|
|
)
|
|
self.order_target_value(target=target_value),
|
|
self.broker.set_cash(self.broker.get_cash() * self.monthly_params["t_rate"])
|
|
|
|
|
|
class QVA(VA):
|
|
def formula(self):
|
|
if not self.months % 3:
|
|
super().formula()
|
|
|
|
|
|
class DCA(FormulaInvesting):
|
|
def formula(self):
|
|
target_value = self.broker.get_value() - reserve
|
|
self.order_target_value(target=target_value)
|
|
|
|
|
|
class QDCA(DCA):
|
|
def formula(self):
|
|
if not self.months % 3:
|
|
super().formula()
|
|
|
|
|
|
class SmaCross(Investing):
|
|
params = dict(
|
|
pfast=50, # period for the fast moving average
|
|
pslow=200, # period for the slow moving average
|
|
)
|
|
# list of parameters which are configurable for the strategy
|
|
def __init__(self, *args, **kwargs):
|
|
super().__init__(*args, **kwargs)
|
|
sma1 = bt.ind.SMA(period=self.p.pfast) # fast moving average
|
|
sma2 = bt.ind.SMA(period=self.p.pslow) # slow moving average
|
|
self.crossover = bt.ind.CrossOver(sma1, sma2) # crossover signal
|
|
|
|
def next(self):
|
|
if not self.position: # not in the market
|
|
if self.crossover > 0: # if fast crosses slow to the upside
|
|
self.order_target_value(target=self.broker.get_cash()) # enter long
|
|
|
|
elif self.crossover < 0: # in the market & cross to the downside
|
|
self.close() # close long position
|
|
|
|
|
|
class SmaVA(SmaCross):
|
|
def next(self):
|
|
if not self.position: # not in the market
|
|
if self.crossover > 0: # if fast crosses slow to the upside
|
|
self.order_target_value(target=self.broker.get_cash()) # enter long
|
|
|
|
elif self.crossover < 0: # in the market & cross to the downside
|
|
self.order_target_value(target=self.broker.get_value() / 2) # close half
|