mirror of
https://github.com/lukewilson2002/autotrader.git
synced 2025-06-15 08:23:51 +00:00
Worked on several new features at once
This commit is contained in:
parent
4a12b93992
commit
435ce1e144
@ -10,15 +10,26 @@ import (
|
||||
)
|
||||
|
||||
var (
|
||||
ErrEOF = errors.New("end of the input data")
|
||||
ErrNoData = errors.New("no data")
|
||||
ErrEOF = errors.New("end of the input data")
|
||||
ErrNoData = errors.New("no data")
|
||||
ErrPositionClosed = errors.New("position closed")
|
||||
)
|
||||
|
||||
func Backtest(trader *Trader) {
|
||||
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 {
|
||||
SignalManager
|
||||
DataBroker Broker
|
||||
Data *df.DataFrame
|
||||
Cash float64
|
||||
@ -110,6 +121,7 @@ func (b *TestBroker) MarketOrder(symbol string, units float64, stopLoss, takePro
|
||||
|
||||
b.orders = append(b.orders, order)
|
||||
b.positions = append(b.positions, position)
|
||||
b.SignalEmit("OrderPlaced", order)
|
||||
|
||||
return order, nil
|
||||
}
|
||||
@ -138,6 +150,63 @@ func NewTestBroker(dataBroker Broker, data *df.DataFrame, cash, leverage, spread
|
||||
}
|
||||
|
||||
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 {
|
||||
|
15
broker.go
15
broker.go
@ -38,6 +38,17 @@ type Order 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 {
|
||||
@ -45,6 +56,10 @@ type Broker interface {
|
||||
Candles(symbol string, frequency string, count int) (*df.DataFrame, error)
|
||||
MarketOrder(symbol string, units float64, stopLoss, takeProfit float64) (Order, error)
|
||||
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
|
||||
// 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
|
||||
}
|
||||
|
173
data.go
173
data.go
@ -11,6 +11,179 @@ import (
|
||||
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 {
|
||||
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.
|
||||
|
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)
|
||||
}
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user