code: public static <N extends Number & Comparable<? super N>>
  	Predicate<EvolutionResult<?, N>> byFitnessConvergence(
  		final int shortFilterSize,
  		final int longFilterSize,
  		final BiPredicate<DoubleMoments, DoubleMoments> proceed
  	) {
  		return new FitnessConvergenceLimit<>(
  			shortFilterSize,
  			longFilterSize,
  			proceed
  		);
  	}
docs: Return a predicate, which will truncate the evolution stream if the
  fitness is converging. Two filters of different lengths are used to
  smooth the best fitness across the generations.
  
  <pre>{@code
  final Phenotype<DoubleGene, Double> result = engine.stream()
  .limit(byFitnessConvergence(5, 15, (s, l) -> {
  final double div = max(abs(s.getMean()), abs(l.getMean()));
  final eps = abs(s.getMean() - l.getMean())/(div <= 10E-20 ? 1.0 : div);
  return eps >= 10E-5
  }))
  .collect(toBestPhenotype());
  }</pre>
  
  In the example above, the moving average of the short- and long filter
  is used for determining the fitness convergence.
  
  @apiNote The returned predicate maintains mutable state.
  Using it in a parallel evolution streams needs external synchronization
  of the {@code test} method.
  
  @since 3.7
  
  @param shortFilterSize the size of the short filter
  @param longFilterSize the size of the long filter. The long filter size
  also determines the minimum number of generations of the evolution
  stream.
  @param proceed the predicate which determines when the evolution stream
  is truncated. The first parameter of the predicate contains the
  double statistics of the short filter and the second parameter
  contains the statistics of the long filter
  @param <N> the fitness type
  @return a new fitness convergence strategy
  @throws NullPointerException if the {@code proceed} predicate is
  {@code null}