mirror of
https://github.com/lukewilson2002/autotrader.git
synced 2025-08-02 13:19:32 +00:00
Worked on several new features at once
This commit is contained in:
@@ -10,15 +10,26 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
ErrEOF = errors.New("end of the input data")
|
ErrEOF = errors.New("end of the input data")
|
||||||
ErrNoData = errors.New("no data")
|
ErrNoData = errors.New("no data")
|
||||||
|
ErrPositionClosed = errors.New("position closed")
|
||||||
)
|
)
|
||||||
|
|
||||||
func Backtest(trader *Trader) {
|
func Backtest(trader *Trader) {
|
||||||
trader.Tick()
|
trader.Tick()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TestBroker is a broker that can be used for testing. It implements the Broker interface and fulfills orders
|
||||||
|
//
|
||||||
|
// Signals:
|
||||||
|
// - Tick(nil) - Called when the broker ticks.
|
||||||
|
// - OrderPlaced(Order) - Called when an order is placed.
|
||||||
|
// - OrderFilled(Order) - Called when an order is filled.
|
||||||
|
// - OrderCanceled(Order) - Called when an order is canceled.
|
||||||
|
// - PositionClosed(Position) - Called when a position is closed.
|
||||||
|
// - PositionModified(Position) - Called when a position changes.
|
||||||
type TestBroker struct {
|
type TestBroker struct {
|
||||||
|
SignalManager
|
||||||
DataBroker Broker
|
DataBroker Broker
|
||||||
Data *df.DataFrame
|
Data *df.DataFrame
|
||||||
Cash float64
|
Cash float64
|
||||||
@@ -110,6 +121,7 @@ func (b *TestBroker) MarketOrder(symbol string, units float64, stopLoss, takePro
|
|||||||
|
|
||||||
b.orders = append(b.orders, order)
|
b.orders = append(b.orders, order)
|
||||||
b.positions = append(b.positions, position)
|
b.positions = append(b.positions, position)
|
||||||
|
b.SignalEmit("OrderPlaced", order)
|
||||||
|
|
||||||
return order, nil
|
return order, nil
|
||||||
}
|
}
|
||||||
@@ -138,6 +150,63 @@ func NewTestBroker(dataBroker Broker, data *df.DataFrame, cash, leverage, spread
|
|||||||
}
|
}
|
||||||
|
|
||||||
type TestPosition struct {
|
type TestPosition struct {
|
||||||
|
closed bool
|
||||||
|
entryPrice float64
|
||||||
|
id string
|
||||||
|
leverage float64
|
||||||
|
symbol string
|
||||||
|
stopLoss float64
|
||||||
|
takeProfit float64
|
||||||
|
time time.Time
|
||||||
|
units float64
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *TestPosition) Close() error {
|
||||||
|
if p.closed {
|
||||||
|
return ErrPositionClosed
|
||||||
|
}
|
||||||
|
p.closed = true
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *TestPosition) Closed() bool {
|
||||||
|
return p.closed
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *TestPosition) EntryPrice() float64 {
|
||||||
|
return p.entryPrice
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *TestPosition) Id() string {
|
||||||
|
return p.id
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *TestPosition) Leverage() float64 {
|
||||||
|
return p.leverage
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *TestPosition) PL() float64 {
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *TestPosition) Symbol() string {
|
||||||
|
return p.symbol
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *TestPosition) StopLoss() float64 {
|
||||||
|
return p.stopLoss
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *TestPosition) TakeProfit() float64 {
|
||||||
|
return p.takeProfit
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *TestPosition) Time() time.Time {
|
||||||
|
return p.time
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *TestPosition) Units() float64 {
|
||||||
|
return p.units
|
||||||
}
|
}
|
||||||
|
|
||||||
type TestOrder struct {
|
type TestOrder struct {
|
||||||
|
15
broker.go
15
broker.go
@@ -38,6 +38,17 @@ type Order interface {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type Position interface {
|
type Position interface {
|
||||||
|
Close() error // Close attempts to close the position and returns an error if it fails. If the error is nil, the position was closed.
|
||||||
|
Closed() bool // Closed returns true if the position has been closed with the broker.
|
||||||
|
EntryPrice() float64 // EntryPrice returns the price of the symbol at the time the position was opened.
|
||||||
|
Id() string // Id returns the unique identifier of the position by the broker.
|
||||||
|
Leverage() float64 // Leverage returns the leverage of the position.
|
||||||
|
PL() float64 // PL returns the profit or loss of the position.
|
||||||
|
Symbol() string // Symbol returns the symbol name of the position.
|
||||||
|
StopLoss() float64 // StopLoss returns the stop loss price of the position.
|
||||||
|
TakeProfit() float64 // TakeProfit returns the take profit price of the position.
|
||||||
|
Time() time.Time // Time returns the time the position was opened.
|
||||||
|
Units() float64 // Units returns the number of units purchased or sold by the position.
|
||||||
}
|
}
|
||||||
|
|
||||||
type Broker interface {
|
type Broker interface {
|
||||||
@@ -45,6 +56,10 @@ type Broker interface {
|
|||||||
Candles(symbol string, frequency string, count int) (*df.DataFrame, error)
|
Candles(symbol string, frequency string, count int) (*df.DataFrame, error)
|
||||||
MarketOrder(symbol string, units float64, stopLoss, takeProfit float64) (Order, error)
|
MarketOrder(symbol string, units float64, stopLoss, takeProfit float64) (Order, error)
|
||||||
NAV() float64 // NAV returns the net asset value of the account.
|
NAV() float64 // NAV returns the net asset value of the account.
|
||||||
|
// Orders returns a slice of orders that have been placed with the broker. If an order has been canceled or
|
||||||
|
// filled, it will not be returned.
|
||||||
Orders() []Order
|
Orders() []Order
|
||||||
|
// Positions returns a slice of positions that are currently open with the broker. If a position has been
|
||||||
|
// closed, it will not be returned.
|
||||||
Positions() []Position
|
Positions() []Position
|
||||||
}
|
}
|
||||||
|
173
data.go
173
data.go
@@ -11,6 +11,179 @@ import (
|
|||||||
df "github.com/rocketlaunchr/dataframe-go"
|
df "github.com/rocketlaunchr/dataframe-go"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
type Series interface {
|
||||||
|
Copy() Series
|
||||||
|
Len() int
|
||||||
|
}
|
||||||
|
|
||||||
|
type Frame interface {
|
||||||
|
Copy() Frame
|
||||||
|
Len() int
|
||||||
|
|
||||||
|
// Comparison functions.
|
||||||
|
Equal(other Frame) bool
|
||||||
|
NotEqual(other Frame) bool
|
||||||
|
Less(other Frame) bool
|
||||||
|
LessEqual(other Frame) bool
|
||||||
|
Greater(other Frame) bool
|
||||||
|
GreaterEqual(other Frame) bool
|
||||||
|
|
||||||
|
// Easy access functions.
|
||||||
|
Date(i int) time.Time
|
||||||
|
Open(i int) float64
|
||||||
|
High(i int) float64
|
||||||
|
Low(i int) float64
|
||||||
|
Close(i int) float64
|
||||||
|
Volume(i int) float64
|
||||||
|
Dates() Series
|
||||||
|
Opens() Series
|
||||||
|
Highs() Series
|
||||||
|
Lows() Series
|
||||||
|
Closes() Series
|
||||||
|
Volumes() Series
|
||||||
|
|
||||||
|
// Custom data columns
|
||||||
|
Value(column string, i int) interface{}
|
||||||
|
Float(column string, i int) float64
|
||||||
|
Int(column string, i int) int
|
||||||
|
String(column string, i int) string
|
||||||
|
// Time returns the value of the column at index i. The first value is at index 0. A negative value for i (-n) can be used to get n values from the latest, like Python's negative indexing. If i is out of bounds, 0 is returned.
|
||||||
|
Time(column string, i int) time.Time
|
||||||
|
}
|
||||||
|
|
||||||
|
type DataFrame struct {
|
||||||
|
*df.DataFrame // DataFrame with a Date, Open, High, Low, Close, and Volume column.
|
||||||
|
}
|
||||||
|
|
||||||
|
func (o *DataFrame) Copy() *DataFrame {
|
||||||
|
return &DataFrame{o.DataFrame.Copy()}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (o *DataFrame) Len() int {
|
||||||
|
if o.DataFrame == nil {
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
return o.NRows()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Date returns the value of the Date column at index i. The first value is at index 0. A negative value for i (-n) can be used to get n values from the latest, like Python's negative indexing. If i is out of bounds, 0 is returned.
|
||||||
|
// This is the equivalent to calling Time("Date", i).
|
||||||
|
func (o *DataFrame) Date(i int) time.Time {
|
||||||
|
return o.Time("Date", i)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Open returns the open price of the candle at index i. The first candle is at index 0. A negative value for i (-n) can be used to get n candles from the latest, like Python's negative indexing. If i is out of bounds, 0 is returned.
|
||||||
|
// This is the equivalent to calling Float("Open", i).
|
||||||
|
func (o *DataFrame) Open(i int) float64 {
|
||||||
|
return o.Float("Open", i)
|
||||||
|
}
|
||||||
|
|
||||||
|
// High returns the high price of the candle at index i. The first candle is at index 0. A negative value for i (-n) can be used to get n candles from the latest, like Python's negative indexing. If i is out of bounds, 0 is returned.
|
||||||
|
// This is the equivalent to calling Float("High", i).
|
||||||
|
func (o *DataFrame) High(i int) float64 {
|
||||||
|
return o.Float("High", i)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Low returns the low price of the candle at index i. The first candle is at index 0. A negative value for i (-n) can be used to get n candles from the latest, like Python's negative indexing. If i is out of bounds, 0 is returned.
|
||||||
|
// This is the equivalent to calling Float("Low", i).
|
||||||
|
func (o *DataFrame) Low(i int) float64 {
|
||||||
|
return o.Float("Low", i)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Close returns the close price of the candle at index i. The first candle is at index 0. A negative value for i (-n) can be used to get n candles from the latest, like Python's negative indexing. If i is out of bounds, 0 is returned.
|
||||||
|
// This is the equivalent to calling Float("Close", i).
|
||||||
|
func (o *DataFrame) Close(i int) float64 {
|
||||||
|
return o.Float("Close", i)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Volume returns the volume of the candle at index i. The first candle is at index 0. A negative value for i (-n) can be used to get n candles from the latest, like Python's negative indexing. If i is out of bounds, 0 is returned.
|
||||||
|
// This is the equivalent to calling Float("Volume", i).
|
||||||
|
func (o *DataFrame) Volume(i int) float64 {
|
||||||
|
return o.Float("Volume", i)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Value returns the value of the column at index i. The first value is at index 0. A negative value for i (-n) can be used to get n values from the latest, like Python's negative indexing. If i is out of bounds, nil is returned.
|
||||||
|
func (o *DataFrame) Value(column string, i int) interface{} {
|
||||||
|
colIdx, err := o.DataFrame.NameToColumn(column)
|
||||||
|
if err != nil {
|
||||||
|
return nil
|
||||||
|
} else if o.DataFrame == nil || i >= o.Len() {
|
||||||
|
return 0
|
||||||
|
} else if i < 0 {
|
||||||
|
i = o.Len() - i
|
||||||
|
if i < 0 {
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
return o.Series[colIdx].Value(i)
|
||||||
|
}
|
||||||
|
return o.Series[colIdx].Value(i)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Float returns the value of the column at index i casted to float64. The first value is at index 0. A negative value for i (-n) can be used to get n values from the latest, like Python's negative indexing. If i is out of bounds, 0 is returned.
|
||||||
|
func (o *DataFrame) Float(column string, i int) float64 {
|
||||||
|
val := o.Value(column, i)
|
||||||
|
if val == nil {
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
switch val.(type) {
|
||||||
|
case float64:
|
||||||
|
return val.(float64)
|
||||||
|
default:
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Int returns the value of the column at index i casted to int. The first value is at index 0. A negative value for i (-n) can be used to get n values from the latest, like Python's negative indexing. If i is out of bounds, 0 is returned.
|
||||||
|
func (o *DataFrame) Int(column string, i int) int {
|
||||||
|
val := o.Value(column, i)
|
||||||
|
if val == nil {
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
switch val.(type) {
|
||||||
|
case int:
|
||||||
|
return val.(int)
|
||||||
|
default:
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// String returns the value of the column at index i casted to string. The first value is at index 0. A negative value for i (-n) can be used to get n values from the latest, like Python's negative indexing. If i is out of bounds, "" is returned.
|
||||||
|
func (o *DataFrame) String(column string, i int) string {
|
||||||
|
val := o.Value(column, i)
|
||||||
|
if val == nil {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
switch val.(type) {
|
||||||
|
case string:
|
||||||
|
return val.(string)
|
||||||
|
default:
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Time returns the value of the column at index i casted to time.Time. The first value is at index 0. A negative value for i (-n) can be used to get n values from the latest, like Python's negative indexing. If i is out of bounds, time.Time{} is returned.
|
||||||
|
func (o *DataFrame) Time(column string, i int) time.Time {
|
||||||
|
val := o.Value(column, i)
|
||||||
|
if val == nil {
|
||||||
|
return time.Time{}
|
||||||
|
}
|
||||||
|
switch val.(type) {
|
||||||
|
case time.Time:
|
||||||
|
return val.(time.Time)
|
||||||
|
default:
|
||||||
|
return time.Time{}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewChartData(data *df.DataFrame) *DataFrame {
|
||||||
|
return &DataFrame{data}
|
||||||
|
}
|
||||||
|
|
||||||
|
type RollingWindow struct {
|
||||||
|
DataFrame
|
||||||
|
Period int
|
||||||
|
}
|
||||||
|
|
||||||
type DataCSVLayout struct {
|
type DataCSVLayout struct {
|
||||||
LatestFirst bool // Whether the latest data is first in the dataframe. If false, the latest data is last.
|
LatestFirst bool // Whether the latest data is first in the dataframe. If false, the latest data is last.
|
||||||
DateFormat string // The format of the date column. Example: "03/22/2006". See https://pkg.go.dev/time#pkg-constants for more information.
|
DateFormat string // The format of the date column. Example: "03/22/2006". See https://pkg.go.dev/time#pkg-constants for more information.
|
||||||
|
62
signals.go
Normal file
62
signals.go
Normal file
@@ -0,0 +1,62 @@
|
|||||||
|
package autotrader
|
||||||
|
|
||||||
|
import "reflect"
|
||||||
|
|
||||||
|
type Signaler interface {
|
||||||
|
SignalConnect(signal string, handler func(interface{})) error // SignalConnect connects the handler to the signal.
|
||||||
|
SignalConnected(signal string, handler func(interface{})) bool // SignalConnected returns true if the handler is connected to the signal.
|
||||||
|
SignalConnections(signal string) []func(interface{}) // SignalConnections returns a slice of handlers connected to the signal.
|
||||||
|
SignalDisconnect(signal string, handler func(interface{})) // SignalDisconnect removes the handler from the signal.
|
||||||
|
SignalEmit(signal string, data interface{}) // SignalEmit emits the signal with the data.
|
||||||
|
}
|
||||||
|
|
||||||
|
type SignalManager struct {
|
||||||
|
signalConnections map[string][]func(interface{})
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *SignalManager) SignalConnect(signal string, handler func(interface{})) error {
|
||||||
|
if s.signalConnections == nil {
|
||||||
|
s.signalConnections = make(map[string][]func(interface{}))
|
||||||
|
}
|
||||||
|
s.signalConnections[signal] = append(s.signalConnections[signal], handler)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *SignalManager) SignalConnected(signal string, handler func(interface{})) bool {
|
||||||
|
if s.signalConnections == nil {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
for _, h := range s.signalConnections[signal] {
|
||||||
|
if reflect.ValueOf(h).Pointer() == reflect.ValueOf(handler).Pointer() {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *SignalManager) SignalConnections(signal string) []func(interface{}) {
|
||||||
|
if s.signalConnections == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return s.signalConnections[signal]
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *SignalManager) SignalDisconnect(signal string, handler func(interface{})) {
|
||||||
|
if s.signalConnections == nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
for i, h := range s.signalConnections[signal] {
|
||||||
|
if reflect.ValueOf(h).Pointer() == reflect.ValueOf(handler).Pointer() {
|
||||||
|
s.signalConnections[signal] = append(s.signalConnections[signal][:i], s.signalConnections[signal][i+1:]...)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *SignalManager) SignalEmit(signal string, data interface{}) {
|
||||||
|
if s.signalConnections == nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
for _, handler := range s.signalConnections[signal] {
|
||||||
|
handler(data)
|
||||||
|
}
|
||||||
|
}
|
Reference in New Issue
Block a user