Giter VIP home page Giter VIP logo

techan's Introduction

Techan

codecov

TechAn is a technical analysis library for Go! It provides a suite of tools and frameworks to analyze financial data and make trading decisions.

Features

  • Basic and advanced technical analysis indicators
  • Profit and trade analysis
  • Strategy building

Installation

$ go get github.com/sdcoffey/techan

Quickstart

series := techan.NewTimeSeries()

// fetch this from your preferred exchange
dataset := [][]string{
	// Timestamp, Open, Close, High, Low, volume
	{"1234567", "1", "2", "3", "5", "6"},
}

for _, datum := range dataset {
	start, _ := strconv.ParseInt(datum[0], 10, 64)
	period := techan.NewTimePeriod(time.Unix(start, 0), time.Hour*24)

	candle := techan.NewCandle(period)
	candle.OpenPrice = big.NewFromString(datum[1])
	candle.ClosePrice = big.NewFromString(datum[2])
	candle.MaxPrice = big.NewFromString(datum[3])
	candle.MinPrice = big.NewFromString(datum[4])

	series.AddCandle(candle)
}

closePrices := techan.NewClosePriceIndicator(series)
movingAverage := techan.NewEMAIndicator(closePrices, 10) // Create an exponential moving average with a window of 10

fmt.Println(movingAverage.Calculate(0).FormattedString(2))

Creating trading strategies

indicator := techan.NewClosePriceIndicator(series)

// record trades on this object
record := techan.NewTradingRecord()

entryConstant := techan.NewConstantIndicator(30)
exitConstant := techan.NewConstantIndicator(10)

// Is satisfied when the price ema moves above 30 and the current position is new
entryRule := techan.And(
	techan.NewCrossUpIndicatorRule(entryConstant, indicator),
	techan.PositionNewRule{})
	
// Is satisfied when the price ema moves below 10 and the current position is open
exitRule := techan.And(
	techan.NewCrossDownIndicatorRule(indicator, exitConstant),
	techan.PositionOpenRule{})

strategy := techan.RuleStrategy{
	UnstablePeriod: 10, // Period before which ShouldEnter and ShouldExit will always return false
	EntryRule:      entryRule,
	ExitRule:       exitRule,
}

strategy.ShouldEnter(0, record) // returns false

Enjoying this project?

Are you using techan in production? You can sponsor its development by buying me a coffee! ☕

ETH: 0x2D9d3A1c16F118A3a59d0e446d574e1F01F62949

Credits

Techan is heavily influenced by the great ta4j. Many of the ideas and frameworks in this library owe their genesis to the great work done over there.

License

Techan is released under the MIT license. See LICENSE for details.

techan's People

Contributors

aliforever avatar danhenke avatar melekhine avatar sdcoffey avatar shkim avatar tsubus avatar zhengyangfeng00 avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

techan's Issues

Macd Signal

How to obtain the macd signal?

In the tests, one can only obtain the macd and macd histogram values

package techan

import (
	"testing"

	"github.com/stretchr/testify/assert"
)

func TestNewMACDIndicator(t *testing.T) {
	series := randomTimeSeries(100)

	macd := NewMACDIndicator(NewClosePriceIndicator(series), 12, 26)

	assert.NotNil(t, macd)
}

func TestNewMACDHistogramIndicator(t *testing.T) {
	series := randomTimeSeries(100)

	macd := NewMACDIndicator(NewClosePriceIndicator(series), 12, 26)
	macdHistogram := NewMACDHistogramIndicator(macd, 9)

	assert.NotNil(t, macdHistogram)
}

Any thoughts ?

Requesting improved documentation by examples and more detailed comments

Am very appreciative of you work on this. Please add more documentation via examples, with comments on every line, and more realistic scenarios. I know this seems like just a chore, and it’s easier said than done, but needs to be done. For example, examples were last updated 4 years ago, but last update seems to have been 26 days ago, to the Readme. I have to fight for better documentation at work every day :) Always tough to justify the time spent on it. But I find that, as a result, people tend to get a more solid start, ask fewer “dumb” questions along the way, use the code in the right way, figure out issues more efficiently on their own, and they are more willing to contribute in kind when they resolve the eventual bug. Thank you

Checkout-Java-SDK

Hello, Steve Coffey!
What is the difference between PayPal Orders API and Payments API from Checkout-Java-SDK?
Which one to use if I want to use authentications?
It is seems that Orders API not allows doing void of authorization. It feels like Order API is for use cases where you want to capture instantly and Payments API is for use cases when you want to use authorization, but it's not clear for me yet, am I right?

Orders API
https://developer.paypal.com/docs/api/orders/v2/

Payments API
https://developer.paypal.com/docs/api/payments/v2/

Cashing in indicators

Hey! I had a problem recalculating the last value of a series due to result caching. I want to propose to take out the need to use the cache for the flag. What do you think of it?

for example

does profit analyze support future?

I found some profile analyze use exitvalue sub costbaseis, but in future, you could sell short first and then buy long.
does the profit analyze with order side?

should EMA consider time window?

Hi, I see the implementation of EMA does not consider the time window, is it correct?

func (ema *emaIndicator) Calculate(index int) big.Decimal {
if cachedValue := returnIfCached(ema, index, func(i int) big.Decimal {
return NewSimpleMovingAverage(ema.indicator, ema.window).Calculate(i)
}); cachedValue != nil {
return *cachedValue
}
todayVal := ema.indicator.Calculate(index).Mul(ema.alpha)
result := todayVal.Add(ema.Calculate(index - 1).Mul(ema.alpha))
cacheResult(ema, index, result)
return result
}

Also, is RSI correct since RSI relays on EMA?

Something wrong how RSI is calculated

I can't say for sure what's wrong (I'm not familiar how RSI is calculated), but the calculation itself is not providing the right result when I compare to what I see on Tradingview. To triple-check I was not doing something wrong, I tried with this library https://github.com/markcheno/go-talib, which provided the expected result.

Also I'm using successfully the EMA of this library, which works well, so I highly suspect that the RSI calculation is not correct.

TimeSeries holding candles infinitely?

I'm running a server which appends candles to a techan.TimeSeries struct. Since I can't save the candles forever(if so, Out Of Memory will make me crazy), I'm throwing away the old ones if a new candle is appended(for example, if len(timeSeries.Candles) > 200, I throw up the old ones to hold only 200 candles). I'll call my candles` slice MyBuffer.
The problem is: if I run MACD, it gives me the same value for every new candle appended. And the reason I analysed is this:

} else if len(ema.resultCache) > index && ema.resultCache[index] != nil {

The ema.resultCache's result is returned, since the size of the cache is set to be very large and MyBuffer is much more small, therefore always passing the condition to return the cached value.

Is setting MyBuffer's size the only way to solve this situation? Or is there other graceful way to this problem?

MACD Signal line (EMA) seems to be broken/not working...

Based on a series of data, to plot the MACD and signal lines, I am doing:

	macD := analytics.MACD(closePriceIndicator, 12, 26)
	macDSignal := analytics.MACDSignalLine(macD, 9)

Where the above two functions are defined as:

func MACD(indicator techan.Indicator, shortWindow, longWindow int) techan.Indicator {
	return techan.NewMACDIndicator(indicator, shortWindow, longWindow)
}
func MACDSignalLine(indicator techan.Indicator, window int) techan.Indicator {
	return techan.NewEMAIndicator(indicator, window)
}

However when I plot this, I get two very different lines

image

To check I wasn't going completely mad, I changed the signal line to use an SMA instead, so my MACDSignalLine function becomes

func MACDSignalLine(indicator techan.Indicator, window int) techan.Indicator {
	return techan.NewSimpleMovingAverage(indicator, window) // Create an exponential moving average with a window of 10
}

which plots like

image

Which suggests that the EMA isn't working correctly for some reason here. Any suggestions as to where Im going wrong would be great, thanks!

Question: What do you guys mean by window?

	ema := NewEMAIndicator(NewClosePriceIndicator(ts), 10)

	decimalEquals(t, 63.6948, ema.Calculate(9))
	decimalEquals(t, 63.2649, ema.Calculate(10))
	decimalEquals(t, 62.9458, ema.Calculate(11))

ts variable has a time-period set for candles already. So what is this window parameter passed to NewEMAIndicator? Also, what is the purpose of that parameter index given to Calculate?

I couldn't find any documentation for it. Can someone please explain it to me?

EMA always return 0.00

Hello, i used your code with data and ... nothing works. For example EMA always gives 0

Volume:	870.19 Time:	2021-11-28T07:00:00 -> 2021-11-28T08:00:00
Open:	54377.42
Close:	54475.96
High:	54590.48
Low:	54173.81
Volume:	787.07 Time:	2021-11-28T08:00:00 -> 2021-11-28T09:00:00
Open:	54475.97
Close:	54515.61
High:	54636.65
Low:	54396.33
Volume:	985.20 Time:	2021-11-28T09:00:00 -> 2021-11-28T10:00:00
Open:	54515.60
Close:	54396.20
High:	54682.90
Low:	54231.92
Volume:	823.84 Time:	2021-11-28T10:00:00 -> 2021-11-28T11:00:00
Open:	54393.87
Close:	54335.80
High:	54475.00
Low:	54130.72
Volume:	765.69 Time:	2021-11-28T11:00:00 -> 2021-11-28T12:00:00
Open:	54335.80
Close:	54718.15
High:	54933.80
Low:	54184.60
Volume:	1028.53 Time:	2021-11-28T12:00:00 -> 2021-11-28T13:00:00
Open:	54718.15
Close:	54493.09
High:	54875.00
Low:	54425.27
Volume:	1030.39 Time:	2021-11-28T13:00:00 -> 2021-11-28T14:00:00
Open:	54493.10
Close:	54356.62
High:	54635.52
Low:	54263.16
Volume:	909.93 Time:	2021-11-28T14:00:00 -> 2021-11-28T15:00:00
Open:	54356.62
Close:	54274.20
High:	54396.33
Low:	54203.13
Volume:	777.66 Time:	2021-11-28T15:00:00 -> 2021-11-28T16:00:00
Open:	54274.20
Close:	54365.00
High:	54555.00
Low:	54029.99
Volume:	1130.34 Time:	2021-11-28T16:00:00 -> 2021-11-28T17:00:00
Open:	54365.00
Close:	54296.14
High:	54450.00
Low:	54169.84
Volume:	753.77 Time:	2021-11-28T17:00:00 -> 2021-11-28T18:00:00
Open:	54296.14
Close:	54188.43
High:	54373.00
Low:	53942.01
Volume:	1277.10 Time:	2021-11-28T18:00:00 -> 2021-11-28T19:00:00
Open:	54188.43
Close:	53896.37
High:	54287.25
Low:	53800.00
Volume:	1358.34 Time:	2021-11-28T19:00:00 -> 2021-11-28T20:00:00
Open:	53896.36
Close:	54108.99
High:	54186.17
Low:	53256.64
Volume:	2958.13 Time:	2021-11-28T20:00:00 -> 2021-11-28T21:00:00
Open:	54108.99
Close:	54617.85
High:	54967.50
Low:	54044.98
Volume:	2249.23 Time:	2021-11-28T21:00:00 -> 2021-11-28T22:00:00
Open:	54617.85
Close:	54918.51
High:	55149.99
Low:	54617.84
Volume:	1304.77 Time:	2021-11-28T22:00:00 -> 2021-11-28T23:00:00
Open:	54918.51
Close:	56273.23
High:	56390.00
Low:	54863.01
Volume:	3117.97 Time:	2021-11-28T23:00:00 -> 2021-11-29T00:00:00
Open:	56273.23
Close:	56029.82
High:	56729.72
Low:	56023.01
Volume:	2427.77 Time:	2021-11-29T00:00:00 -> 2021-11-29T01:00:00
Open:	56029.81
Close:	57274.88
High:	57445.05
Low:	56000.00
Volume:	3468.79 Time:	2021-11-29T01:00:00 -> 2021-11-29T02:00:00
Open:	57274.89
Close:	57765.73
High:	58000.15
Low:	57136.56
Volume:	3073.53 Time:	2021-11-29T02:00:00 -> 2021-11-29T03:00:00
Open:	57765.73
Close:	57639.76
High:	58242.09
Low:	57501.99
Volume:	2729.83 Time:	2021-11-29T03:00:00 -> 2021-11-29T04:00:00
Open:	57643.14
Close:	57309.25
High:	57715.42
Low:	57269.72
Volume:	1769.15 Time:	2021-11-29T04:00:00 -> 2021-11-29T05:00:00
Open:	57309.26
Close:	57359.02
High:	57426.95
Low:	57176.00
Volume:	1648.18 Time:	2021-11-29T05:00:00 -> 2021-11-29T06:00:00
Open:	57359.02
Close:	57249.58
High:	57642.93
Low:	57232.00
Volume:	1358.98 Time:	2021-11-29T06:00:00 -> 2021-11-29T07:00:00
Open:	57249.58
Close:	57325.75
High:	57384.99
Low:	57200.00
Volume:	801.75 Time:	2021-11-29T07:00:00 -> 2021-11-29T08:00:00
Open:	57325.76
Close:	57426.91
High:	57500.63
Low:	57309.65
Volume:	1002.85 Time:	2021-11-29T08:00:00 -> 2021-11-29T09:00:00
Open:	57426.92
Close:	57515.13
High:	57688.00
Low:	57390.00
Volume:	1047.27 Time:	2021-11-29T09:00:00 -> 2021-11-29T10:00:00
Open:	57512.17
Close:	57514.33
High:	57642.85
Low:	57455.08
Volume:	174.91]}

EMA output: 0.00

The code :

df := techan.NewTimeSeries()

	for _, k := range *klines {
		// opentime, open, high, low, close, volume
		period := techan.NewTimePeriod(time.Unix(k.OpenTime/1000, 0), *duration)

		candle := techan.NewCandle(period)
		candle.OpenPrice = big.NewFromString(k.Open)
		candle.ClosePrice = big.NewFromString(k.Close)
		candle.MaxPrice = big.NewFromString(k.High)
		candle.MinPrice = big.NewFromString(k.Low)
		candle.Volume = big.NewFromString(k.Volume)

		df.AddCandle(candle)
	}

	fmt.Println(df)
        closePrices := techan.NewClosePriceIndicator(df)
	movingAverage := techan.NewEMAIndicator(closePrices, 10) // Create an exponential moving average with a window of 10

	fmt.Println("EMA output :" + movingAverage.Calculate(0).FormattedString(2))

EMA calculation differ from ta4j documentation

Moving average indicator should NOT cache result of last index in a series as stated on ta4j documentation page about indicators:

Values for the last Bar will not be cached. This allows you to modify the last bar of the TimeSeries by adding price/trades to it and to recalculate results with indicators.

Changes

	resultCache: make([]*big.Decimal, 1, 10000),
	} else if index < len(ema.resultCache)-1 {
  • func (ema *emaIndicator) cacheResult(index int, val big.Decimal) {
    if index < len(ema.resultCache) {
    ema.resultCache[index] = &val
    } else {
    ema.resultCache = append(ema.resultCache, &val)
    }
    }

    should become:
func (ema *emaIndicator) cacheResult(index int, val big.Decimal) {
	// do not cache the result for last index
	if index == len(ema.resultCache) - 1 {
		return
	}
	if index < len(ema.resultCache) {
		ema.resultCache[index] = &val
	} else {
		ema.resultCache = append(ema.resultCache, &val)
	}
}

this is a bug

Hello sdcoffey,
Long time to see you here, I hope you're ok.
In this function, it's not satisfied to use close price divide CostBasis

func (slr stopLossRule) IsSatisfied(index int, record *TradingRecord) bool {
	if !record.CurrentPosition().IsOpen() {
		return false
	}

	openPrice := record.CurrentPosition().CostBasis()
	loss := slr.Indicator.Calculate(index).Div(openPrice).Sub(big.ONE)
	return loss.LTE(slr.tolerance)
}

openPrice := record.CurrentPosition().CostBasis()

Add a way to include transaction fees

Hello!

Would it be possible to add a way to include transaction fees into the calculations?
I see two way to do it:

  • Update the Strategy interface in order to include it.
  • Make a new analyze tool

What do you think of it?

Unable to use the library

@sdcoffey thanks so much for this library, i am very new to golang and i like to learn a new language through a project and thought of building a bot with this library i am unable to get the strategy struct/function, along with a couple of rules working the way i want them to, i began making my own implementations but i still have lots of issues, I know the question is rather vague but no replies on pull requests for awhile that's why i'm throwing this out here

SMA Bug in v0.12.1 ?

package main

import (
	"fmt"
	"time"

	"github.com/sdcoffey/big"
	"github.com/sdcoffey/techan"
)

func calcSma(series *techan.TimeSeries, days int) []big.Decimal {
	closePrices := techan.NewClosePriceIndicator(series)
	sma := techan.NewSimpleMovingAverage(closePrices, days)

	cnt := len(series.Candles)
	ret := make([]big.Decimal, cnt)
	for idx := range ret {
		ret[idx] = sma.Calculate(idx)
	}
	return ret
}

func main() {
	series := techan.NewTimeSeries()
	for i := 1; i <= 30; i++ {
		day := time.Date(2020, 5, i, 0, 0, 0, 0, time.Local)
		candle := techan.NewCandle(techan.TimePeriod{
			Start: day,
			End:   day.Add(time.Duration(23) * time.Hour),
		})

		candle.ClosePrice = big.NewDecimal(float64(100 + i))
		added := series.AddCandle(candle)
		if !added {
			fmt.Println("AddCandle failed")
			return
		}
	}

	indicators := calcSma(series, 10)
	fmt.Println("SMA:", indicators)
}

with version 0.12.0

module main

go 1.17

require (
	github.com/sdcoffey/big v0.7.0
	github.com/sdcoffey/techan v0.12.0
)

the program run result is:
SMA: [101 101.5 102 102.5 103 103.5 104 104.5 105 105.5 106.5 107.5 108.5 109.5 110.5 111.5 112.5 113.5 114.5 115.5 116.5 117.5 118.5 119.5 120.5 121.5 122.5 123.5 124.5 125.5]

I think above result is correct, but with v0.12.1

 module main

go 1.17

require (
	github.com/sdcoffey/big v0.7.0
	github.com/sdcoffey/techan v0.12.1
)

the result is:
SMA: [0 0 0 0 0 0 0 0 0 105.5 106.5 107.5 108.5 109.5 110.5 111.5 112.5 113.5 114.5 115.5 116.5 117.5 118.5 119.5 120.5 121.5 122.5 123.5 124.5 125.5]

I think the initial 0s are incorrect.

Is this a bug or a new feature?

Representing NA values

Hi! I think it's useful to have a special NA or NaN value to represent the case where a value cannot be calculated. Other popular data analysis libraries such like numpy/pandas all have it.

One use case is that I want to calculate the difference between today's close price and yesterday's close price for each candle in the time series. Then I want to do dif := techan.NewDifferenceIndicator(today, NewRefIndicator(yday, 1)) but if I accidentally call dif.Calculate(0) it will segfault.

I think we can workaround this problem by other means but I feel the most natural way is to add a NA value. What's your thoughts on this?

support trading record of database

what's a wonderful project! It's great!
can it support load trading recorde data from database/log file and save trading record data to database/log file? then analysis this offline data satisfied.

SMA and EMA get the same result

Hi sdcoffey,

I use the EMA function to calculate EMA(20) result, but I got the MA(20)'s result, I'm sure that the EMA(20) is different with MA(20).

The kline close prices:
43794.37
46253.40
45584.99
45511.00
44399.00
47800.00
47068.51
46973.82
45901.29
44695.95
44705.29
46760.62
49322.47
48821.87
49239.22
49488.85
47674.01
48973.32
46843.87
49069.90
48895.35
48767.83
46982.91
47100.89
47375.36

The EMA(25) result is: 47120 (it should be 46305)

The MA(25) result is: 47120

How I calculate the EMA(25) results:

closePrices := techan.NewClosePriceIndicator(series)
movingAverage := techan.NewEMAIndicator(closePrices, 25)
fmt.Println(movingAverage.Calculate(24).FormattedString(0))

And this is how I calculate MA(25) result:

closePrices := techan.NewClosePriceIndicator(series)
movingAverage := techan.**NewSimpleMovingAverage**(closePrices, 25)
fmt.Println(movingAverage.Calculate(24.FormattedString(0))

BTW, the lib version I used:
image

Please help me check that if the EMA function has bug, thanks!


Updates: sorry It's my fault, I should create more klines, thanks!

Integrating multiple timeframes into a single strategy

Hi Steve thanks for porting this lib over to Go. I'm new to trading & technical analysis...

So I was wondering if its possible to create strategies using multiple timeframes.

If so, is it possible to do using your package?

Thanks!

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.