Giter VIP home page Giter VIP logo

Comments (58)

briandilley avatar briandilley commented on May 19, 2024 2

The Decimal changes that i've proposed in my pull request make most of this stuff a little easier to deal with anyway. For instance:

Decimal.valueOf(9).multiply(Decimal.valueOf(9))

becomes:

Decimal.valueOf(9).multiply(9)

from ta4j.

edlins avatar edlins commented on May 19, 2024 2

If Simon wants to merge his work that allows users to choose their implementation, then I'm fine with that. I won't use an implementation built on BigDecimal but I don't care if it's there for me to ignore. Plus, with multiple implementations available, we can provide concrete examples comparing performance and precision across double, BigDecimal, etc.

Of course I really dislike the naming of NumOperations but that's superficial. ;)

from ta4j.

team172011 avatar team172011 commented on May 19, 2024 2
  1. Okay with Num
  2. Maybe it is possible to have a function in AbstractIndicator:
  • public Num numOf(double val) -> So we can do: Num a = numOf(0) shorter than current version Decimal a = Decimal.ZERO
  1. Also okay with that, even it was never a problem for me^^

from ta4j.

briandilley avatar briandilley commented on May 19, 2024 2

from ta4j.

nimo23 avatar nimo23 commented on May 19, 2024

Be care of "double" conversion process in "ExactDecimal":

string as input for new BigDecimal("3.33") leads to higher precision than double as input for new BigDecimal(3.33).

More:

from ta4j.

edlins avatar edlins commented on May 19, 2024

Might suggest class name DoubleDecimal instead of FastDecimal. Hypothetical LongDecimal could be "Faster".

from ta4j.

nimo23 avatar nimo23 commented on May 19, 2024

By the way, I am wondering why typical math libraries all uses simple "double" for its complex math calculation without any option. Normally, end user expects high precision in those complex math calculations, which can not be guaranteed by these libraries. Or?

from ta4j.

nimo23 avatar nimo23 commented on May 19, 2024

I would suggest to name the files the following:

  • Decimal.java(Interface)
  • LongDecimal.java (->uses long)
  • ShortDecimal.java (->uses short)
  • BaseDecimal.java (->uses BigDecimal),

where "LongDecimal.java" is the standard.

The other two are provided by a optional property (enum of type "DecimalType.java") within the "TimeSeries"-Constructor or elsewhere where such calculation is needed

And put all these files under "org.ta4j.core.decimal".

from ta4j.

edlins avatar edlins commented on May 19, 2024

Decimal.java(Interface)
LongDecimal.java (->uses long)
ShortDecimal.java (->uses short)
BaseDecimal.java (->uses BigDecimal),

Don't forget:
DoubleDecimal.java (->uses double)

from ta4j.

team172011 avatar team172011 commented on May 19, 2024

My idea is to replace every BigDecimal appearance with the Decimal interface, so that the user can choose the DecimalType he wants to use when initializing the tick data (here we have to make sure that all Ticks (arrays) of a (columnar based) TimeSeries use the same implementation).

That means

  • timeSeries.add(open, high, low, close, volumen...) -> whereby OHLC data consists of Decimal implementation (LongDecimal, BaseDecimal or further custom implementations)
  • All the other calculations are just based on the interface

Extracting the interface is easy:


public interface Decimal extends Serializable, Comparable<Decimal>{

Decimal plus(Decimal augend);

    /**
     * Returns a {@code Decimal} whose value is {@code (this - augend)},
     * @param subtrahend value to be subtracted from this {@code Decimal}.
     * @return {@code this - subtrahend}, rounded as necessary
     */
    Decimal minus(Decimal subtrahend);

    /**
     * Returns a {@code Decimal} whose value is {@code this * multiplicand},
     * @param multiplicand value to be multiplied by this {@code Decimal}.
     * @return {@code this * multiplicand}, rounded as necessary
     */
    Decimal multipliedBy(Decimal multiplicand);
    
    (...)
}

But now i am unsure about how to represent the NaN value and other static functions especially the valueOf(val) functions. Maybe we need a factory class?

from ta4j.

nimo23 avatar nimo23 commented on May 19, 2024

Maybe you should use default (static) methods within the "interface" Decimal.class (in java 9, interfaces can also have private methods).

Overall, the user should be able to use "Decimal.staticMethod" instead of "BaseDecimal.method()". Because formula can get long when using BaseDecimal instead of the shorter name Decimal.

"BaseDecimal.valueOf(9).multiply(BaseDecimal.valueOf(9)"

vs

"Decimal.valueOf(9).multiply(Decimal.valueOf(9))" (preferred)

My suggest is:

Please, give the whole thing a new name, rename "Decimal" to "Number". It is clearer and shorter. It is a number and not only a decimal!

For example:

// new version:
// The code with this shrinks and formulas are easier to overview
// Maybe you can make use of the "Builder"-Pattern (it can be made also with static methods!)
Number(4).plus(Number(3)).multiply(Number(2).log());

instead of

// old version:
// The code is bloated and formulas are not easy to overview
Decimal.valueOf(4).plus(Decimal.valueOf(3)).multiply(Decimal.valueOf(2).log());

Please rename "Decimal" to "Number" and make its use less redundant.

  • The whole code basis will shrink
  • formulas are easier to overview.
  • potential bugs in formulas can be found easier with new version.
  • And we have to write less code with the new naming scheme.

You can name it:

  • Number.java (interface with default static methods using BigDecimal)
  • ShortNumber.java (short implementation)
  • LongNumber.java (long implementation)
  • DoubleNumber.java (double implementation)

all within packace: "org.ta4j.core.number"

There is also a "Interface Builder Pattern" and a "Builder-Pattern for Method-Chainings".

from ta4j.

damir78 avatar damir78 commented on May 19, 2024

Hi guys,
I think you should read https://github.com/tools4j/decimal4j/wiki/Performance

image

from ta4j.

team172011 avatar team172011 commented on May 19, 2024

At the moment i am working on a solution that allows the user to define the number type via generic class in the TimeSeries:

  • public class BaseTimeSeries<D extends Decimal> extends AbstractTimeSeries<D> //Decimal can be named Numbers....

  • TheAbstractTimeSeries<D extends Decimal> implements the Tick<D extends Decimal> interface and the TimeSeries interface and hold class variables and functions for the columnar approach.

  • Additional i have a DecimalFactory that has a static function DecimalFactory.valueOf(double val, Decimal implementation) that delivers the value of val in the corresponding class of implementation

    • This solution is a little bit dirty i think, because the implementations (at the moment BaseDecimal and LongDecimal have to implement a getType() function that the Factory calls to get the corresponding type. That is my current problem, you cannot get the type of a generic class

For example

TimeSeries series = new TimeSeries<LongDecimal>()
Decimal zero = DecimalFactory.valueOf(0, LongDecimal);

@damir78 currently i am using Decimal10f of decimal4j for the LongDecimal implementation

from ta4j.

team172011 avatar team172011 commented on May 19, 2024
public class DecimalFactory{

    public static Decimal valueOf(double val, Decimal decimalClass){
        if(decimalClass != null && decimalClass.getType() == DecimalType.BaseDecimal){
            return BaseDecimal.valueOf(val);
        }
        if(decimalClass != null && decimalClass.getType() == DecimalType.LongDecimal){
            return LongDecimal.valueOf(val);
        }

        throw new ClassCastException(String.format("Decimal implementation of %s  not supported",decimalClass.toString()));
    }

}

from ta4j.

nimo23 avatar nimo23 commented on May 19, 2024

Look for "reifiable type" in Java9.

A reifiable type exposes its complete type information at runtime.

E.g. in java 9 u can use diamond op in abstract classes.

Maybe, for full deletion of such a DecimalFactory, you ve to wait for java 10 (project valhalla). Normally, such things can be solved actually with CDI (Context Dependency Injetion), However, it s not that kind of code, you use annotations to expose the type information to runtime..cdi 2 (weld) can be uses within common java se applications..

from ta4j.

team172011 avatar team172011 commented on May 19, 2024

ta4j supports java8+

from ta4j.

nimo23 avatar nimo23 commented on May 19, 2024

As java.lang.Number already exist, maybe "Decimal" should either renamed to org.ta4j.core.number or to org.ta4j.core.num.

A "Num" can be seen as a abbreviation of Number (for example, Phyton has a "NumPy"). So we can call it only a "Num" which represents any kind of Numbers:

// new version:
// The code with this shrinks  and formulas are easier to overview
// Maybe you can make use of the "Builder"-Pattern (it can be made also with static methods!)
Num(4).plus(Num(3)).multiply(Num(2).log());

So:

  • Num.java (interface with default methods of BigDecimal)
  • DoubleNum.java (implementation with double values)
  • LongNum.java
    ..

from ta4j.

edlins avatar edlins commented on May 19, 2024

Sorry for all the questions here; feel free to ignore.

Num(4).plus(Num(3)).multiply(Num(2).log());

If Num is the interface, isn't Num(4) trying to call an interface constructor? Is that better done with an abstract class?

And, what's the benefit of using generics if you don't mind re-implementing the interface methods in the classes? In fact, I'd prefer to implement all the interface methods for each class to make them efficient.
So why not:

public interface Num {
	public boolean isNaN();
	public boolean isZero();
	public Num divide(Num divisor);
	public Num add(Num augend);
	// <all the other operators..>
	public BigDecimal bigDecimalValue();
	public double doubleValue();
	public String toString();
}

I tried that and just coded so that BigDecimalNum#divide(Num divisor) calls divisor.BigDecimalValue() and DoubleNum#divide(Num divisor) calls divisor.doubleValue(). It's probably dumb but I'm not sure why.

from ta4j.

nimo23 avatar nimo23 commented on May 19, 2024

If Num is the interface, isn't Num(4) trying to call an interface constructor? Is that better done with an abstract class?

good question. with java 9, there is not much a difference of abstract classes and interfaces. interfaces can also have default methods (default divide(Num divisor){..}) and static methods. Which one to choose? abstract classes have the benefit of having (abstract) constructors (and can make use of the Builder-Patteren inherited by all others ...(num(x).plus(num(3))..However, I guess, static methods should be preferred .(Num(x).plus(Num(3))--i m not sure..

And, what's the benefit of using generics.. In fact, I'd prefer to implement all the interface methods for each class to make them efficient.

yes, good idea!

from ta4j.

team172011 avatar team172011 commented on May 19, 2024

I have published my approach at this branch (package:columnar_timeSeries_and_decimal_interface ). At the moment, there are two different implementations of the NumOperations interface (lets talk about naming later...)

  • BaseDecimal -> uses BigDecimal class
  • LongDecimal -> uses Decimal5f class from Decimal4j
  • DoubleDecimal -> uses Double primitive

Every class of NumOperations has also to implement a static field of NumOperationsFactory by convention that should allow the static context (like NaN, valueOf(val), TEN, HUNDRED etc).

Once can declare and initialize a columnar based TimeSeries<D> with generic class parameter D extends NumOperations by calling :

// declare and initialize empty time series of two different types
BaseTimeSeries<LongDecimal> seriesWithLongDecimal = new BaseTimeSeries<>(LongDecimal.NUM_OPERATIONS_FACTORY);
BaseTimeSeries<BaseDecimal> seriesWithBaseDecimal = new BaseTimeSeries<>(BaseDecimal.NUM_OPERATIONS_FACTORY);

// declare and initialize indicators on the time series
ClosePriceIndicator closePriceIndicator = new ClosePriceIndicator(seriesWithBaseDecimal);
SMAIndicator smaIndicator = new SMAIndicator(closePriceIndicator, 10);

ClosePriceIndicator closePriceIndicatorLong = new ClosePriceIndicator(seriesWithLongDecimal);
SMAIndicator smaIndicatorLong = new SMAIndicator(closePriceIndicator, 10);

The NUM_OPERATIONS_FACTORY is stored and provided by the TimeSeries and is needed to enable basic operations in Bar and Indicator. E.g.:

@Override
    protected NumOperations calculate(int index) {
        NumOperations sum = getNumFactory().ZERO(); // get value Zero of underlying NumOperations implementation
        for (int i = Math.max(0, index - timeFrame + 1); i <= index; i++) {
            sum = sum.plus(indicator.getValue(i));
        }

        final int realTimeFrame = Math.min(timeFrame, index + 1);
        return sum.dividedBy(getNumFactory().valueOf(realTimeFrame)); // get int value of underlying NumOperations implementation
    }

With this kind of solution we could optionally increase the performance by choosing the underlying NumOperations implementation (or write own):

grafik

--------------Test (time frame 2-3)--------------
[Column based LongDecimal ] time: 542 lastValue: 1166397.5
[Column based BaseDecimal ] time: 3794 lastValue: 1166397.5
--------------Test (time frame 2-4)--------------
[Column based LongDecimal ] time: 467 lastValue: 1166397.0
[Column based BaseDecimal ] time: 6448 lastValue: 1166397.0
--------------Test (time frame 2-5)--------------
[Column based LongDecimal ] time: 568 lastValue: 1166396.5
[Column based BaseDecimal ] time: 8970 lastValue: 1166396.5
--------------Test (time frame 2-6)--------------
[Column based LongDecimal ] time: 686 lastValue: 1166396.0
[Column based BaseDecimal ] time: 11997 lastValue: 1166396.0
--------------Test (time frame 2-7)--------------
[Column based LongDecimal ] time: 895 lastValue: 1166395.5
[Column based BaseDecimal ] time: 14857 lastValue: 1166395.5
--------------Test (time frame 2-8)--------------
[Column based LongDecimal ] time: 1125 lastValue: 1166395.0
[Column based BaseDecimal ] time: 18318 lastValue: 1166395.0
--------------Test (time frame 2-9)--------------
[Column based LongDecimal ] time: 1332 lastValue: 1166394.5
[Column based BaseDecimal ] time: 21546 lastValue: 1166394.5
--------------Test (time frame 2-10)--------------
[Column based LongDecimal ] time: 1574 lastValue: 1166394.0
[Column based BaseDecimal ] time: 24770 lastValue: 1166394.0
--------------Test (time frame 2-11)--------------
[Column based LongDecimal ] time: 2160 lastValue: 1166393.5
[Column based BaseDecimal ] time: 28656 lastValue: 1166393.5
--------------Test (time frame 2-12)--------------
[Column based LongDecimal ] time: 2208 lastValue: 1166393.0
[Column based BaseDecimal ] time: 31066 lastValue: 1166393.0
--------------Test (time frame 2-13)--------------
[Column based LongDecimal ] time: 2604 lastValue: 1166392.5
[Column based BaseDecimal ] time: 34508 lastValue: 1166392.5
--------------Test (time frame 2-14)--------------
[Column based LongDecimal ] time: 3023 lastValue: 1166392.0
[Column based BaseDecimal ] time: 38646 lastValue: 1166392.0
--------------Test (time frame 2-15)--------------
[Column based LongDecimal ] time: 3301 lastValue: 1166391.5
[Column based BaseDecimal ] time: 42131 lastValue: 1166391.5
--------------Test (time frame 2-16)--------------
[Column based LongDecimal ] time: 3756 lastValue: 1166391.0
[Column based BaseDecimal ] time: 46649 lastValue: 1166391.0
--------------Test (time frame 2-17)--------------
[Column based LongDecimal ] time: 4398 lastValue: 1166390.5
[Column based BaseDecimal ] time: 50336 lastValue: 1166390.5
--------------Test (time frame 2-18)--------------
[Column based LongDecimal ] time: 4476 lastValue: 1166390.0
[Column based BaseDecimal ] time: 52375 lastValue: 1166390.0
--------------Test (time frame 2-19)--------------
[Column based LongDecimal ] time: 4851 lastValue: 1166389.5
[Column based BaseDecimal ] time: 56256 lastValue: 1166389.5
--------------Test (time frame 2-20)--------------
[Column based LongDecimal ] time: 5381 lastValue: 1166389.0
[Column based BaseDecimal ] time: 60851 lastValue: 1166389.0
--------------Test (time frame 2-21)--------------
[Column based LongDecimal ] time: 5762 lastValue: 1166388.5
[Column based BaseDecimal ] time: 62810 lastValue: 1166388.5
--------------Test (time frame 2-22)--------------
[Column based LongDecimal ] time: 6152 lastValue: 1166388.0
[Column based BaseDecimal ] time: 66855 lastValue: 1166388.0
--------------Test (time frame 2-23)--------------
[Column based LongDecimal ] time: 6555 lastValue: 1166387.5
[Column based BaseDecimal ] time: 69643 lastValue: 1166387.5
--------------Test (time frame 2-24)--------------
[Column based LongDecimal ] time: 7008 lastValue: 1166387.0
[Column based BaseDecimal ] time: 73597 lastValue: 1166387.0
--------------Test (time frame 2-25)--------------
[Column based LongDecimal ] time: 7464 lastValue: 1166386.5
[Column based BaseDecimal ] time: 81176 lastValue: 1166386.5
--------------Test (time frame 2-26)--------------
[Column based LongDecimal ] time: 8770 lastValue: 1166386.0
[Column based BaseDecimal ] time: 90044 lastValue: 1166386.0
--------------Test (time frame 2-27)--------------
[Column based LongDecimal ] time: 9171 lastValue: 1166385.5
[Column based BaseDecimal ] time: 90886 lastValue: 1166385.5
--------------Test (time frame 2-28)--------------
[Column based LongDecimal ] time: 9950 lastValue: 1166385.0
[Column based BaseDecimal ] time: 97250 lastValue: 1166385.0
--------------Test (time frame 2-29)--------------
[Column based LongDecimal ] time: 10288 lastValue: 1166384.5
[Column based BaseDecimal ] time: 95660 lastValue: 1166384.5
--------------Test (time frame 2-30)--------------
[Column based LongDecimal ] time: 10590 lastValue: 1166384.0
[Column based BaseDecimal ] time: 100420 lastValue: 1166384.0

[update1: added first idea of DoubleDecimal ]

from ta4j.

nimo23 avatar nimo23 commented on May 19, 2024

NumOperations sum = getNumFactory().ZERO();

So "NumOperations" is this what i had before with Decimal.class..NumFactory?

Look at these versions:

  • Decimal sum = Decimal.ZERO; (the old one)
  • NumOperations sum = getNumFactory().ZERO(); (little awkward..)
  • Num<BigDecimal> sum = Num.ZERO(); (needs runtime reflection of Type..)
  • Num<Double> sum = Num.zero;
  • DoubleNum sum = Num.zero;
// runtime reflection available !!
@NumType(Double) 
Num sum = Num.zero; 
  • Num sum = Num.zero; (the best)

To differ between Num-Implementations, you can use custom annotation and read the value within code.

RetentionPolicy.RUNTIME – The annotation is be available for reflection at runtime. This is what we generally use for our custom annotations.

/**
 * A qualifier used to differentiate between different Num-Implementations
 */
@Qualifier
@Target({ElementType.TYPE, ElementType.METHOD, ElementType.PARAMETER, ElementType.FIELD})
@Retention(RetentionPolicy.RUNTIME)
public @interface NumType
{
	Data value();

	public enum Data{Double, Long, Short};
}

and use it like this:

// runtime reflection available !!
@NumType(Double) 
Num sum = Num.zero; 

or better on Class-Level with something like this..

@NumType(Double) 
public class DoubleNum extends Num {

   @Override
   public void setDelegate(){
      return Double.class;
   }

// and all the other things are equal to default methods of Num, 
// so no need to overwrite or put these methods here again..

}

Then user can either use this:

Num = DoubleNum(32).plus(DoubleNum.one);

or this

@NumType(Double) 
Num = Num(32).plus(Num.one)

from ta4j.

team172011 avatar team172011 commented on May 19, 2024

I do not understand your example. How should runtime reflection be available for a generic class? Thought this is the only way: https://stackoverflow.com/questions/3403909/get-generic-type-of-class-at-runtime

from ta4j.

team172011 avatar team172011 commented on May 19, 2024

To differ between Num-Implementations, you can use custom annotation and read the value within code.

You have to know the type if you want to use
@NumType(Double) // Double
or not?

But the calculate(index) function of an Indicator does not know the underlying type!

from ta4j.

nimo23 avatar nimo23 commented on May 19, 2024

Ok, forget the annotation thing. However, it can be solved.

How should runtime reflection be available for a generic class?

There are a few options to get the generic type in runtime. Please read the following and take the best:

from ta4j.

briandilley avatar briandilley commented on May 19, 2024

This PR was merged and may address this issue as well: #99

EDIT: I guess it was reverted, not sure why though... @team172011 ?

from ta4j.

team172011 avatar team172011 commented on May 19, 2024

@briandilley because of failed travis tests. I could not reproduce them local and mered again. Unfortunately travis test failed again because of

Tests run: 2, Failures: 1, Errors: 0, Skipped: 0, Time elapsed: 0 sec <<< FAILURE!
testValueOf(org.ta4j.core.DecimalTest)  Time elapsed: 0 sec  <<< FAILURE!
java.lang.AssertionError: expected:<0.33000000000000001554312234475219> but was:<0.33>

Failed tests:   getValue(org.ta4j.core.indicators.helpers.DecimalTransformIndicatorTest): expected:<1.3862943611198906> but was:<1.3862943611198905724535279659904>
  testValueOf(org.ta4j.core.DecimalTest): expected:<0.33000000000000001554312234475219> but was:<0.33>

from ta4j.

team172011 avatar team172011 commented on May 19, 2024

@briandilley you have to use static function valueOf(val) to generate new Decimal instances, otherwise the test regarding multiplication symmetrically will fail.

from ta4j.

briandilley avatar briandilley commented on May 19, 2024

from ta4j.

team172011 avatar team172011 commented on May 19, 2024

@nimo23 in my opinion your suggestions are to expensive. At least every indicator uses such avalueOf(val) function. So you have to do those checks every time calling the calculates(index) function.
I implemented the Factory because of this answers from StackOverflow. Maybe we could create a TimeSeriesFactory that creates the corresponding TimeSeries for the user, so he is not confronted directly with the DecimalClass.NUM_FACTORY parameter of a TimeSeries

from ta4j.

nimo23 avatar nimo23 commented on May 19, 2024

in my opinion your suggestions are to expensive.

TOO Expensive?? Did you measure? What is the trade off? It will far away from the bottleneck like BigDecimal. What is the trade-off to provide a api which has such design flaws which are nothing more than workarounds.

As I do discover such #103 StackOverflowErrors and very slow runtime when using little (!) complex formulas with (getValue(index -1)*getValue(index-2)) and the restricted use of common math operations like Math.exp or the like, I come in doubt to like the BigDecimal or the (abstract/interface) Decimal idea thing..

When I want to express somethink like this easy calculation:

double a = 3 + 2 * Math.exp(3 * 2) * getValue(index -2);

and need to rewrite this to:

ShortDecimal d = ShortDecimal.valueOf(3);
Factory x = get..
DoubleNumber a = DoubleNumber.valueOf(3) + DoubleNumber.valueOf(2) + DoubleNumber.valueOf(Math.exp(d.toDouble().multipliedBy(DoubleDecimal.valueOf(2)))..
....i get out of sync...what I wanted to do, I hope there are no bugs, look around 10 times again and again ??? only because some few wanted the 100 digits to start the travel to the moon..

only because I am to have for the minority of the users the last 1000 digits to be correct..for what ever reason..maybe, because they wanted to navigate to the moon.

you know what I mean?

Then please tell me: what is expensive? My time to write such tedious formulas only to express this little snippet?:

double a = 3 + 2 * Math.exp(3 * 2) * getValue(index -2);

The first right direction was to replace the Decimal-idea with "java.lang.Number"..the "Num" what I thinking of. The next direction would be to use Double as Standard and make the user possible to write common math calculations with this syntax:

double a = 3 + 2 * Math.exp(3 * 2) * getValue(index -2);

and not with this:

ShortDecimal d = ShortDecimal.valueOf(3);
Factory x = get..
DoubleNumber a = DoubleNumber.valueOf(3) + DoubleNumber.valueOf(2) + DoubleNumber.valueOf(Math.exp(d.toDouble().multipliedBy(DoubleDecimal.valueOf(2)))..
....i get out of sync...what I wanted to do, I hope there are no bugs, look around 10 times again and again ??? only because some few wanted the 100 digits to start the travel to the moon..

So what is expensive? My time to write such things.

from ta4j.

nimo23 avatar nimo23 commented on May 19, 2024

I dont like the interface idea at all and I like the idea to convert ta4j to use double as standard, like any other "highly mathematical libraries (!) " do. So if these libs can use double without fear for the 1000 digits than we should do it 1000 times more!

from ta4j.

team172011 avatar team172011 commented on May 19, 2024

My time to write the such tedious formulas only to express this little snippet?

There will be no changes regarding expression of formulas. They will stay the same as with Decimal class. This is the same approach all non primitive types use (like BigDecimal, Decimal4j, and so on ..). We only use the interface (current naming Value) in in the indicators, no LongDecimal, ShortDecimal or BaseDecimal classes.

Factory f = getNumFactory();
Value a = f.valueOf(3).plus(f.valueOf(2.)).multipliedBy(f.valueOf(....

The next direction would be to use Double as Standard and make the user possible to write common math calculations with this syntax:

This was discussed in #75. This issue is the result. At most double will come as interface implementation.

Did you measure? What is the trade off? It will far away from the bottleneck like BigDecimal. What is the trade-off to provide a api which has such design flaws which are nothing more than workarounds.

My approach is not final. Feel free to show better solutions. I would prefer to discuss example implementations of TimeSeries and Indicator and not a collection of links

from ta4j.

nimo23 avatar nimo23 commented on May 19, 2024

At first:

Factory f = getNumFactory();
Value a = f.of(3).plus(f.of(2.)).multiply(f.of(....

would be a little better..however..

I dont like the interface idea at all and I like the idea to convert ta4j to use Double as standard, like any other "highly mathematical libraries (!) " do, even more. So if these libs can use double without fear for the 1000 digits than we should do it 1000 times more!

I would suggest to free up ta4j from the Decimal-world I:

  • delete all the factory around Decimal/Number/etc
  • convert this project to use plain "Double"
  • convert all formulas to use typical math like the one I type in my calculator.

dont you think so? I guess, BigDecimal is only important in cases of money.

from ta4j.

nimo23 avatar nimo23 commented on May 19, 2024

and not a collection of links

I have taken my time to show how you can solve this problem around the "generic type in runtime". Because you were wrong with your statement. And thank you, too.

from ta4j.

briandilley avatar briandilley commented on May 19, 2024

dont you think so? I guess, BigDecimal is only important in cases of money.

If this project doesn't properly support currency then I would imagine a lot of users (myself included) would no longer find this library useful.

from ta4j.

nimo23 avatar nimo23 commented on May 19, 2024

If this project doesn't properly support currency

Can you give me an example when it fails ?

For example: Calculation of SMA shows this with double:

16.149999999999998578914528479800

and this with BigDecimal

16.149999999999998578914528479843565422345532

so what is the exact benefit of having the last digit in my indicator?

Is it because of the pips or what?

from ta4j.

briandilley avatar briandilley commented on May 19, 2024

I don't have a specific example for you. I just know that using Float and/or Double for currency is a bad idea. Here's an SO post about it: https://stackoverflow.com/questions/3730019/why-not-use-double-or-float-to-represent-currency

Representing money as a double or float will probably look good at first as the software rounds off the tiny errors, but as you perform more additions, subtractions, multiplications and divisions on inexact numbers, you'll lose more and more precision as the errors add up. This makes floats and doubles inadequate for dealing with money, where perfect accuracy for multiples of base 10 powers is required.

from ta4j.

nimo23 avatar nimo23 commented on May 19, 2024

I don't have a specific example for you.

I know, because I did think about it long before and did not find any use case.

https://stackoverflow.com/questions/3730019/why-not-use-double-or-float-to-represent-currency

Yes, I know that, too. Long long before. You can believe me: I know the differences in detail.

The thing is: it is for MONEY!

If you have this in your deposit:

12.410001 and multiply it with 100000 with some pips then the last digits are also important, hence use BigDecimal (or Long). No debate. This is clear.

However, for the indicators, I guess, is a different world. Or?

from ta4j.

edlins avatar edlins commented on May 19, 2024

I could be wrong as I'm coming up to speed, but this issue 88 is primarily regarding the calculation of Indicators and determining whether Rules have been satisfied in evaluating a Strategy. The issue of currency I thought was confined to the analysis criteria, which is where the Strategy signals (TradingRecord) are turned into things like profit and loss, drawdown, comparison to buy-and-hold, etc. i.e. RSI rounding errors may (unlikely) impact the timing of a Trade but should not impact the cost of the Trade.

from ta4j.

team172011 avatar team172011 commented on May 19, 2024

From

Providing support for a Decimal type helps in many cases. Many languages have a decimal type, but they are underused.

Understanding the approximation that occurs when working with representation of real numbers is important. Using both decimal and floating point types 9 * (1/9) != 1 is a correct statement. When constants a optimizer may optimize the calculation so that it is correct.

Providing an approximates operator would help. However, such comparisons are problematic. Note that .9999 trillion dollars is approximately equal to 1 trillion dollars. Could you please deposit the difference in my bank account?

There are endless discussions and explanations why not to use floating point arithmetic. I do not want to create a lib that "works like expected as far as your numbers are not to big or to small and your calculations are not to complex".

from ta4j.

nimo23 avatar nimo23 commented on May 19, 2024

@briandilley the syntax is far better than before. Good approach! Would be nice to replace the "valueOf" with "of":

Decimal.of(9) ("it is a "decimal" "of" 9)

and replace the Decimal with "Num", then we can use:

Num.of(9) because it is a "NUMBER" "of" 9 and not always a decimal, you know?

from ta4j.

nimo23 avatar nimo23 commented on May 19, 2024

RSI rounding errors may (unlikely) impact the timing of a Trade but should not impact the cost of the Trade.

Thanks @edlins. Yes, you are totally right with your statement! This is exactly what I tried to explain here.

"The heavy computations for the indicators are waste and totally needlees!"

@team172011 please dont get me wrong, but you are wrong with your statement. As long as you do not provide a example for a value of a indicator which reflects in a wrong decision (rule). You cannot find one.

from ta4j.

nimo23 avatar nimo23 commented on May 19, 2024

I won't use an implementation built on BigDecimal

I will not use it, either.

Did not get the point why calculations of indicators need this useless precision.

from ta4j.

team172011 avatar team172011 commented on May 19, 2024

@edlins its Value at the moment ;-)

from ta4j.

nimo23 avatar nimo23 commented on May 19, 2024

Please use common abbreviations for the math operations of the Num-Interface

  1. Value should not be used for a class - it s a so common word. Call it "Num". Use this:
// better
public interface Num<D extends Number> extends Serializable, Comparable<Num> {
..
}
  1. Factory should not be used for a class - it s too generic.
// Instead of this:
Factory f = getNumFactory();
// rename to this:
Num f = getNumInstance(); // call it getNumInstance
  1. Rename math operations for better usability:

Instead of this:

Value.valueOf(9).multiply(9).multiply(9).divide(3).multipy(Value.valueOf(2));

use this:

Num.of(9).mult(9).div(3).mult(Num.of(2))

It s a reduction of 50% of code and better readability!

from ta4j.

briandilley avatar briandilley commented on May 19, 2024

from ta4j.

team172011 avatar team172011 commented on May 19, 2024

@briandilley how should an interface have a static method that returns the corresponding class? You cannot have this in the Num interface

I have updated the prototype and added a simple factory. It allows to choose the delegate via enum:

import static TimeSeriesFactory.delegate.BigDecimal;
import static TimeSeriesFactory.delegate.Decimal10f;
public void canDeclareAndInit() {
        // declare and initialize empty time series of two different types
        ColumnarTimeSeries decimal10fBase = TimeSeriesFactory.create(Decimal10f);
        ColumnarTimeSeries bigDecimalBase = TimeSeriesFactory.create(BigDecimal);

        // declare and initialize indicators on the time series
        ClosePriceIndicator closePriceIndicator = new ClosePriceIndicator(bigDecimalBase);
        SMAIndicator smaIndicator = new SMAIndicator(closePriceIndicator, 10);

        ClosePriceIndicator closePriceIndicatorLong = new ClosePriceIndicator(decimal10fBase);
        SMAIndicator smaIndicatorLong = new SMAIndicator(closePriceIndicator, 10);
}

from ta4j.

briandilley avatar briandilley commented on May 19, 2024

from ta4j.

nimo23 avatar nimo23 commented on May 19, 2024

We can not provide a double implementation, as long as we do not have a robust method for equality or comparability check between doubles. If we have one, we can fully switch to double without providing other implementations: #113

from ta4j.

damir78 avatar damir78 commented on May 19, 2024

Guys, I think you need to follow the important software principle : KISS )))))

Feel you free to reuse:

bildschirmfoto 2017-12-10 um 01 53 55

from ta4j.

briandilley avatar briandilley commented on May 19, 2024

from ta4j.

damir78 avatar damir78 commented on May 19, 2024

@briandilley ... Abstraction is the key... ... And make different implementations: Double, BigDecimal, Decimal, SuperDecimal, SmallDecimal, WhateverDecimal.....

from ta4j.

briandilley avatar briandilley commented on May 19, 2024

from ta4j.

team172011 avatar team172011 commented on May 19, 2024

@damir78 implementing an interface with generic class parameter is not the problem. Exactly this has been done in the prototype with Double, BigDecimal and Decimal4f. Take a look at the ColumnarTimeSeries , where the user can decide which data type he wants to use via generic class parameter (and NumFactory...).

The indicators just call the Num interface and work with generic data type of the ColumnarTimeSeries by which they was initialized. At the moment I am looking for a prettier way to get the possibility to use valueOf() functions in the indicators (We need this because it is often necessary to start with sum = 0 etc.). At the moment I am working with the NumFactory that can be reached via the ColumnarTimeSeries, but this is a little bit more complicated...

So far the only acceptable solution I could imagine is to work with a factory pattern: Make constructor of TimeSeries private and add static getter methods for creating a TimeSeries with the corresponding data type the user wants, like:

TimeSeries doubleSeries = TimeSeries.getDoubleBasedSeries(name, ticks)
TimeSeries decimal4fSeries = TimeSeries.getDecimal4fBasedSeries(name, ticks)
TimeSeries bigDecimalSeries = TimeSeries.getBigDecimalBasedSeries(name, ticks)

from ta4j.

nimo23 avatar nimo23 commented on May 19, 2024

I dont get the point why user should be allowed to select different kinds of Number-Types as long as we do not have a "robust method to compare doubles". It s dangerous - because user expect comparing numbers is equal between any number types. But this is not the case and user expect the same valide rules as with other kind of numbers. If we have a robust method, then there is also no need for other kind of number types and we can fully switch to double. As long as we do not have it, using other kind of number types expect BigDecimal is dangerous. So no need for different number types. Look at other frameworks - they only work with one number type.

It is better to improve this lib by providing consistent double comparisions techniques as described in #113 instead of ignoring these facts and having a bunch of ugly different number types, including jdecimal for nonsense - this does not simplify the overall framework and makes it not robust. Why do you focus on such useless things?

from ta4j.

nimo23 avatar nimo23 commented on May 19, 2024

@briandilley could u recommend a better lib ?

from ta4j.

damir78 avatar damir78 commented on May 19, 2024

@team172011 Short answer for how-to: provide custom initialization-> in one case init with 0, in ohter with NaN, etc.
Long answer for how-to: I'm currently writing my own ta library : using my implementation : my rolling window will be initliazed in some cases with 0(sum) or with NaNs. Rolling window is ready for calculations if ... the result is not more NaN, or passed unstable period....

@briandilley good decision, because this one is not "battle ready". I never used ta4j as well. Another problem of this framework and why I'm not using ta4j: it is index based (int index...) :)

@nimo23 "...better lib" -> ta-lib - old good library. Of course, ta4j has some nice good ideas. You can take they with. You can use ta-lib with ta4j strategies for example or write your own ta implementation

from ta4j.

team172011 avatar team172011 commented on May 19, 2024

#161

from ta4j.

Related Issues (20)

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.