diff --git a/analysis.go b/analysis.go index 92a055c..d5c7205 100644 --- a/analysis.go +++ b/analysis.go @@ -66,9 +66,10 @@ type LogTradesAnalysis struct { // Analyze logs trades to provided io.Writer func (lta LogTradesAnalysis) Analyze(record *TradingRecord) float64 { logOrder := func(trade *Position) { - fmt.Fprintln(lta.Writer, fmt.Sprintf("%s - enter with buy %s (%s @ $%s)", trade.EntranceOrder().ExecutionTime.UTC().Format(time.RFC822), trade.EntranceOrder().Security, trade.EntranceOrder().Amount, trade.EntranceOrder().Price)) - fmt.Fprintln(lta.Writer, fmt.Sprintf("%s - exit with sell %s (%s @ $%s)", trade.ExitOrder().ExecutionTime.UTC().Format(time.RFC822), trade.ExitOrder().Security, trade.ExitOrder().Amount, trade.ExitOrder().Price)) - + fmt.Fprintln(lta.Writer, fmt.Sprintf("%s - enter with %s %s (%s @ $%s)", + trade.EntranceOrder().ExecutionTime.Format(time.RFC822), trade.EntranceOrder().Side, trade.EntranceOrder().Security, trade.EntranceOrder().Amount, trade.EntranceOrder().Price)) + fmt.Fprintln(lta.Writer, fmt.Sprintf("%s - exit with %s %s (%s @ $%s)", + trade.ExitOrder().ExecutionTime.Format(time.RFC822), trade.EntranceOrder().Side, trade.ExitOrder().Security, trade.ExitOrder().Amount, trade.ExitOrder().Price)) profit := trade.ExitValue().Sub(trade.CostBasis()) fmt.Fprintln(lta.Writer, fmt.Sprintf("Profit: $%s", profit)) } diff --git a/order.go b/order.go index a8aedec..d824235 100644 --- a/order.go +++ b/order.go @@ -15,6 +15,17 @@ const ( SELL ) +func (os OrderSide) String() string { + switch os { + case BUY: + return "BUY" + case SELL: + return "SELL" + default: + return "UNKNOWN" + } +} + // Order represents a trade execution (buy or sell) with associated metadata. type Order struct { Side OrderSide diff --git a/rule_stop.go b/rule_stop.go index 063840c..f827698 100644 --- a/rule_stop.go +++ b/rule_stop.go @@ -1,12 +1,19 @@ package techan -import "github.com/sdcoffey/big" +import ( + "github.com/sdcoffey/big" +) type stopLossRule struct { Indicator tolerance big.Decimal } +type stopGainRule struct { + Indicator + tolerance big.Decimal +} + // NewStopLossRule returns a new rule that is satisfied when the given loss tolerance (a percentage) is met or exceeded. // Loss tolerance should be a value between -1 and 1. func NewStopLossRule(series *TimeSeries, lossTolerance float64) Rule { @@ -20,8 +27,31 @@ func (slr stopLossRule) IsSatisfied(index int, record *TradingRecord) bool { if !record.CurrentPosition().IsOpen() { return false } - - openPrice := record.CurrentPosition().CostBasis() + openPrice := record.CurrentPosition().EntranceOrder().Price loss := slr.Indicator.Calculate(index).Div(openPrice).Sub(big.ONE) + if record.CurrentPosition().IsShort() { + loss = loss.Neg() + } return loss.LTE(slr.tolerance) } + +// NewStopLossRule returns a new rule that is satisfied when the given loss tolerance (a percentage) is met or exceeded. +// Loss tolerance should be a value between -1 and 1. +func NewStopGainRule(series *TimeSeries, gainTolerance float64) Rule { + return stopGainRule{ + Indicator: NewClosePriceIndicator(series), + tolerance: big.NewDecimal(gainTolerance), + } +} + +func (sgr stopGainRule) IsSatisfied(index int, record *TradingRecord) bool { + if !record.CurrentPosition().IsOpen() { + return false + } + openPrice := record.CurrentPosition().EntranceOrder().Price + gain := sgr.Indicator.Calculate(index).Div(openPrice).Sub(big.ONE) + if record.CurrentPosition().IsShort() { + gain = gain.Neg() + } + return gain.GTE(sgr.tolerance) +}