mirror of
https://github.com/lukewilson2002/autotrader.git
synced 2025-06-15 08:23:51 +00:00
Add indexed frame shifting
This commit is contained in:
parent
ee67d5c170
commit
30b6482fbf
244
frame_indexed.go
244
frame_indexed.go
@ -20,6 +20,12 @@ func (t UnixTime) String() string {
|
|||||||
return t.Time().String()
|
return t.Time().String()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func UnixTimeStep(frequency time.Duration) func(UnixTime, int) UnixTime {
|
||||||
|
return func(t UnixTime, amt int) UnixTime {
|
||||||
|
return UnixTime(t.Time().Add(frequency * time.Duration(amt)).Unix())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// It is worth mentioning that if you want to use time.Time as an index type, then you should use the public UnixTime as a Unix int64 time which can be converted back into a time.Time easily. See [time.Time](https://pkg.go.dev/time#Time) for more information on why you should not compare Time with == (or a map, which is what the IndexedFrame uses).
|
// It is worth mentioning that if you want to use time.Time as an index type, then you should use the public UnixTime as a Unix int64 time which can be converted back into a time.Time easily. See [time.Time](https://pkg.go.dev/time#Time) for more information on why you should not compare Time with == (or a map, which is what the IndexedFrame uses).
|
||||||
type IndexedFrame[I comparable] struct {
|
type IndexedFrame[I comparable] struct {
|
||||||
*SignalManager
|
*SignalManager
|
||||||
@ -28,12 +34,12 @@ type IndexedFrame[I comparable] struct {
|
|||||||
|
|
||||||
// It is worth mentioning that if you want to use time.Time as an index type, then you should use int64 as a Unix time. See [time.Time](https://pkg.go.dev/time#Time) for more information on why you should not compare Time with == (or a map, which is what the IndexedFrame uses).
|
// It is worth mentioning that if you want to use time.Time as an index type, then you should use int64 as a Unix time. See [time.Time](https://pkg.go.dev/time#Time) for more information on why you should not compare Time with == (or a map, which is what the IndexedFrame uses).
|
||||||
func NewIndexedFrame[I comparable](series ...*IndexedSeries[I]) *IndexedFrame[I] {
|
func NewIndexedFrame[I comparable](series ...*IndexedSeries[I]) *IndexedFrame[I] {
|
||||||
d := &IndexedFrame[I]{
|
f := &IndexedFrame[I]{
|
||||||
&SignalManager{},
|
&SignalManager{},
|
||||||
make(map[string]*IndexedSeries[I], len(series)),
|
make(map[string]*IndexedSeries[I], len(series)),
|
||||||
}
|
}
|
||||||
d.PushSeries(series...)
|
f.PushSeries(series...)
|
||||||
return d
|
return f
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewDOHLCVIndexedFrame returns a IndexedFrame with empty Date, Open, High, Low, Close, and Volume columns.
|
// NewDOHLCVIndexedFrame returns a IndexedFrame with empty Date, Open, High, Low, Close, and Volume columns.
|
||||||
@ -49,8 +55,8 @@ func NewDOHLCVIndexedFrame[I comparable]() *IndexedFrame[I] {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Copy is the same as CopyRange(0, -1)
|
// Copy is the same as CopyRange(0, -1)
|
||||||
func (d *IndexedFrame[I]) Copy() *IndexedFrame[I] {
|
func (f *IndexedFrame[I]) Copy() *IndexedFrame[I] {
|
||||||
return d.CopyRange(0, -1)
|
return f.CopyRange(0, -1)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Copy returns a new IndexedFrame with a copy of the original series. start is an EasyIndex and count is the number of rows to copy from start onward. If count is negative then all rows from start to the end of the IndexedFrame are copied. If there are not enough rows to copy then the maximum amount is returned. If there are no items to copy then a IndexedFrame will be returned with a length of zero but with the same column names as the original.
|
// Copy returns a new IndexedFrame with a copy of the original series. start is an EasyIndex and count is the number of rows to copy from start onward. If count is negative then all rows from start to the end of the IndexedFrame are copied. If there are not enough rows to copy then the maximum amount is returned. If there are no items to copy then a IndexedFrame will be returned with a length of zero but with the same column names as the original.
|
||||||
@ -60,21 +66,21 @@ func (d *IndexedFrame[I]) Copy() *IndexedFrame[I] {
|
|||||||
// Copy(0, 10) - copy the first 10 rows
|
// Copy(0, 10) - copy the first 10 rows
|
||||||
// Copy(-1, 1) - copy the last row
|
// Copy(-1, 1) - copy the last row
|
||||||
// Copy(-10, -1) - copy the last 10 rows
|
// Copy(-10, -1) - copy the last 10 rows
|
||||||
func (d *IndexedFrame[I]) CopyRange(start, count int) *IndexedFrame[I] {
|
func (f *IndexedFrame[I]) CopyRange(start, count int) *IndexedFrame[I] {
|
||||||
out := &IndexedFrame[I]{SignalManager: &SignalManager{}}
|
out := &IndexedFrame[I]{SignalManager: &SignalManager{}}
|
||||||
for _, s := range d.series {
|
for _, s := range f.series {
|
||||||
out.PushSeries(s.CopyRange(start, count))
|
out.PushSeries(s.CopyRange(start, count))
|
||||||
}
|
}
|
||||||
return out
|
return out
|
||||||
}
|
}
|
||||||
|
|
||||||
// Len returns the number of rows in the IndexedFrame or 0 if the IndexedFrame has no rows. If the IndexedFrame has series of different lengths, then the longest length series is returned.
|
// Len returns the number of rows in the IndexedFrame or 0 if the IndexedFrame has no rows. If the IndexedFrame has series of different lengths, then the longest length series is returned.
|
||||||
func (d *IndexedFrame[I]) Len() int {
|
func (f *IndexedFrame[I]) Len() int {
|
||||||
if len(d.series) == 0 {
|
if len(f.series) == 0 {
|
||||||
return 0
|
return 0
|
||||||
}
|
}
|
||||||
var length int
|
var length int
|
||||||
for _, s := range d.series {
|
for _, s := range f.series {
|
||||||
if s.Len() > length {
|
if s.Len() > length {
|
||||||
length = s.Len()
|
length = s.Len()
|
||||||
}
|
}
|
||||||
@ -83,10 +89,10 @@ func (d *IndexedFrame[I]) Len() int {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Select returns a new IndexedFrame with the selected Series. The series are not copied so the returned IndexedFrame will be a reference to the current IndexedFrame. If a series name is not found, it is ignored.
|
// Select returns a new IndexedFrame with the selected Series. The series are not copied so the returned IndexedFrame will be a reference to the current IndexedFrame. If a series name is not found, it is ignored.
|
||||||
func (d *IndexedFrame[I]) Select(names ...string) *IndexedFrame[I] {
|
func (f *IndexedFrame[I]) Select(names ...string) *IndexedFrame[I] {
|
||||||
out := &IndexedFrame[I]{SignalManager: &SignalManager{}}
|
out := &IndexedFrame[I]{SignalManager: &SignalManager{}}
|
||||||
for _, name := range names {
|
for _, name := range names {
|
||||||
if s := d.Series(name); s != nil {
|
if s := f.Series(name); s != nil {
|
||||||
out.PushSeries(s)
|
out.PushSeries(s)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -103,19 +109,19 @@ func (d *IndexedFrame[I]) Select(names ...string) *IndexedFrame[I] {
|
|||||||
// The order of the columns is not defined.
|
// The order of the columns is not defined.
|
||||||
//
|
//
|
||||||
// If the IndexedFrame has more than 20 rows, the output will include the first ten rows and the last ten rows.
|
// If the IndexedFrame has more than 20 rows, the output will include the first ten rows and the last ten rows.
|
||||||
func (d *IndexedFrame[I]) String() string {
|
func (f *IndexedFrame[I]) String() string {
|
||||||
if d == nil {
|
if f == nil {
|
||||||
return fmt.Sprintf("%T[nil]", d)
|
return fmt.Sprintf("%T[nil]", f)
|
||||||
}
|
}
|
||||||
names := d.Names() // Defines the order of the columns.
|
names := f.Names() // Defines the order of the columns.
|
||||||
series := make([]*IndexedSeries[I], len(names))
|
series := make([]*IndexedSeries[I], len(names))
|
||||||
for i, name := range names {
|
for i, name := range names {
|
||||||
series[i] = d.Series(name)
|
series[i] = f.Series(name)
|
||||||
}
|
}
|
||||||
|
|
||||||
buffer := new(bytes.Buffer)
|
buffer := new(bytes.Buffer)
|
||||||
t := tabwriter.NewWriter(buffer, 0, 0, 2, ' ', 0)
|
t := tabwriter.NewWriter(buffer, 0, 0, 2, ' ', 0)
|
||||||
fmt.Fprintf(t, "%T[%dx%d]\n", d, d.Len(), len(series))
|
fmt.Fprintf(t, "%T[%dx%d]\n", f, f.Len(), len(series))
|
||||||
fmt.Fprintf(t, "[Row]\t[Index]\t%s\t\n", strings.Join(names, "\t"))
|
fmt.Fprintf(t, "[Row]\t[Index]\t%s\t\n", strings.Join(names, "\t"))
|
||||||
|
|
||||||
printRow := func(row int, index I) {
|
printRow := func(row int, index I) {
|
||||||
@ -136,7 +142,7 @@ func (d *IndexedFrame[I]) String() string {
|
|||||||
|
|
||||||
indexes := maps.Keys(series[0].index)
|
indexes := maps.Keys(series[0].index)
|
||||||
// Print the first ten rows and the last ten rows if the IndexedFrame has more than 20 rows.
|
// Print the first ten rows and the last ten rows if the IndexedFrame has more than 20 rows.
|
||||||
if d.Len() > 20 {
|
if f.Len() > 20 {
|
||||||
for i := 0; i < 10; i++ {
|
for i := 0; i < 10; i++ {
|
||||||
printRow(i, indexes[i])
|
printRow(i, indexes[i])
|
||||||
}
|
}
|
||||||
@ -149,7 +155,7 @@ func (d *IndexedFrame[I]) String() string {
|
|||||||
printRow(i, indexes[len(indexes)-i])
|
printRow(i, indexes[len(indexes)-i])
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
for i := 0; i < d.Len(); i++ {
|
for i := 0; i < f.Len(); i++ {
|
||||||
printRow(i, indexes[i])
|
printRow(i, indexes[i])
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -158,9 +164,9 @@ func (d *IndexedFrame[I]) String() string {
|
|||||||
return buffer.String()
|
return buffer.String()
|
||||||
}
|
}
|
||||||
|
|
||||||
func (d *IndexedFrame[I]) Index(row int) *I {
|
func (f *IndexedFrame[I]) Index(row int) *I {
|
||||||
var index *I
|
var index *I
|
||||||
d.ForEachSeries(func(s *IndexedSeries[I]) {
|
f.ForEachSeries(func(s *IndexedSeries[I]) {
|
||||||
if index == nil {
|
if index == nil {
|
||||||
index = s.Index(row)
|
index = s.Index(row)
|
||||||
} else if i := s.Index(row); i == nil || *index != *i {
|
} else if i := s.Index(row); i == nil || *index != *i {
|
||||||
@ -171,89 +177,89 @@ func (d *IndexedFrame[I]) Index(row int) *I {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Date returns the value of the Date column at index i. i is an EasyIndex. If i is out of bounds, time.Time{} is returned. This is equivalent to calling Index(i).
|
// Date returns the value of the Date column at index i. i is an EasyIndex. If i is out of bounds, time.Time{} is returned. This is equivalent to calling Index(i).
|
||||||
func (d *IndexedFrame[I]) Date(i int) *I {
|
func (f *IndexedFrame[I]) Date(i int) *I {
|
||||||
return d.Index(i)
|
return f.Index(i)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Open returns the open price of the candle at index i. i is an EasyIndex. If i is out of bounds, 0 is returned. This is the equivalent to calling Float("Open", i).
|
// Open returns the open price of the candle at index i. i is an EasyIndex. If i is out of bounds, 0 is returned. This is the equivalent to calling Float("Open", i).
|
||||||
func (d *IndexedFrame[I]) Open(i int) float64 {
|
func (f *IndexedFrame[I]) Open(i int) float64 {
|
||||||
return d.Float("Open", i)
|
return f.Float("Open", i)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (d *IndexedFrame[I]) OpenIndex(index I) float64 {
|
func (f *IndexedFrame[I]) OpenIndex(index I) float64 {
|
||||||
return d.FloatIndex("Open", index)
|
return f.FloatIndex("Open", index)
|
||||||
}
|
}
|
||||||
|
|
||||||
// High returns the high price of the candle at index i. i is an EasyIndex. If i is out of bounds, 0 is returned. This is the equivalent to calling Float("High", i).
|
// High returns the high price of the candle at index i. i is an EasyIndex. If i is out of bounds, 0 is returned. This is the equivalent to calling Float("High", i).
|
||||||
func (d *IndexedFrame[I]) High(i int) float64 {
|
func (f *IndexedFrame[I]) High(i int) float64 {
|
||||||
return d.Float("High", i)
|
return f.Float("High", i)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (d *IndexedFrame[I]) HighIndex(index I) float64 {
|
func (f *IndexedFrame[I]) HighIndex(index I) float64 {
|
||||||
return d.FloatIndex("High", index)
|
return f.FloatIndex("High", index)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Low returns the low price of the candle at index i. i is an EasyIndex. If i is out of bounds, 0 is returned. This is the equivalent to calling Float("Low", i).
|
// Low returns the low price of the candle at index i. i is an EasyIndex. If i is out of bounds, 0 is returned. This is the equivalent to calling Float("Low", i).
|
||||||
func (d *IndexedFrame[I]) Low(i int) float64 {
|
func (f *IndexedFrame[I]) Low(i int) float64 {
|
||||||
return d.Float("Low", i)
|
return f.Float("Low", i)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (d *IndexedFrame[I]) LowIndex(index I) float64 {
|
func (f *IndexedFrame[I]) LowIndex(index I) float64 {
|
||||||
return d.FloatIndex("Low", index)
|
return f.FloatIndex("Low", index)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Close returns the close price of the candle at index i. i is an EasyIndex. If i is out of bounds, 0 is returned. This is the equivalent to calling Float("Close", i).
|
// Close returns the close price of the candle at index i. i is an EasyIndex. If i is out of bounds, 0 is returned. This is the equivalent to calling Float("Close", i).
|
||||||
func (d *IndexedFrame[I]) Close(i int) float64 {
|
func (f *IndexedFrame[I]) Close(i int) float64 {
|
||||||
return d.Float("Close", i)
|
return f.Float("Close", i)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (d *IndexedFrame[I]) CloseIndex(index I) float64 {
|
func (f *IndexedFrame[I]) CloseIndex(index I) float64 {
|
||||||
return d.FloatIndex("Close", index)
|
return f.FloatIndex("Close", index)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Volume returns the volume of the candle at index i. i is an EasyIndex. If i is out of bounds, 0 is returned. This is the equivalent to calling Float("Volume", i).
|
// Volume returns the volume of the candle at index i. i is an EasyIndex. If i is out of bounds, 0 is returned. This is the equivalent to calling Float("Volume", i).
|
||||||
func (d *IndexedFrame[I]) Volume(i int) int {
|
func (f *IndexedFrame[I]) Volume(i int) int {
|
||||||
return d.Int("Volume", i)
|
return f.Int("Volume", i)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (d *IndexedFrame[I]) VolumeIndex(index I) int {
|
func (f *IndexedFrame[I]) VolumeIndex(index I) int {
|
||||||
return d.IntIndex("Volume", index)
|
return f.IntIndex("Volume", index)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Dates returns a Series of all the dates in the IndexedFrame. This is equivalent to calling Series("Date").
|
// Dates returns a Series of all the dates in the IndexedFrame. This is equivalent to calling Series("Date").
|
||||||
func (d *IndexedFrame[I]) Dates() *IndexedSeries[I] {
|
func (f *IndexedFrame[I]) Dates() *IndexedSeries[I] {
|
||||||
return d.Series("Date")
|
return f.Series("Date")
|
||||||
}
|
}
|
||||||
|
|
||||||
// Opens returns a FloatSeries of all the open prices in the IndexedFrame. This is equivalent to calling Series("Open").
|
// Opens returns a FloatSeries of all the open prices in the IndexedFrame. This is equivalent to calling Series("Open").
|
||||||
func (d *IndexedFrame[I]) Opens() *IndexedSeries[I] {
|
func (f *IndexedFrame[I]) Opens() *IndexedSeries[I] {
|
||||||
return d.Series("Open")
|
return f.Series("Open")
|
||||||
}
|
}
|
||||||
|
|
||||||
// Highs returns a FloatSeries of all the high prices in the IndexedFrame. This is equivalent to calling Series("High").
|
// Highs returns a FloatSeries of all the high prices in the IndexedFrame. This is equivalent to calling Series("High").
|
||||||
func (d *IndexedFrame[I]) Highs() *IndexedSeries[I] {
|
func (f *IndexedFrame[I]) Highs() *IndexedSeries[I] {
|
||||||
return d.Series("High")
|
return f.Series("High")
|
||||||
}
|
}
|
||||||
|
|
||||||
// Lows returns a FloatSeries of all the low prices in the IndexedFrame. This is equivalent to calling Series("Low").
|
// Lows returns a FloatSeries of all the low prices in the IndexedFrame. This is equivalent to calling Series("Low").
|
||||||
func (d *IndexedFrame[I]) Lows() *IndexedSeries[I] {
|
func (f *IndexedFrame[I]) Lows() *IndexedSeries[I] {
|
||||||
return d.Series("Low")
|
return f.Series("Low")
|
||||||
}
|
}
|
||||||
|
|
||||||
// Closes returns a FloatSeries of all the close prices in the IndexedFrame. This is equivalent to calling Series("Close").
|
// Closes returns a FloatSeries of all the close prices in the IndexedFrame. This is equivalent to calling Series("Close").
|
||||||
func (d *IndexedFrame[I]) Closes() *IndexedSeries[I] {
|
func (f *IndexedFrame[I]) Closes() *IndexedSeries[I] {
|
||||||
return d.Series("Close")
|
return f.Series("Close")
|
||||||
}
|
}
|
||||||
|
|
||||||
// Volumes returns a Series of all the volumes in the IndexedFrame. This is equivalent to calling Series("Volume").
|
// Volumes returns a Series of all the volumes in the IndexedFrame. This is equivalent to calling Series("Volume").
|
||||||
func (d *IndexedFrame[I]) Volumes() *IndexedSeries[I] {
|
func (f *IndexedFrame[I]) Volumes() *IndexedSeries[I] {
|
||||||
return d.Series("Volume")
|
return f.Series("Volume")
|
||||||
}
|
}
|
||||||
|
|
||||||
// Contains returns true if the IndexedFrame contains all the given series names. Remember that names are case sensitive.
|
// Contains returns true if the IndexedFrame contains all the given series names. Remember that names are case sensitive.
|
||||||
func (d *IndexedFrame[I]) Contains(names ...string) bool {
|
func (f *IndexedFrame[I]) Contains(names ...string) bool {
|
||||||
for _, name := range names {
|
for _, name := range names {
|
||||||
if _, ok := d.series[name]; !ok {
|
if _, ok := f.series[name]; !ok {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -261,79 +267,79 @@ func (d *IndexedFrame[I]) Contains(names ...string) bool {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// ContainsDOHLCV returns true if the IndexedFrame contains the series "Date", "Open", "High", "Low", "Close", and "Volume". This is equivalent to calling Contains("Date", "Open", "High", "Low", "Close", "Volume").
|
// ContainsDOHLCV returns true if the IndexedFrame contains the series "Date", "Open", "High", "Low", "Close", and "Volume". This is equivalent to calling Contains("Date", "Open", "High", "Low", "Close", "Volume").
|
||||||
func (d *IndexedFrame[I]) ContainsDOHLCV() bool {
|
func (f *IndexedFrame[I]) ContainsDOHLCV() bool {
|
||||||
return d.Contains("Open", "High", "Low", "Close", "Volume")
|
return f.Contains("Open", "High", "Low", "Close", "Volume")
|
||||||
}
|
}
|
||||||
|
|
||||||
// PushCandle pushes a candlestick to the IndexedFrame. If the IndexedFrame does not contain the series "Date", "Open", "High", "Low", "Close", and "Volume", an error is returned.
|
// PushCandle pushes a candlestick to the IndexedFrame. If the IndexedFrame does not contain the series "Date", "Open", "High", "Low", "Close", and "Volume", an error is returned.
|
||||||
func (d *IndexedFrame[I]) PushCandle(date I, open, high, low, close float64, volume int64) error {
|
func (f *IndexedFrame[I]) PushCandle(date I, open, high, low, close float64, volume int64) error {
|
||||||
if !d.ContainsDOHLCV() {
|
if !f.ContainsDOHLCV() {
|
||||||
return fmt.Errorf("IndexedFrame does not contain Open, High, Low, Close, Volume columns")
|
return fmt.Errorf("IndexedFrame does not contain Open, High, Low, Close, Volume columns")
|
||||||
}
|
}
|
||||||
d.series["Open"].Push(date, open)
|
f.series["Open"].Push(date, open)
|
||||||
d.series["High"].Push(date, high)
|
f.series["High"].Push(date, high)
|
||||||
d.series["Low"].Push(date, low)
|
f.series["Low"].Push(date, low)
|
||||||
d.series["Close"].Push(date, close)
|
f.series["Close"].Push(date, close)
|
||||||
d.series["Volume"].Push(date, volume)
|
f.series["Volume"].Push(date, volume)
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// PushSeries adds the given series to the IndexedFrame. If the IndexedFrame already contains a series with the same name, an error is returned.
|
// PushSeries adds the given series to the IndexedFrame. If the IndexedFrame already contains a series with the same name, an error is returned.
|
||||||
func (d *IndexedFrame[I]) PushSeries(series ...*IndexedSeries[I]) error {
|
func (f *IndexedFrame[I]) PushSeries(series ...*IndexedSeries[I]) error {
|
||||||
if d.series == nil {
|
if f.series == nil {
|
||||||
d.series = make(map[string]*IndexedSeries[I], len(series))
|
f.series = make(map[string]*IndexedSeries[I], len(series))
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, s := range series {
|
for _, s := range series {
|
||||||
name := s.Name()
|
name := s.Name()
|
||||||
if _, ok := d.series[name]; ok {
|
if _, ok := f.series[name]; ok {
|
||||||
return fmt.Errorf("IndexedFrame already contains column %q", name)
|
return fmt.Errorf("IndexedFrame already contains column %q", name)
|
||||||
}
|
}
|
||||||
s.SignalConnect("NameChanged", d, d.onSeriesNameChanged, name)
|
s.SignalConnect("NameChanged", f, f.onSeriesNameChanged, name)
|
||||||
d.series[name] = s
|
f.series[name] = s
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// RemoveSeries removes the given series from the IndexedFrame. If the IndexedFrame does not contain a series with a given name, nothing happens.
|
// RemoveSeries removes the given series from the IndexedFrame. If the IndexedFrame does not contain a series with a given name, nothing happens.
|
||||||
func (d *IndexedFrame[I]) RemoveSeries(names ...string) {
|
func (f *IndexedFrame[I]) RemoveSeries(names ...string) {
|
||||||
for _, name := range names {
|
for _, name := range names {
|
||||||
s, ok := d.series[name]
|
s, ok := f.series[name]
|
||||||
if !ok {
|
if !ok {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
s.SignalDisconnect("NameChanged", d, d.onSeriesNameChanged)
|
s.SignalDisconnect("NameChanged", f, f.onSeriesNameChanged)
|
||||||
delete(d.series, name)
|
delete(f.series, name)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (d *IndexedFrame[I]) onSeriesNameChanged(args ...any) {
|
func (f *IndexedFrame[I]) onSeriesNameChanged(args ...any) {
|
||||||
if len(args) != 2 {
|
if len(args) != 2 {
|
||||||
panic(fmt.Sprintf("expected two arguments, got %d", len(args)))
|
panic(fmt.Sprintf("expected two arguments, got %d", len(args)))
|
||||||
}
|
}
|
||||||
newName := args[0].(string)
|
newName := args[0].(string)
|
||||||
oldName := args[1].(string)
|
oldName := args[1].(string)
|
||||||
|
|
||||||
d.series[newName] = d.series[oldName]
|
f.series[newName] = f.series[oldName]
|
||||||
delete(d.series, oldName)
|
delete(f.series, oldName)
|
||||||
|
|
||||||
// Reconnect our signal handlers to update the name we use in the handlers.
|
// Reconnect our signal handlers to update the name we use in the handlers.
|
||||||
d.series[newName].SignalDisconnect("NameChanged", d, d.onSeriesNameChanged)
|
f.series[newName].SignalDisconnect("NameChanged", f, f.onSeriesNameChanged)
|
||||||
d.series[newName].SignalConnect("NameChanged", d, d.onSeriesNameChanged, newName)
|
f.series[newName].SignalConnect("NameChanged", f, f.onSeriesNameChanged, newName)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Names returns a slice of the names of the series in the IndexedFrame.
|
// Names returns a slice of the names of the series in the IndexedFrame.
|
||||||
func (d *IndexedFrame[I]) Names() []string {
|
func (f *IndexedFrame[I]) Names() []string {
|
||||||
return maps.Keys(d.series)
|
return maps.Keys(f.series)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Series returns a Series of the column with the given name. If the column does not exist, nil is returned.
|
// Series returns a Series of the column with the given name. If the column does not exist, nil is returned.
|
||||||
func (d *IndexedFrame[I]) Series(name string) *IndexedSeries[I] {
|
func (f *IndexedFrame[I]) Series(name string) *IndexedSeries[I] {
|
||||||
if len(d.series) == 0 {
|
if len(f.series) == 0 {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
v, ok := d.series[name]
|
v, ok := f.series[name]
|
||||||
if !ok {
|
if !ok {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
@ -341,37 +347,37 @@ func (d *IndexedFrame[I]) Series(name string) *IndexedSeries[I] {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Value returns the value of the column at index i. i is an EasyIndex. If i is out of bounds, nil is returned.
|
// Value returns the value of the column at index i. i is an EasyIndex. If i is out of bounds, nil is returned.
|
||||||
func (d *IndexedFrame[I]) Value(column string, i int) any {
|
func (f *IndexedFrame[I]) Value(column string, i int) any {
|
||||||
if len(d.series) == 0 {
|
if len(f.series) == 0 {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
if s, ok := d.series[column]; ok {
|
if s, ok := f.series[column]; ok {
|
||||||
return s.Value(i)
|
return s.Value(i)
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (d *IndexedFrame[I]) ValueIndex(column string, index I) any {
|
func (f *IndexedFrame[I]) ValueIndex(column string, index I) any {
|
||||||
if len(d.series) == 0 {
|
if len(f.series) == 0 {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
if s, ok := d.series[column]; ok {
|
if s, ok := f.series[column]; ok {
|
||||||
return s.ValueIndex(index)
|
return s.ValueIndex(index)
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// Float returns the float64 value of the column at index i. i is an EasyIndex. If i is out of bounds or the value was not a float64, then 0 is returned.
|
// Float returns the float64 value of the column at index i. i is an EasyIndex. If i is out of bounds or the value was not a float64, then 0 is returned.
|
||||||
func (d *IndexedFrame[I]) Float(column string, i int) float64 {
|
func (f *IndexedFrame[I]) Float(column string, i int) float64 {
|
||||||
val, ok := d.Value(column, i).(float64)
|
val, ok := f.Value(column, i).(float64)
|
||||||
if !ok {
|
if !ok {
|
||||||
return 0
|
return 0
|
||||||
}
|
}
|
||||||
return val
|
return val
|
||||||
}
|
}
|
||||||
|
|
||||||
func (d *IndexedFrame[I]) FloatIndex(column string, index I) float64 {
|
func (f *IndexedFrame[I]) FloatIndex(column string, index I) float64 {
|
||||||
val, ok := d.ValueIndex(column, index).(float64)
|
val, ok := f.ValueIndex(column, index).(float64)
|
||||||
if !ok {
|
if !ok {
|
||||||
return 0
|
return 0
|
||||||
}
|
}
|
||||||
@ -379,16 +385,16 @@ func (d *IndexedFrame[I]) FloatIndex(column string, index I) float64 {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Int returns the int value of the column at index i. i is an EasyIndex. If i is out of bounds or the value was not an int, then 0 is returned.
|
// Int returns the int value of the column at index i. i is an EasyIndex. If i is out of bounds or the value was not an int, then 0 is returned.
|
||||||
func (d *IndexedFrame[I]) Int(column string, i int) int {
|
func (f *IndexedFrame[I]) Int(column string, i int) int {
|
||||||
val, ok := d.Value(column, i).(int)
|
val, ok := f.Value(column, i).(int)
|
||||||
if !ok {
|
if !ok {
|
||||||
return 0
|
return 0
|
||||||
}
|
}
|
||||||
return val
|
return val
|
||||||
}
|
}
|
||||||
|
|
||||||
func (d *IndexedFrame[I]) IntIndex(column string, index I) int {
|
func (f *IndexedFrame[I]) IntIndex(column string, index I) int {
|
||||||
val, ok := d.ValueIndex(column, index).(int)
|
val, ok := f.ValueIndex(column, index).(int)
|
||||||
if !ok {
|
if !ok {
|
||||||
return 0
|
return 0
|
||||||
}
|
}
|
||||||
@ -396,16 +402,16 @@ func (d *IndexedFrame[I]) IntIndex(column string, index I) int {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Str returns the string value of the column at index i. i is an EasyIndex. If i is out of bounds or the value was not a string, then the empty string "" is returned.
|
// Str returns the string value of the column at index i. i is an EasyIndex. If i is out of bounds or the value was not a string, then the empty string "" is returned.
|
||||||
func (d *IndexedFrame[I]) Str(column string, i int) string {
|
func (f *IndexedFrame[I]) Str(column string, i int) string {
|
||||||
val, ok := d.Value(column, i).(string)
|
val, ok := f.Value(column, i).(string)
|
||||||
if !ok {
|
if !ok {
|
||||||
return ""
|
return ""
|
||||||
}
|
}
|
||||||
return val
|
return val
|
||||||
}
|
}
|
||||||
|
|
||||||
func (d *IndexedFrame[I]) StrIndex(column string, index I) string {
|
func (f *IndexedFrame[I]) StrIndex(column string, index I) string {
|
||||||
val, ok := d.ValueIndex(column, index).(string)
|
val, ok := f.ValueIndex(column, index).(string)
|
||||||
if !ok {
|
if !ok {
|
||||||
return ""
|
return ""
|
||||||
}
|
}
|
||||||
@ -413,24 +419,38 @@ func (d *IndexedFrame[I]) StrIndex(column string, index I) string {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Time returns the time.Time value of the column at index i. i is an EasyIndex. If i is out of bounds or the value was not a Time, then time.Time{} is returned. Use Time.IsZero() to check if the value was valid.
|
// Time returns the time.Time value of the column at index i. i is an EasyIndex. If i is out of bounds or the value was not a Time, then time.Time{} is returned. Use Time.IsZero() to check if the value was valid.
|
||||||
func (d *IndexedFrame[I]) Time(column string, i int) time.Time {
|
func (f *IndexedFrame[I]) Time(column string, i int) time.Time {
|
||||||
val, ok := d.Value(column, i).(time.Time)
|
val, ok := f.Value(column, i).(time.Time)
|
||||||
if !ok {
|
if !ok {
|
||||||
return time.Time{}
|
return time.Time{}
|
||||||
}
|
}
|
||||||
return val
|
return val
|
||||||
}
|
}
|
||||||
|
|
||||||
func (d *IndexedFrame[I]) TimeIndex(column string, index I) time.Time {
|
func (f *IndexedFrame[I]) TimeIndex(column string, index I) time.Time {
|
||||||
val, ok := d.ValueIndex(column, index).(time.Time)
|
val, ok := f.ValueIndex(column, index).(time.Time)
|
||||||
if !ok {
|
if !ok {
|
||||||
return time.Time{}
|
return time.Time{}
|
||||||
}
|
}
|
||||||
return val
|
return val
|
||||||
}
|
}
|
||||||
|
|
||||||
func (d *IndexedFrame[I]) ForEachSeries(f func(*IndexedSeries[I])) {
|
func (f *IndexedFrame[I]) ForEachSeries(fn func(*IndexedSeries[I])) {
|
||||||
for _, s := range d.series {
|
for _, s := range f.series {
|
||||||
f(s)
|
fn(s)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (f *IndexedFrame[I]) Shift(periods int, nilValue any) *IndexedFrame[I] {
|
||||||
|
for _, s := range f.series {
|
||||||
|
_ = s.Shift(periods, nilValue)
|
||||||
|
}
|
||||||
|
return f
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f *IndexedFrame[I]) ShiftIndex(periods int, step func(prev I, amt int) I) *IndexedFrame[I] {
|
||||||
|
for _, s := range f.series {
|
||||||
|
_ = s.ShiftIndex(periods, step)
|
||||||
|
}
|
||||||
|
return f
|
||||||
|
}
|
||||||
|
@ -106,6 +106,58 @@ func TestIndexedFrame(t *testing.T) {
|
|||||||
t.Log(data.String())
|
t.Log(data.String())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestIndexedFrameFunctions(t *testing.T) {
|
||||||
|
data := NewDOHLCVIndexedFrame[UnixTime]()
|
||||||
|
data.PushCandle(UnixTime(time.Date(2021, 5, 13, 0, 0, 0, 0, time.UTC).Unix()), 0.8, 1.2, 0.6, 1.0, 1)
|
||||||
|
data.PushCandle(UnixTime(time.Date(2021, 5, 14, 0, 0, 0, 0, time.UTC).Unix()), 1.0, 1.4, 0.8, 1.2, 1)
|
||||||
|
data.PushCandle(UnixTime(time.Date(2021, 5, 15, 0, 0, 0, 0, time.UTC).Unix()), 1.2, 1.6, 1.0, 1.4, 1)
|
||||||
|
|
||||||
|
data.ShiftIndex(2, UnixTimeStep(time.Hour*24)) // Shift 2 days
|
||||||
|
|
||||||
|
if data.Len() != 3 {
|
||||||
|
t.Fatalf("Expected 3 rows, got %d", data.Len())
|
||||||
|
}
|
||||||
|
if data.Close(-1) != 1.4 {
|
||||||
|
t.Fatalf("Expected latest close to be 1.4, got %f", data.Close(-1))
|
||||||
|
}
|
||||||
|
if !data.Date(0).Time().Equal(time.Date(2021, 5, 15, 0, 0, 0, 0, time.UTC)) {
|
||||||
|
t.Fatalf("Expected first date to be 2021-05-15, got %v", data.Date(0))
|
||||||
|
}
|
||||||
|
if !data.Date(-1).Time().Equal(time.Date(2021, 5, 17, 0, 0, 0, 0, time.UTC)) {
|
||||||
|
t.Fatalf("Expected latest date to be 2021-05-17, got %v", data.Date(-1))
|
||||||
|
}
|
||||||
|
|
||||||
|
data.Shift(-2, 0.0) // Shift all rows up by 2 and clear the last 2 rows with zero.
|
||||||
|
|
||||||
|
if data.Len() != 3 {
|
||||||
|
t.Fatalf("Expected 3 rows, got %d", data.Len())
|
||||||
|
}
|
||||||
|
if data.Close(0) != 1.4 {
|
||||||
|
t.Fatalf("Expected latest close to be 1.4, got %f", data.Close(0))
|
||||||
|
}
|
||||||
|
if data.Close(-1) != 0.0 {
|
||||||
|
t.Fatalf("Expected latest close to be 0.0, got %f", data.Close(-1))
|
||||||
|
}
|
||||||
|
if !data.Date(0).Time().Equal(time.Date(2021, 5, 15, 0, 0, 0, 0, time.UTC)) {
|
||||||
|
t.Fatalf("Expected first date to be 2021-05-15, got %v", data.Date(0))
|
||||||
|
}
|
||||||
|
|
||||||
|
data.Shift(1, 0.0) // Shift all rows down by 1 and clear the first row with zero.
|
||||||
|
|
||||||
|
if data.Len() != 3 {
|
||||||
|
t.Fatalf("Expected 3 rows, got %d", data.Len())
|
||||||
|
}
|
||||||
|
if data.Close(0) != 0.0 {
|
||||||
|
t.Fatalf("Expected latest close to be 0.0, got %f", data.Close(0))
|
||||||
|
}
|
||||||
|
if data.Close(1) != 1.4 {
|
||||||
|
t.Fatalf("Expected latest close to be 1.4, got %f", data.Close(1))
|
||||||
|
}
|
||||||
|
if !data.Date(0).Time().Equal(time.Date(2021, 5, 15, 0, 0, 0, 0, time.UTC)) {
|
||||||
|
t.Fatalf("Expected first date to be 2021-05-15, got %v", data.Date(0))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func TestDOHLCVDataFrame(t *testing.T) {
|
func TestDOHLCVDataFrame(t *testing.T) {
|
||||||
data := NewDOHLCVFrame()
|
data := NewDOHLCVFrame()
|
||||||
if !data.ContainsDOHLCV() {
|
if !data.ContainsDOHLCV() {
|
||||||
|
26
series.go
26
series.go
@ -400,6 +400,32 @@ func (s *Series) Rolling(period int) *RollingSeries {
|
|||||||
return NewRollingSeries(s, period)
|
return NewRollingSeries(s, period)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (s *Series) Shift(periods int, nilVal any) *Series {
|
||||||
|
if periods == 0 {
|
||||||
|
return s
|
||||||
|
} else if periods > 0 {
|
||||||
|
// Shift values forward.
|
||||||
|
for i := s.Len() - 1; i >= periods; i-- {
|
||||||
|
s.data[i] = s.data[i-periods]
|
||||||
|
}
|
||||||
|
// Fill in nil values.
|
||||||
|
for i := 0; i < periods; i++ {
|
||||||
|
s.data[i] = nilVal
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
periods = -periods
|
||||||
|
// Shift values backward.
|
||||||
|
for i := 0; i < periods; i++ {
|
||||||
|
s.data[i] = s.data[periods-i]
|
||||||
|
}
|
||||||
|
// Fill in nil values.
|
||||||
|
for i := periods; i < s.Len(); i++ {
|
||||||
|
s.data[i] = nilVal
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return s
|
||||||
|
}
|
||||||
|
|
||||||
type RollingSeries struct {
|
type RollingSeries struct {
|
||||||
series *Series
|
series *Series
|
||||||
period int
|
period int
|
||||||
|
@ -259,6 +259,24 @@ func (s *IndexedSeries[I]) SetValueIndex(index I, val any) *IndexedSeries[I] {
|
|||||||
return s.SetValue(row, val)
|
return s.SetValue(row, val)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (s *IndexedSeries[I]) Shift(periods int, nilValue any) *IndexedSeries[I] {
|
||||||
|
_ = s.series.Shift(periods, nilValue)
|
||||||
|
return s
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *IndexedSeries[I]) ShiftIndex(periods int, step func(prev I, amt int) I) *IndexedSeries[I] {
|
||||||
|
if periods == 0 {
|
||||||
|
return s
|
||||||
|
}
|
||||||
|
// Shift the indexes.
|
||||||
|
newIndexes := make(map[I]int, len(s.index))
|
||||||
|
for index, i := range s.index {
|
||||||
|
newIndexes[step(index, periods)] = i
|
||||||
|
}
|
||||||
|
s.index = newIndexes
|
||||||
|
return s
|
||||||
|
}
|
||||||
|
|
||||||
// Sub subtracts the other series values from this series values. The other series must have the same index type. The values are subtracted by comparing their indexes. For example, subtracting two IndexedSeries that share no indexes will result in no change of values.
|
// Sub subtracts the other series values from this series values. The other series must have the same index type. The values are subtracted by comparing their indexes. For example, subtracting two IndexedSeries that share no indexes will result in no change of values.
|
||||||
func (s *IndexedSeries[I]) Sub(other *IndexedSeries[I]) *IndexedSeries[I] {
|
func (s *IndexedSeries[I]) Sub(other *IndexedSeries[I]) *IndexedSeries[I] {
|
||||||
for index, row := range s.index {
|
for index, row := range s.index {
|
||||||
|
Loading…
x
Reference in New Issue
Block a user