generationk

package module
v0.1.2 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Mar 10, 2021 License: GPL-3.0 Imports: 15 Imported by: 1

README

⚠️ This is an early version. The package can be used for backtesting, but the API is not stable yet. This is a work in progress. Once stability is reached, version 1.0.0 will be tagged. Please check back then. ⚠️

Features

  1. Read CVS file with historic data and back test in parallell. Testing ~5000 daily data per 100 CSV files can take about 1 sec. to complete.

ToDo

  1. To set entry and exit conditions independently to be able to combine many different ones, ex. entry RSI < 20; exit MA2 > MA5
  2. To be able to try which parameters are the best ones for an indicator (simplified branch)
  3. Use a genetic algo to scan the search space for most profitable parameters
  4. Optimize for other functions than profit, drawdown, volatility
  5. Make a WS API, deploy as lambda functions
  6. It would be interesting to output some common statistics about back test except profit etc.

generationk

The inspiration for this project took place after using a few other backtesting frameworks in Python.

I was tired of waiting for results and concluded that I want the fast feeling of a compiled language and I wanted all processor cores to be used.

I looked at a few different ones in Golang but they where either very complex for simple tasks or did not really appeal to the context.

Design choices

I have created 3 versions of this framework.

  1. The very first version was based on channels and real time but it very rarely stock data is real time in backtesting, even a minute is considered very granular and 5 minutes or 10 minutes are much more common. In that case reading a 2-3 data points will advance time 20-30 minutes just for placing an order which is not realistic in the end.

  2. The second version was based on callbacks using interfaces which would have the advantage of feeding one data point after the other into the backtester from the simulated data source and this way obtain a more realistic feeling. A main argument is that it is not possible to peek at future data by mistake. In the end I think a backtest that peeks into future data stands out in the results as amazing and is actually easy to spot. I also felt that everything gets very slow and the reason is not strong enough since backtests are not realistic anyway. My main priority is to check statistics to see if there is an edge and get a quick overview and then to see how I could manually trade a system.

  3. In the third version which is the current what is provided is basically a for loop which loops over data. I have also tried to remove all kinds of encapsulation and special 'types'. I try to as much as possible only work with float64. Because it gives a degree of freedom and any functions can be written to work on numbers. So I want to as much as possible have access to the raw data and numbers instead of a 'LinearNumberSeries' struct and so on...

The Crossing MA example looks like this

type MACrossStrategy struct {
	ma50  []float64
	close []float64
}

//Once is used to declare what indicators will be used; it's run once
func (ma *MACrossStrategy) Once(ctx *K.Context, ohlc *K.OHLC) error {

	//The closing prices
	ma.close = ohlc.Close

	//The Simple Moving Average length 50 periods, the ones from 0 to 50 
	//will be registred in the array as well
	ma.ma50 = indicators.SimpleMovingAverage(ohlc.Close, 50)

	//If the init period is set, the PerBar function below will not be called 
	//until the InitPeriod is reached
	ctx.SetInitPeriod(50)

	//No errors to my knowledge
	return nil
}

//PerBar gets called when there is new data coming in
func (ma *MACrossStrategy) PerBar(k int, callback K.Callback) error {

	//Closing price breaks the MA50
	if ma.close[k] > ma.ma50[k] {
		//Are we owning this stock since before?
		if !callback.Owning() {
			//No. Then we can buy 100 stocks with a marketorder
			callback.SendOrder(K.BuyOrder, K.MarketOrder, 100)

to run the above strategy a command like this could be used:

go run generationk/cmd backtest -test MACrossStrategy -dir ../test/data/CSV2/ -fromDate 01/01/2015

Strategies

I want the strategies directory to contain working trading strategies that are actually used in the market. There are many working ones and the hard part is to trade the system, actually not find a system that is profitable. If you find something interesting, please share it. Sharing is caring 🤗

Future implementation

  • To use genetic algorithms to find the best trading system for a stocks / stocks
  • To plug in machine learning

Literature on backtesting

Even though we back test we are still biased. In their work on representativeness, Tversky and Kahneman (1974) find that people expect that a sequence of outcomes generated by a random process will resemble the essential characteristics of that process even when the sequence is short. Griffin and Tversky (1992) provide an extension documenting that people focus on the strength or extremeness of the evidence with insufficient regard of its credence, predictability, and weight.

Documentation

Index

Constants

View Source
const (
	//Open value will be used for the data
	Open = iota
	//High value will be used for the data
	High
	//Low value will be used for the data
	Low
	//Close value will be used for the data
	Close
	//Volume value will be used for the data
	Volume
	//Default is used when the data is something else than ohlcv
	Default
)

Variables

View Source
var AssetDoesNotExist = errors.New("Asset does not exist")
View Source
var EndOfBacktest = errors.New("End of backtest")
View Source
var EndOfData = errors.New("End of data")
View Source
var FFToStartDate = errors.New("Fast forwarding to start date")
View Source
var Initialization = errors.New("Initialization in Once() failed")
View Source
var UnknownDirection = errors.New("Unknown type of direction for order - should be Buy, Sell, Short or Cover")
View Source
var UnstablePeriod = errors.New("The stable period is not yet reached")

Functions

func MapRecordsInvesting

func MapRecordsInvesting(records ...string) (time.Time, []float64)

func ParseFloat

func ParseFloat(value string) float64

pasetFloat is used to parse the floats from the CSV files and is a better way to to handle errors

func Run

func Run(ctx *Context, dm *DataManager)

func RunStrategyOnAssets

func RunStrategyOnAssets(ctx *Context, dm *DataManager)

Types

type Accepted

type Accepted struct {
}

Accepted is a status of the order to indicate that an order has been accepted by the broker.

func (Accepted) String

func (a Accepted) String() string

type Asset

type Asset struct {
	// contains filtered or unexported fields
}

Asset data type

func NewAsset

func NewAsset(name string, ohlc *OHLC, length int) *Asset

NewAsset is used to create a new asset-

func (Asset) GetLength

func (a Asset) GetLength() int

type Broker

type Broker struct {
	// contains filtered or unexported fields
}

Broker is used to send orders

func (*Broker) SendOrder

func (b *Broker) SendOrder(order Order, orderstatus OrderStatus) error

SendOrder is used to place an order with the broker

func (*Broker) SetComission

func (b *Broker) SetComission(comission Comission)

SetComission is used to set a comission scheme

type Callback

type Callback interface {
	Owning() bool
	IsOwning(assetName string) (bool, error)
	SendOrder(direction Direction, orderType OrderType, qty int) error
	SendOrderFor(assetName string, direction Direction, orderType OrderType, qty int) error
	Assets() []string
}

Callback is used in the strategy to give actions back to the backtest in progress

type Comission

type Comission interface {
	GetComisson(price float64, qty int) float64
}

Comission interface can be used to implment any comission scheme

type Context

type Context struct {
	K int
	// contains filtered or unexported fields
}

Context holds holds the strategy, assets, indicators per asset, start date, end date, the portfolio the current date and the unstable period

func NewContext

func NewContext() *Context

NewContext creates a new context

func (*Context) AddAsset

func (ctx *Context) AddAsset(asset *Asset)

AddAsset is used to add assets that the strategy will use

func (*Context) AddIndicator

func (ctx *Context) AddIndicator(indicator indicators.Indicator)

AddIndicator will add it to all assets

func (*Context) AddIndicatorOnAsset

func (ctx *Context) AddIndicatorOnAsset(asset *Asset, indicator indicators.Indicator)

AddIndicatorOnAsset will add an indicator on the asset

func (*Context) AddIndicatorWithParams

func (ctx *Context) AddIndicatorWithParams(indicator indicators.Indicator, param indicators.Param)

AddIndicator will add it to all assets

func (*Context) GetAssetByName

func (ctx *Context) GetAssetByName(name string) *Asset

GetAssetByName return a specific strategy

func (*Context) GetAssetIndicatorByName

func (ctx *Context) GetAssetIndicatorByName(name string) []indicators.Indicator

GetAssetIndicatorByName is used to get the indicators associated with the asset

func (*Context) GetAssets

func (ctx *Context) GetAssets() []Asset

GetAssets returns the assets used in the strategy

func (*Context) GetDataPath

func (ctx *Context) GetDataPath() string

func (*Context) GetEndDate

func (ctx *Context) GetEndDate() time.Time

func (*Context) GetInitPeriod

func (ctx *Context) GetInitPeriod() int

GetInitPeriod returns the period

func (*Context) GetStartDate

func (ctx *Context) GetStartDate() time.Time

func (*Context) GetStrategy

func (ctx *Context) GetStrategy() Strategy

SetStrategy is used to set the strategy that will be run

func (*Context) SetDataPath

func (ctx *Context) SetDataPath(path string)

func (*Context) SetEndDate

func (ctx *Context) SetEndDate(endTime time.Time)

AddEndDate is used to set the strategy that will be run

func (*Context) SetInitPeriod

func (ctx *Context) SetInitPeriod(period int)

SetInitPeriod is used to set the unstable period, the longest period shoul be used

func (*Context) SetStartDate

func (ctx *Context) SetStartDate(startTime time.Time)

AddStartDate is used to set the start date

func (*Context) SetStrategy

func (ctx *Context) SetStrategy(strategy Strategy)

AddStrategy is used to set the strategy that will be run

func (*Context) Time

func (ctx *Context) Time() time.Time

Time returns the time of the current bar

type DataEvent

type DataEvent struct {
	Name string
	Ohlc OHLC
}

DataEvent is a data structure used to carry OHLC data

func (DataEvent) String

func (d DataEvent) String() string

type DataHandler

type DataHandler interface {
	AddAsset(asset *Asset)
}

DataHandler is the interface used to recieve data from any data producing function. It can be used to feed data to generationK

type DataManager

type DataManager struct {
	Folder      string
	Headers     bool
	MappingFunc Maprecords
}

func NewCSVDataManager

func NewCSVDataManager(folder string, headers bool, mapping Maprecords) *DataManager

func (*DataManager) ReadCSVFile

func (d *DataManager) ReadCSVFile(file string) *Asset

type DataUpdate

type DataUpdate interface {
	Update(ohlc OHLC)
}

DataUpdate is used to update the data in the assets

type Direction

type Direction int
const (
	//BuyOrder order
	BuyOrder Direction = iota
	//SellOrder order
	SellOrder
	//ShortOrder order to short
	ShortOrder
	//CoverOrder to cover a shrot
	CoverOrder
	//Zero = 0
	ZERO = 0
	//Empty account
	EMPTY = 0.0
)
const (
	Long Direction = iota
	Short
)

Direction of a trade: long or short

type Directon

type Directon int

Directon is used to describe an order

type Event

type Event interface {
	String() string
}

Event type

type Fill

type Fill struct {
	Qty       int
	Price     float64
	AssetName string
	Time      time.Time
}

Fill is used to indicate to the implementer of OrderStatus that an order has been filled

func (Fill) String

func (f Fill) String() string

type FixedComission

type FixedComission struct {
	// contains filtered or unexported fields
}

FixedComission is an example of a fixded comission with 2 limits

func (FixedComission) GetComisson

func (f FixedComission) GetComisson(price float64, qty int) float64

GetComission return the amount for more than or less than 500 pieces of asset

type GenerationK

type GenerationK struct {
	// contains filtered or unexported fields
}

func NewGenerationK

func NewGenerationK() *GenerationK

NewGenerationK is used to create a new backtest

func (*GenerationK) AddAsset

func (k *GenerationK) AddAsset(asset *Asset)

AddAsset is used to add a pointer to an asset

func (*GenerationK) AddStrategy

func (k *GenerationK) AddStrategy(strat Strategy)

AddStrategy is used to add a strategy to the backtest

func (*GenerationK) Assets

func (k *GenerationK) Assets() []string

Assets returns an array of assets

func (*GenerationK) GetAsset

func (k *GenerationK) GetAsset() Asset

Returns an array of all assets

func (*GenerationK) GetAssetByName

func (k *GenerationK) GetAssetByName(name string) *Asset

GetAssetByName returns a pointer to the asset by that name

func (*GenerationK) GetAssets

func (k *GenerationK) GetAssets() []Asset

Returns an array of all assets

func (*GenerationK) IsOwning

func (k *GenerationK) IsOwning(assetName string) (bool, error)

OwnPosition is used to find out if we have a holding in an asset and the assumption is that the strategy is using multiple assets

func (*GenerationK) Owning

func (k *GenerationK) Owning() bool

Owning is used to find out if we have a holding and we are only processing 1 asset

func (*GenerationK) Run

func (k *GenerationK) Run() error

func (*GenerationK) SendOrder

func (k *GenerationK) SendOrder(direction Direction, orderType OrderType, qty int) error

OrderSend is used to send an order to the broker, return an error if the asset does not exist

func (*GenerationK) SendOrderFor

func (k *GenerationK) SendOrderFor(assetName string, direction Direction, orderType OrderType, qty int) error

OrderSend is used to send an order to the broker, return an error if the asset does not exist

func (*GenerationK) SetBalance

func (k *GenerationK) SetBalance(balance float64)

SetBalance is used to set the balance when the backtest is started

func (*GenerationK) SetComission

func (k *GenerationK) SetComission(comission Comission)

SetComission is used to set the comission scheme is there is one

func (*GenerationK) SetDataManager

func (k *GenerationK) SetDataManager()

AddDataManager is currently not used

func (*GenerationK) SetEndDate

func (k *GenerationK) SetEndDate(endDate time.Time)

AddEndDate is used to set the end date for the backtest

func (*GenerationK) SetPortfolio

func (k *GenerationK) SetPortfolio(portfolio *Portfolio)

AddPortfolio is used to add a pointer to a portfolio to the backtest

func (*GenerationK) SetStartDate

func (k *GenerationK) SetStartDate(startDate time.Time)

AddStartDate is used to set the end date for the backtest

type Holding

type Holding struct {
	// contains filtered or unexported fields
}

This is what we are owning, a holding

type IndicatorError

type IndicatorError struct {
	Err error
	// contains filtered or unexported fields
}

IndicatorError Used to signal an error with an indicator

func (IndicatorError) Error

func (e IndicatorError) Error() string

type IndicatorNotReadyError

type IndicatorNotReadyError struct {
	Msg string //description of error
	Len int    //the length needed before trying again
}

IndicatorNotReadyError is an error thrown when an indicator needs more data to be used

func (IndicatorNotReadyError) Error

func (e IndicatorNotReadyError) Error() string

type Job

type Job interface {
	GetId() int
	GetFileName() string
	GetResult() float64
	GetParams() []float64
}

type JobStruct

type JobStruct struct {
	Id       int
	FileName string
	Result   float64
	Params   []Params
}

func (*JobStruct) GetFileName

func (j *JobStruct) GetFileName() string

func (*JobStruct) GetId

func (j *JobStruct) GetId() int

func (*JobStruct) GetResult

func (j *JobStruct) GetResult() float64

func (*JobStruct) SetParams

func (j *JobStruct) SetParams(params ...Params)

type Maprecords

type Maprecords func(...string) (time.Time, []float64)

type MultiStrategy

type MultiStrategy interface {
	GetParams() []*Params
	Once(ctx *Context, assets []*Asset) error
	Update(k *int) error
	PerBar(k int, callback Callback) error
}

type OHLC

type OHLC struct {
	Time                           []time.Time
	Open, High, Low, Close, Volume []float64
}

OHLC data type

type OhlcConst

type OhlcConst int

type Order

type Order struct {
	Asset string

	Time  time.Time
	Price float64
	Qty   int
	// contains filtered or unexported fields
}

Order describes an order that is used to buy / sell an asset

func (Order) String

func (o Order) String() string

type OrderStatus

type OrderStatus interface {
	OrderEvent(orderEvent Event)
}

OrderStatus is a callback interface used to recieve information about orders, it is used by the broker

type OrderType

type OrderType int
const (
	// A market order is an order to buy or sell a stock at the market’s
	// current best available price. A market order typically ensures
	// an execution but it does not guarantee a specified price.
	MarketOrder OrderType = iota

	//A limit order is an order to buy or sell a stock with a restriction on
	// the maximum price to be paid or the minimum price to be received
	// (the “limit price”). If the order is filled, it will only be at the
	// specified limit price or better. However, there is no assurance of
	// execution. A limit order may be appropriate when you think you can
	// buy at a price lower than—or sell at a price higher than—the
	// current quote.
	// Maximum price for buys, or minimum price for sells, at which the
	// order should be filled.
	LimitOrder

	// A stop order is an order to buy or sell a stock at the market price once
	// the stock has traded at or through a specified price (the “stop price”).
	// If the stock reaches the stop price, the order becomes a market order and
	// is filled at the next available market price. If the stock fails to reach
	// the stop price, the order is not executed.
	// For sells, the order will be placed if market price falls below this value.
	// For buys, the order will be placed if market price rises above this value.
	StopOrder

	// A stop order is an order to buy or sell a stock at the market price once
	// the stock has traded at or through a specified price (the “stop price”).
	// If the stock reaches the stop price, a limit order is placed
	StopLimitOrder
)

type Params

type Params struct {
	Value, Low, High float64
}

func (Params) GetValue

func (p Params) GetValue() float64

func (*Params) NewParams

func (p *Params) NewParams(value, low, high float64) *Params

type PartialFill

type PartialFill struct {
}

PartialFill is used to giv enotice to the strategy that a partial fill took place

func (PartialFill) String

func (pf PartialFill) String() string

type PercentageComission

type PercentageComission struct {
	// contains filtered or unexported fields
}

PercentageComission is an example of a comission scheme with a fixed percentage

func (PercentageComission) GetComisson

func (pc PercentageComission) GetComisson(price float64, qty int) float64

Returns the comission based on a percentage of the amount, qty is not used in this scheme

type Portfolio

type Portfolio struct {
	sync.Mutex
	// contains filtered or unexported fields
}

The portfolio holds assets: holdings, the portfolio holds a mutext to be able to use the same portfolio when testing many assets in parallell but updating the account on a single portfolio

func NewPortfolio

func NewPortfolio() *Portfolio

Is used to create a new portfolio

func (*Portfolio) AddHolding

func (p *Portfolio) AddHolding(position Holding)

AddHolding, its been bought

func (*Portfolio) GetBalance

func (p *Portfolio) GetBalance() float64

GetBalance returns in the balance of the account

func (*Portfolio) IsOwning

func (p *Portfolio) IsOwning(assetName string) bool

IsOwning is used to find out if a position is already owned in this asset

func (*Portfolio) RemoveHolding

func (p *Portfolio) RemoveHolding(position Holding)

Remove a holding, its sold

func (*Portfolio) SetBalance

func (p *Portfolio) SetBalance(amount float64)

SetBalance is used to set the starting balance of the account

type Quit

type Quit struct{}

Quit event type

func (Quit) String

func (q Quit) String() string

type RebalanceStrategy

type RebalanceStrategy interface {
	GetInterval() string
	Rebalance(k int, date time.Time, callback Callback) error
}

type Rejected

type Rejected struct {
	// contains filtered or unexported fields
}

Rejected type is for order that can not be executed

func (Rejected) String

func (r Rejected) String() string

type Strategy

type Strategy interface {
	GetParams() []*Params
	Once(ctx *Context, ohlc *OHLC) error
	Update(k *int) error
	PerBar(k int, callback Callback) error
}

Strategy is the class where the logic is placed to buy and sell assets the two methods that needs to be implemented are Setup and Tick. The Setup method is used to define if any indicators will be used and what period they need to be stable. The Tick method is called for every new data which arrives and is a possibility to make checks and send orders.

type Submitted

type Submitted struct {
}

Submitted is a status used after an order is to be processed by the broker

func (Submitted) String

func (s Submitted) String() string

type Tick

type Tick struct{}

Tick event type

func (Tick) String

func (t Tick) String() string

Directories

Path Synopsis
indicators module
strategies module

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL