Advertisement
xmd79

WIPFunctionLyaponov

Sep 19th, 2023
219
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
text 12.71 KB | None | 0 0
  1. // This source code is subject to the terms of the Mozilla Public License 2.0 at https://mozilla.org/MPL/2.0/
  2. // © RicardoSantos
  3.  
  4. //@version=5
  5. // checkpoint
  6. // 100 matrix version
  7. // 126 map version
  8.  
  9. //@description Lyapunov exponents are mathematical measures used to describe the behavior of a system over
  10. // time. They are named after Russian mathematician Alexei Lyapunov, who first introduced the concept in the
  11. // late 19th century. The exponent is defined as the rate at which a particular function or variable changes
  12. // over time, and can be positive, negative, or zero.
  13. // Positive exponents indicate that a system tends to grow or expand over time, while negative exponents
  14. // indicate that a system tends to shrink or decay. Zero exponents indicate that the system does not change
  15. // significantly over time. Lyapunov exponents are used in various fields of science and engineering, including
  16. // physics, economics, and biology, to study the long-term behavior of complex systems.
  17. // ~ generated description from vicuna13b
  18. //
  19. // ---
  20. // To calculate the Lyapunov Exponent (LE) of a given Time Series, we need to follow these steps:
  21. // 1. Firstly, you should have access to your data in some format like CSV or Excel file. If not, then you can collect it manually using tools such as stopwatches and measuring tapes.
  22. // 2. Once the data is collected, clean it up by removing any outliers that may skew results. This step involves checking for inconsistencies within your dataset (e.g., extremely large or small values) and either discarding them entirely or replacing with more reasonable estimates based on surrounding values.
  23. // 3. Next, you need to determine the dimension of your time series data. In most cases, this will be equal to the number of variables being measured in each observation period (e.g., temperature, humidity, wind speed).
  24. // 4. Now that we have a clean dataset with known dimensions, we can calculate the LE for our Time Series using the following formula:
  25. // λ = log(||M^T * M - I||)/log(||v||)
  26. // where:
  27. // λ (Lyapunov Exponent) is the quantity that will be calculated.
  28. // ||...|| denotes an Euclidean norm of a vector or matrix, which essentially means taking the square root of the sum of squares for each element in the vector/matrix.
  29. // M represents our Jacobian Matrix whose elements are given by:
  30. // J_ij = (∂fj / ∂xj) where fj is the jth variable and xj is the ith component of the initial condition vector x(t). In other words, each element in this matrix represents how much a small change in one variable affects another.
  31. // I denotes an identity matrix whose elements are all equal to 1 (or any constant value if you prefer). This term essentially acts as a baseline for comparison purposes since we want our Jacobian Matrix M^T * M to be close to it when the system is stable and far away from it when the system is unstable.
  32. // v represents an arbitrary vector whose Euclidean norm ||v|| will serve as a scaling factor in our calculation. The choice of this particular vector does not matter since we are only interested in its magnitude (i.e., length) for purposes of normalization. However, if you want to ensure that your results are accurate and consistent across different datasets or scenarios, it is recommended to use the same initial condition vector x(t) as used earlier when calculating our Jacobian Matrix M.
  33. // 5. Finally, once we have calculated λ using the formula above, we can interpret its value in terms of stability/instability for our Time Series data:
  34. // - If λ < 0, then this indicates that the system is stable (i.e., nearby trajectories will converge towards each other over time).
  35. // - On the other hand, if λ > 0, then this implies that the system is unstable (i.e., nearby trajectories will diverge away from one another over time).
  36. // ~ generated description from airoboros33b
  37. //
  38. // ---
  39. // Reference:
  40. // https://en.wikipedia.org/wiki/Lyapunov_exponent
  41. // https://www.collimator.ai/reference-guides/what-is-a-lyapunov-exponent
  42. // https://blog.abhranil.net/2014/07/22/calculating-the-lyapunov-exponent-of-a-time-series-with-python-code/
  43. // https://www.researchgate.net/publication/222460963_Determining_Lyapunov_Exponents_From_a_Time_Series
  44. // https://physics.stackexchange.com/questions/102529/calculating-lyapunov-exponents-from-a-multi-dimensional-experimental-time-series
  45. //
  46. // ---
  47. // This is a work in progress, it may contain errors so use with caution.
  48. // If you find flaws or suggest something new, please leave a comment bellow.
  49. library("WIPFunctionLyaponov")
  50.  
  51. import RicardoSantos/SimilarityMeasures/1
  52. import RicardoSantos/CommonTypesMapUtil/1
  53.  
  54. // @function helper function to get the name of distance function by a index (0 -> 13).\
  55. // Functions: SSD, Euclidean, Manhattan, Minkowski, Chebyshev, Correlation, Cosine, Camberra, MAE, MSE, Lorentzian, Intersection, Penrose Shape, Meehl.
  56. export _measure_function (int i=0) =>
  57. switch i
  58. 00 => 'ssd'
  59. 01 => 'euclidean'
  60. 02 => 'manhattan'
  61. 03 => 'minkowski'
  62. 04 => 'chebychev'
  63. 05 => 'correlation'
  64. 06 => 'cosine'
  65. 07 => 'camberra'
  66. 08 => 'mae'
  67. 09 => 'mse'
  68. 10 => 'lorentzian'
  69. 11 => 'intersection'
  70. 12 => 'penrose'
  71. 13 => 'meehl'
  72.  
  73. // @function Calculate the vector distance between two vectors.
  74. vdistance (array<float> a, array<float> b, string type = 'ssd') =>
  75. switch type
  76. 'ssd' => SimilarityMeasures.ssd(a, b)
  77. 'euclidean' => SimilarityMeasures.euclidean(a, b)
  78. 'manhattan' => SimilarityMeasures.manhattan(a, b)
  79. 'minkowski' => SimilarityMeasures.minkowski(a, b)
  80. 'chebychev' => SimilarityMeasures.chebyshev(a, b)
  81. 'correlation' => SimilarityMeasures.correlation(a, b)
  82. 'cosine' => SimilarityMeasures.cosine(a, b)
  83. 'camberra' => SimilarityMeasures.camberra(a, b)
  84. 'mae' => SimilarityMeasures.mae(a, b)
  85. 'mse' => SimilarityMeasures.mse(a, b)
  86. 'lorentzian' => SimilarityMeasures.lorentzian(a, b)
  87. 'intersection' => SimilarityMeasures.intersection(a, b)
  88. 'penrose' => SimilarityMeasures.penrose(a, b)
  89. 'meehl' => SimilarityMeasures.meehl(a, b)
  90.  
  91. // @function Helper function to test the output exponents state system and outputs description into a string.
  92. export _test (array<float> L) =>
  93. float _sum = 0.0
  94. int _neg_count = 0
  95. bool _is_chaotic = false
  96. for _e in L
  97. if _e > 0.0
  98. _is_chaotic := true
  99. break
  100. if _e < 0.0
  101. _neg_count += 1
  102. _sum += _e
  103. _is_stable = _neg_count == L.size()
  104. switch
  105. _is_chaotic => 'System is Chaotic.\n Small differences in initial conditions will lead to vastly different outcomes over time.\n' // atleast one positive exponent.
  106. _is_stable => 'System is Stable.\n Small perturbations from equilibrium will decay over time, and the system will return to its original state.\n' // all exponents are negative
  107. _sum == 0.0 => 'System is Neutrally Stable.\n Small perturbations from equilibrium will neither converge nor diverge.\n'//the sum of all Lyapunov exponents is zero
  108. => 'Unable to determine System.'
  109.  
  110. // reference: arXiv:2308.01013
  111. // Bayesian framework for characterizing
  112. // cryptocurrency market dynamics, structural
  113. // dependency, and volatility using potential field
  114. // by: Anoop C V, Neeraj Negi, Anup Aprem
  115.  
  116.  
  117. // @function Estimate the Lyaponov Exponents for multiple series in a row matrix.
  118. // @param data Data series in a row matrix format (a row represents a 1d series).
  119. // @param initial_distance Initial distance limit.
  120. // @param distance_function Name of the distance function to be used, default:`ssd`.
  121. // @returns List of Lyaponov exponents.
  122. export estimate (map<string, CommonTypesMapUtil.ArrayFloat> X, float initial_distance=1.0e6, string distance_function='ssd') =>
  123. array<string> _series = X.keys()
  124. int _N = _series.size()-1
  125. int _length = X.get(_series.get(0)).v.size()
  126. array<float> _L = array.new<float>()
  127. matrix<float> _lambda = matrix.new<float>(_N+1, _length, 0.0)
  128. for [_i, _si] in _series
  129. if _i < _N
  130. for _j = _i + 1 to _N
  131. array<float> _Xi = X.get(_si ).v
  132. array<float> _Xj = X.get(_series.get(_j)).v
  133. float _dist1 = vdistance(_Xi, _Xj, distance_function)
  134. int _Nij = _N - _j
  135. // Current distance under initial distance or predetermined.
  136. if _dist1 <= initial_distance
  137. for _k = 0 to _Nij
  138. float _1k = 1.0 / _k
  139. array<float> _Sik = X.get(_series.get(_i + _k)).v
  140. array<float> _Sjk = X.get(_series.get(_j + _k)).v
  141. // Calculate divergence from initial.
  142. for _l = 0 to _length-1
  143. float _dist2 = vdistance(_Sik.slice(_l, _length), _Sjk.slice(_l, _length), distance_function)
  144. float _div = _1k * math.log(_dist2 / _dist1)
  145. _lambda.set(_k, _l, _div)
  146. _lambda.set(_i, _j, nz(_lambda.get(_i, _j)) + _div)
  147. _L.push(nz(_lambda.get(_i, _j) / _Nij, 0.0))
  148. _L
  149.  
  150. // @function Maximal Lyaponov Exponent.
  151. // @param L List of Lyapunov exponents.
  152. // @returns Highest exponent.
  153. export max (array<float> L) => L.max()
  154.  
  155. import RicardoSantos/DebugConsole/13 as console
  156. logger = console.new()
  157. logger.table.cell_set_height(0, 0, 80.0)
  158. logger.table.cell_set_width(0, 0, 80.0)
  159.  
  160. var matrix<float> mat = matrix.new<float>(5, 10, na)
  161. // test with random source:
  162. // if barstate.islastconfirmedhistory
  163. // for _i = 0 to 4
  164. // for _j = 0 to 9
  165. // mat.set(_i, _j, 1000.0 + 100.0 * math.random(-1.0, 1.0))
  166. // lyap = estimate(mat, array.new<float>(10), e, k, dt)
  167. // log.warning('{0}: {1}', test(lyap), lyap)
  168. // line.new(bar_index, 0, bar_index+1, lyap.get(0))
  169. // for _i = 1 to 9
  170. // line.new(bar_index+_i, lyap.get(_i-1), bar_index+_i+1, lyap.get(_i))
  171.  
  172. // test with assets source:
  173. string _sym0 = 'BTCUSD'
  174. string _sym1 = 'ETHUSD'
  175. string _sym2 = 'XRPUSD'
  176. string _sym3 = 'SOLUSD'
  177. string _sym4 = 'SPX'
  178. var array<string> symbols = array.from(_sym0, _sym1, _sym2, _sym3, _sym4)
  179. float _s0 = request.security(_sym0, timeframe.period, close, barmerge.gaps_on, barmerge.lookahead_off)
  180. float _s1 = request.security(_sym1, timeframe.period, close, barmerge.gaps_on, barmerge.lookahead_off)
  181. float _s2 = request.security(_sym2, timeframe.period, close, barmerge.gaps_on, barmerge.lookahead_off)
  182. float _s3 = request.security(_sym3, timeframe.period, close, barmerge.gaps_on, barmerge.lookahead_off)
  183. float _s4 = request.security(_sym4, timeframe.period, close, barmerge.gaps_on, barmerge.lookahead_off)
  184.  
  185. var map0 = map.new<string, CommonTypesMapUtil.ArrayFloat>()
  186. var map1 = map.new<string, CommonTypesMapUtil.ArrayFloat>()
  187. var map2 = map.new<string, CommonTypesMapUtil.ArrayFloat>()
  188. // mat3 = matrix.new<float>(5, 10, 0.0)
  189. // for _i = 0 to 4
  190. // for _j = 0 to 9
  191. // mat.set(_i, _j, nz(sel(_i, _j)))
  192. // mat2.set(_i, _j, bar_index[_j])
  193. // mat3.set(_i, _j, math.random(-1.0, 1.0))
  194. int length = input.int(10)
  195. if barstate.islastconfirmedhistory
  196. for [_si, _sym] in symbols
  197. _series = CommonTypesMapUtil.ArrayFloat.new(array.new<float>(length, 0.0))
  198. map0.put(_sym, _series)
  199. map1.put('seq1', CommonTypesMapUtil.ArrayFloat.new(array.new<float>(length, 0.0)))
  200. map1.put('seq2', CommonTypesMapUtil.ArrayFloat.new(array.new<float>(length, 0.0)))
  201. map1.put('seq3', CommonTypesMapUtil.ArrayFloat.new(array.new<float>(length, 0.0)))
  202. map2.put('rng1', CommonTypesMapUtil.ArrayFloat.new(array.new<float>(length, 0.0)))
  203. map2.put('rng2', CommonTypesMapUtil.ArrayFloat.new(array.new<float>(length, 0.0)))
  204. map2.put('rng3', CommonTypesMapUtil.ArrayFloat.new(array.new<float>(length, 0.0)))
  205. map2.put('rng4', CommonTypesMapUtil.ArrayFloat.new(array.new<float>(length, 0.0)))
  206. for _i = 0 to length - 1
  207. map0.get(_sym0).v.set(_i, _s0[_i])
  208. map0.get(_sym1).v.set(_i, _s1[_i])
  209. map0.get(_sym2).v.set(_i, _s2[_i])
  210. map0.get(_sym3).v.set(_i, _s3[_i])
  211. map0.get(_sym4).v.set(_i, _s4[_i])
  212. map1.get('seq1').v.set(_i, bar_index[_i])
  213. map1.get('seq2').v.set(_i, bar_index[_i])
  214. map1.get('seq3').v.set(_i, bar_index[_i])
  215. map2.get('rng1').v.set(_i, math.random(-1.0, 1.0))
  216. map2.get('rng2').v.set(_i, math.random(-1.5, 1.0))
  217. map2.get('rng3').v.set(_i, math.random(-2.0, 3.0))
  218. map2.get('rng4').v.set(_i, math.random(-3.0, 5.0))
  219. // logger.queue(str.format('{0}', map0.get(_sym0).v))
  220. lyap0 = estimate(map0, vdistance(map0.get(symbols.get(0)).v, map0.get(symbols.get(1)).v))
  221. logger.queue(str.format('{0}: {1}', _test(lyap0), str.tostring(lyap0, '#.####')))
  222. lyap1 = estimate(map1, vdistance(map1.get('seq1').v, map1.get('seq2').v))
  223. logger.queue(str.format('{0}: {1}', _test(lyap1), str.tostring(lyap1, '#.####')))
  224. lyap2 = estimate(map2, vdistance(map2.get('rng1').v, map2.get('rng2').v))
  225. logger.queue(str.format('{0}: {1}', _test(lyap2), str.tostring(lyap2, '#.####')))
  226. // line.new(bar_index, 0, bar_index+1, lyap.get(0))
  227. // for _i = 1 to 9
  228. // line.new(bar_index+_i, lyap.get(_i-1), bar_index+_i+1, lyap.get(_i))
  229.  
  230. logger.update()
  231.  
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement