Files
any2anexoj/internal/filler.go
T
natercioandnatercio 9bd4230ff1
Badges / coveralls (push) Successful in 1m4s
Internal state persistence (#27)
Allow us to generate year over year reports without having to rerun everything from the beginning.

Co-authored-by: Natercio Moniz <[email protected]>
2026-08-03 00:33:06 +01:00

139 lines
3.4 KiB
Go

package internal
import (
"container/list"
"github.com/shopspring/decimal"
)
type Filler struct {
Record
filled decimal.Decimal
quantity decimal.Decimal
price decimal.Decimal
}
func NewFiller(r Record) *Filler {
return &Filler{
Record: r,
quantity: r.Quantity(),
price: r.Price(),
}
}
// NewFillerFromState constructs a Filler from a previously persisted state.
// It bypasses the Record-derived defaults so callers can restore a lot that
// may have been split-adjusted or partially filled since the Record was first
// created.
func NewFillerFromState(record Record, quantity, price, filled decimal.Decimal) *Filler {
return &Filler{
Record: record,
filled: filled,
quantity: quantity,
price: price,
}
}
func (f *Filler) Quantity() decimal.Decimal { return f.quantity }
func (f *Filler) Price() decimal.Decimal { return f.price }
// Filled returns how much of the Filler's quantity has already been consumed.
func (f *Filler) Filled() decimal.Decimal { return f.filled }
// Fill accrues some quantity. Returns how mutch was accrued in the 1st return value and whether
// it was filled or not on the 2nd return value.
func (f *Filler) Fill(quantity decimal.Decimal) (decimal.Decimal, bool) {
unfilled := f.quantity.Sub(f.filled)
delta := decimal.Min(unfilled, quantity)
f.filled = f.filled.Add(delta)
return delta, f.IsFilled()
}
// IsFilled returns true if the fill is equal to the record quantity.
func (f *Filler) IsFilled() bool {
return f.filled.Equal(f.quantity)
}
// ApplySplit adjusts the lot for a stock split by the given ratio (newQty/oldQty).
// The total cost basis is preserved: quantity scales up, price scales down proportionally.
func (f *Filler) ApplySplit(ratio decimal.Decimal) {
f.quantity = f.quantity.Mul(ratio)
f.filled = f.filled.Mul(ratio)
f.price = f.price.Div(ratio)
}
type FillerQueue struct {
l *list.List
}
// Push inserts the Filler at the back of the queue.
func (fq *FillerQueue) Push(f *Filler) {
if f == nil {
return
}
if fq == nil {
// This would cause a panic anyway so, we panic with a more meaningful message
panic("Push to nil FillerQueue")
}
if fq.l == nil {
fq.l = list.New()
}
fq.l.PushBack(f)
}
// Pop removes and returns the first Filler of the queue in the 1st return value if there is one. If
// the queue is already empty returns false on the 2nd return value, otherwise returns true.
func (fq *FillerQueue) Pop() (*Filler, bool) {
el := fq.frontElement()
if el == nil {
return nil, false
}
val := fq.l.Remove(el)
return val.(*Filler), true
}
// Peek returns the front Filler of the queue in the 1st return value. If the list is empty returns
// false on the 2nd return value, true otherwise.
func (fq *FillerQueue) Peek() (*Filler, bool) {
el := fq.frontElement()
if el == nil {
return nil, false
}
return el.Value.(*Filler), true
}
func (fq *FillerQueue) frontElement() *list.Element {
if fq == nil || fq.l == nil {
return nil
}
return fq.l.Front()
}
// AdjustForSplit applies a stock split ratio to all lots in the queue.
func (fq *FillerQueue) AdjustForSplit(ratio decimal.Decimal) {
if fq == nil || fq.l == nil {
return
}
for e := fq.l.Front(); e != nil; e = e.Next() {
e.Value.(*Filler).ApplySplit(ratio)
}
}
// Len returns how many elements are currently on the queue
func (fq *FillerQueue) Len() int {
if fq == nil || fq.l == nil {
return 0
}
return fq.l.Len()
}