lunaris 0.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/README.md +1 -0
- data/lib/lunaris/autoloader.rb +42 -0
- data/lib/lunaris/base.rb +11 -0
- data/lib/lunaris/config/application.rb +21 -0
- data/lib/lunaris/config/logger.rb +39 -0
- data/lib/lunaris/config/sentry_client.rb +20 -0
- data/lib/lunaris/helpers/file_helper.rb +21 -0
- data/lib/lunaris/helpers/loggable.rb +15 -0
- data/lib/lunaris/resources/builders/crypto_market_data_builder.rb +98 -0
- data/lib/lunaris/resources/clients/aws.rb +34 -0
- data/lib/lunaris/resources/clients/binance.rb +22 -0
- data/lib/lunaris/resources/clients/fgi.rb +35 -0
- data/lib/lunaris/resources/controllers/market_automation_controller.rb +17 -0
- data/lib/lunaris/resources/endpoint.rb +39 -0
- data/lib/lunaris/resources/jobs/market_automation_job.rb +39 -0
- data/lib/lunaris/resources/models/crypto.rb +41 -0
- data/lib/lunaris/resources/models/crypto_market_data.rb +19 -0
- data/lib/lunaris/resources/models/indicator.rb +68 -0
- data/lib/lunaris/resources/models/ticker.rb +31 -0
- data/lib/lunaris/resources/repositories/file_storage_repository.rb +35 -0
- data/lib/lunaris/resources/services/crypto_market_data_pipeline.rb +47 -0
- data/lib/lunaris/resources/services/indicator_service.rb +59 -0
- data/lib/lunaris/resources/services/indicators/base_indicator_service.rb +43 -0
- data/lib/lunaris/resources/services/indicators/momentum.rb +15 -0
- data/lib/lunaris/resources/services/indicators/sentiment.rb +58 -0
- data/lib/lunaris/resources/services/indicators/trend_following.rb +15 -0
- data/lib/lunaris/resources/services/indicators/volatility.rb +15 -0
- data/lib/lunaris/resources/services/indicators/volume_based.rb +15 -0
- data/lib/lunaris/version.rb +5 -0
- data/lib/lunaris.rb +25 -0
- metadata +242 -0
checksums.yaml
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
---
|
|
2
|
+
SHA256:
|
|
3
|
+
metadata.gz: 18415f6f131d128759635c746a46116dd5fae820875baecd27211cdfcf2a6e63
|
|
4
|
+
data.tar.gz: 228861eb236f3d8eae1e8e78352a28389eff617068eff54f9935004ffe481052
|
|
5
|
+
SHA512:
|
|
6
|
+
metadata.gz: 0fb5fc4c6a1c547aae1dd0560d99634055ca35c4559e31c4f8a9d296871b963d0c3aaba8ff0d3ecbe692f698e37f23b33acd485345d2b1e170012b73f9b02191
|
|
7
|
+
data.tar.gz: 3197810c146528e3094dd693876eecd2cf4f065ae9d369515a94e073438ae0e824dfe7294eb6306f7637b106efef55dd55d0df23165d79faa8ab580bb2db1045
|
data/README.md
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
# next-gen-ruby
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'zeitwerk'
|
|
4
|
+
|
|
5
|
+
module Lunaris
|
|
6
|
+
class Autoloader
|
|
7
|
+
class Inflector < ::Zeitwerk::Inflector
|
|
8
|
+
INFLECTIONS_MAP = {
|
|
9
|
+
version: 'VERSION'
|
|
10
|
+
}.freeze
|
|
11
|
+
|
|
12
|
+
def camelize(basename, _abspath)
|
|
13
|
+
INFLECTIONS_MAP[basename.to_sym] || super
|
|
14
|
+
end
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
class << self
|
|
18
|
+
LIB_PATH = ::File.dirname(__dir__).freeze
|
|
19
|
+
|
|
20
|
+
def setup!
|
|
21
|
+
loader = ::Zeitwerk::Loader.new
|
|
22
|
+
loader.tag = 'lunaris'
|
|
23
|
+
loader.inflector = Inflector.new
|
|
24
|
+
loader.push_dir(LIB_PATH)
|
|
25
|
+
loader.collapse(::File.join(LIB_PATH, 'lunaris', 'resources'))
|
|
26
|
+
|
|
27
|
+
ignored_paths.each { |path| loader.ignore(path) }
|
|
28
|
+
|
|
29
|
+
loader.setup
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
private
|
|
33
|
+
|
|
34
|
+
def ignored_paths
|
|
35
|
+
[
|
|
36
|
+
::File.join(LIB_PATH, 'lunaris', 'patches'),
|
|
37
|
+
::File.join(LIB_PATH, 'lunaris', 'config', 'schedule.rb')
|
|
38
|
+
].freeze
|
|
39
|
+
end
|
|
40
|
+
end
|
|
41
|
+
end
|
|
42
|
+
end
|
data/lib/lunaris/base.rb
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'time'
|
|
4
|
+
require 'tzinfo'
|
|
5
|
+
|
|
6
|
+
module Lunaris
|
|
7
|
+
module Config
|
|
8
|
+
module Application
|
|
9
|
+
def self.set_timezone(timezone) # rubocop: disable Naming/AccessorMethodName
|
|
10
|
+
ENV['TZ'] = timezone
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
def self.timestamp_to_date(timestamp)
|
|
14
|
+
utc_time = Time.at(timestamp).utc
|
|
15
|
+
tz = TZInfo::Timezone.get('GMT')
|
|
16
|
+
|
|
17
|
+
tz.utc_to_local(utc_time)
|
|
18
|
+
end
|
|
19
|
+
end
|
|
20
|
+
end
|
|
21
|
+
end
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'logger'
|
|
4
|
+
require 'singleton'
|
|
5
|
+
require 'date'
|
|
6
|
+
|
|
7
|
+
module Lunaris
|
|
8
|
+
module Config
|
|
9
|
+
class Logger
|
|
10
|
+
include Singleton
|
|
11
|
+
include Lunaris::Helpers::FileHelper
|
|
12
|
+
|
|
13
|
+
def initialize
|
|
14
|
+
log_directory = defined?(RSpec) ? 'spec' : File.expand_path('', Dir.pwd)
|
|
15
|
+
log_file = "#{log_directory}/#{base_path_hour}/#{file_name}"
|
|
16
|
+
FileUtils.mkdir_p(File.dirname(log_file))
|
|
17
|
+
|
|
18
|
+
@logger = ::Logger.new(log_file)
|
|
19
|
+
@logger.level = ::Logger::DEBUG
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
def info(message)
|
|
23
|
+
@logger.info(message)
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
def error(message)
|
|
27
|
+
@logger.error(message)
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
private
|
|
31
|
+
|
|
32
|
+
def file_name
|
|
33
|
+
env = ENV['APP_ENV'] || 'development'
|
|
34
|
+
|
|
35
|
+
"#{env}.log"
|
|
36
|
+
end
|
|
37
|
+
end
|
|
38
|
+
end
|
|
39
|
+
end
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'sentry-ruby'
|
|
4
|
+
|
|
5
|
+
module Lunaris
|
|
6
|
+
module Config
|
|
7
|
+
class SentryClient
|
|
8
|
+
def self.setup
|
|
9
|
+
return unless defined?(Sentry)
|
|
10
|
+
|
|
11
|
+
Sentry.init do |config|
|
|
12
|
+
config.dsn = ENV.fetch('SENTRY_DSN', nil)
|
|
13
|
+
config.traces_sample_rate = 0.2
|
|
14
|
+
config.environment = ENV.fetch('APP_ENV', 'development')
|
|
15
|
+
config.send_default_pii = true
|
|
16
|
+
end
|
|
17
|
+
end
|
|
18
|
+
end
|
|
19
|
+
end
|
|
20
|
+
end
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Lunaris
|
|
4
|
+
module Helpers
|
|
5
|
+
module FileHelper
|
|
6
|
+
def initialize_time
|
|
7
|
+
@today ||= DateTime.parse(ENV.fetch('DATETIME', DateTime.now))
|
|
8
|
+
end
|
|
9
|
+
|
|
10
|
+
def base_path_day
|
|
11
|
+
initialize_time
|
|
12
|
+
"data/#{@today.strftime('%Y-%m-%d')}"
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
def base_path_hour
|
|
16
|
+
initialize_time
|
|
17
|
+
"#{base_path_day}/#{@today.strftime('%H')}"
|
|
18
|
+
end
|
|
19
|
+
end
|
|
20
|
+
end
|
|
21
|
+
end
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Lunaris
|
|
4
|
+
module Builders
|
|
5
|
+
class CryptoMarketDataBuilder
|
|
6
|
+
attr_reader :crypto, :tickers, :indicator_obj
|
|
7
|
+
|
|
8
|
+
INDICATOR_TYPES = %i[sma_values ema_values macd_values rsi_values sr_values tsi_values bb_values atr_values
|
|
9
|
+
kc_values obv_values fgi_values].freeze
|
|
10
|
+
|
|
11
|
+
def initialize(crypto, tickers, indicators)
|
|
12
|
+
@crypto = crypto
|
|
13
|
+
@tickers = tickers
|
|
14
|
+
@indicator_obj = Models::Indicator.new(indicators: indicators)
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
def build
|
|
18
|
+
crypto.to_h.merge(data: build_tickers_data)
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
private
|
|
22
|
+
|
|
23
|
+
def build_tickers_data
|
|
24
|
+
Models::Ticker.sorted_by_date(tickers).first(6).map do |ticker|
|
|
25
|
+
ticker_data(ticker)
|
|
26
|
+
end
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
def build_values(type, filtered_indicators)
|
|
30
|
+
case type
|
|
31
|
+
when :fgi_values
|
|
32
|
+
day_builder(filtered_indicators)
|
|
33
|
+
else
|
|
34
|
+
datetime_builder(filtered_indicators)
|
|
35
|
+
end
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
def datetime_builder(values)
|
|
39
|
+
values.each_slice(2).with_object({}) do |(indicator, index), data|
|
|
40
|
+
process_indicator(indicator, index, data)
|
|
41
|
+
end
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
def filter_indicators(type, ticker_datetime)
|
|
45
|
+
method = type == :fgi_values ? :filter_by_day : :filter_by_datetime
|
|
46
|
+
indicator_obj.send(method, indicator_obj.indicators.send(type), ticker_datetime)
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
def format_attribute(variable, index)
|
|
50
|
+
attribute = variable.to_s.delete('@').to_sym
|
|
51
|
+
attribute = format_sma_ema(attribute, index) if %i[sma ema].include?(attribute)
|
|
52
|
+
attribute
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
def format_indicators(ticker_datetime)
|
|
56
|
+
INDICATOR_TYPES.each_with_object({}) do |type, hash|
|
|
57
|
+
filtered_indicators = filter_indicators(type, ticker_datetime)
|
|
58
|
+
hash[type] = format_to_hash(build_values(type, filtered_indicators))
|
|
59
|
+
end
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
def format_sma_ema(attribute, index)
|
|
63
|
+
suffix = (index.even? ? '10' : '20')
|
|
64
|
+
:"#{attribute}#{suffix}"
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
def format_to_hash(values)
|
|
68
|
+
return values if values.is_a?(Hash)
|
|
69
|
+
|
|
70
|
+
values.reduce({}, :merge)
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
def process_indicator(indicator, index, data)
|
|
74
|
+
indicator.instance_variables.each do |variable|
|
|
75
|
+
attribute = format_attribute(variable, index)
|
|
76
|
+
next if attribute == :date_time
|
|
77
|
+
|
|
78
|
+
data[attribute] = indicator.instance_variable_get(variable)
|
|
79
|
+
end
|
|
80
|
+
end
|
|
81
|
+
|
|
82
|
+
def ticker_data(ticker)
|
|
83
|
+
ticker.to_h.merge(format_indicators(ticker.date))
|
|
84
|
+
end
|
|
85
|
+
|
|
86
|
+
def day_builder(values)
|
|
87
|
+
values.map do |d|
|
|
88
|
+
{
|
|
89
|
+
value: d.value,
|
|
90
|
+
value_classification: d.value_classification,
|
|
91
|
+
timestamp: Config::Application.timestamp_to_date(d.timestamp.to_i),
|
|
92
|
+
time_until_update: d.time_until_update
|
|
93
|
+
}
|
|
94
|
+
end
|
|
95
|
+
end
|
|
96
|
+
end
|
|
97
|
+
end
|
|
98
|
+
end
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'aws-sdk-lambda'
|
|
4
|
+
|
|
5
|
+
module Lunaris
|
|
6
|
+
module Clients
|
|
7
|
+
class Aws
|
|
8
|
+
include Lunaris::Helpers::Loggable
|
|
9
|
+
|
|
10
|
+
attr_reader :client
|
|
11
|
+
|
|
12
|
+
def initialize
|
|
13
|
+
@client = ::Aws::Lambda::Client.new(
|
|
14
|
+
access_key_id: ENV.fetch('AWS_ACCESS_KEY_ID'),
|
|
15
|
+
secret_access_key: ENV.fetch('AWS_SECRET_ACCESS_KEY'),
|
|
16
|
+
region: ENV.fetch('AWS_REGION', 'eu-west-2')
|
|
17
|
+
)
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
def invoke(params)
|
|
21
|
+
response = client.invoke({
|
|
22
|
+
function_name: 'Lunaris',
|
|
23
|
+
invocation_type: 'RequestResponse',
|
|
24
|
+
log_type: 'Tail',
|
|
25
|
+
payload: JSON.generate(params)
|
|
26
|
+
})
|
|
27
|
+
|
|
28
|
+
payload = JSON.parse(response.payload.string)
|
|
29
|
+
log_info("Lambda call with params: #{params} - status: #{payload['statusCode']}")
|
|
30
|
+
payload['body']
|
|
31
|
+
end
|
|
32
|
+
end
|
|
33
|
+
end
|
|
34
|
+
end
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'rest-client'
|
|
4
|
+
require 'json'
|
|
5
|
+
|
|
6
|
+
module Lunaris
|
|
7
|
+
module Clients
|
|
8
|
+
class Binance
|
|
9
|
+
attr_reader :client, :params
|
|
10
|
+
|
|
11
|
+
def initialize(params)
|
|
12
|
+
@params = params
|
|
13
|
+
@client = Clients::Aws.new
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
def candlestick
|
|
17
|
+
lambda_params = { lambda: { class: 'Clients::Binance', method: 'candlestick' } }
|
|
18
|
+
client.invoke(lambda_params.merge(params))
|
|
19
|
+
end
|
|
20
|
+
end
|
|
21
|
+
end
|
|
22
|
+
end
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'rest-client'
|
|
4
|
+
require 'json'
|
|
5
|
+
require 'fileutils'
|
|
6
|
+
|
|
7
|
+
module Lunaris
|
|
8
|
+
module Clients
|
|
9
|
+
class Fgi
|
|
10
|
+
attr_accessor :params
|
|
11
|
+
attr_reader :client, :logger, :today
|
|
12
|
+
|
|
13
|
+
def initialize(params)
|
|
14
|
+
@params = params
|
|
15
|
+
@client = Clients::Aws.new
|
|
16
|
+
@today = Date.today
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
def values
|
|
20
|
+
datetime = DateTime.parse(ENV.fetch('DATETIME', nil))
|
|
21
|
+
params[:limit] = 10_000 if datetime.to_date < today
|
|
22
|
+
base_path = "data/#{datetime.strftime('%Y-%m-%d')}"
|
|
23
|
+
|
|
24
|
+
file_repo = Repositories::FileStorageRepository.new(base_path, 'fgi.json')
|
|
25
|
+
data = file_repo.load
|
|
26
|
+
return data if data
|
|
27
|
+
|
|
28
|
+
lambda_params = { lambda: { class: 'Clients::Fgi', method: 'values' } }
|
|
29
|
+
json_data = client.invoke(lambda_params.merge(params))
|
|
30
|
+
file_repo.save(json_data)
|
|
31
|
+
json_data
|
|
32
|
+
end
|
|
33
|
+
end
|
|
34
|
+
end
|
|
35
|
+
end
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Lunaris
|
|
4
|
+
module Controllers
|
|
5
|
+
class MarketAutomationController
|
|
6
|
+
attr_reader :data
|
|
7
|
+
|
|
8
|
+
def initialize(data)
|
|
9
|
+
@data = data
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
def execute_job
|
|
13
|
+
Jobs::MarketAutomationJob.new(data).perform
|
|
14
|
+
end
|
|
15
|
+
end
|
|
16
|
+
end
|
|
17
|
+
end
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Lunaris
|
|
4
|
+
class Endpoint
|
|
5
|
+
attr_reader :logger, :params
|
|
6
|
+
|
|
7
|
+
def initialize(params)
|
|
8
|
+
ENV.fetch('APP_ENV', 'development')
|
|
9
|
+
Config::Application.set_timezone('GMT')
|
|
10
|
+
Config::SentryClient.setup
|
|
11
|
+
|
|
12
|
+
@params = params
|
|
13
|
+
set_datetime
|
|
14
|
+
@logger = Config::Logger.instance
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
def call
|
|
18
|
+
data = params[:data]
|
|
19
|
+
controller = Object.const_get(params[:controller_name]).new(data)
|
|
20
|
+
controller.send(params[:action_name])
|
|
21
|
+
rescue StandardError => e
|
|
22
|
+
logger.error("Error occured: #{e.message}")
|
|
23
|
+
Sentry.capture_exception(e)
|
|
24
|
+
raise e
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
private
|
|
28
|
+
|
|
29
|
+
def set_datetime
|
|
30
|
+
if params&.dig(:data, :timestamps, :end)
|
|
31
|
+
timestamp = params.dig(:data, :timestamps, :end) / 1000
|
|
32
|
+
datetime = Config::Application.timestamp_to_date(timestamp)
|
|
33
|
+
ENV['DATETIME'] = datetime.to_s
|
|
34
|
+
else
|
|
35
|
+
ENV['DATETIME'] = Time.now.to_s
|
|
36
|
+
end
|
|
37
|
+
end
|
|
38
|
+
end
|
|
39
|
+
end
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'ostruct'
|
|
4
|
+
|
|
5
|
+
module Lunaris
|
|
6
|
+
module Jobs
|
|
7
|
+
class MarketAutomationJob
|
|
8
|
+
include Lunaris::Helpers::Loggable
|
|
9
|
+
|
|
10
|
+
attr_reader :params
|
|
11
|
+
|
|
12
|
+
def initialize(params = nil)
|
|
13
|
+
@params = params
|
|
14
|
+
@logger = Config::Logger.instance
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
def perform
|
|
18
|
+
cryptos = Models::Crypto.all
|
|
19
|
+
log_info("MarketAutomationJob started with #{cryptos.size} cryptos")
|
|
20
|
+
|
|
21
|
+
futures = create_pipelines(cryptos)
|
|
22
|
+
Concurrent::Promises.zip(*futures).value!
|
|
23
|
+
|
|
24
|
+
log_info('MarketAutomationJob completed successfully')
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
private
|
|
28
|
+
|
|
29
|
+
def create_pipelines(cryptos)
|
|
30
|
+
cryptos.map do |crypto|
|
|
31
|
+
Concurrent::Promises.future do
|
|
32
|
+
context = OpenStruct.new(crypto: crypto, params: params)
|
|
33
|
+
Services::CryptoMarketDataPipeline.new(context).process
|
|
34
|
+
end
|
|
35
|
+
end
|
|
36
|
+
end
|
|
37
|
+
end
|
|
38
|
+
end
|
|
39
|
+
end
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'csv'
|
|
4
|
+
|
|
5
|
+
module Lunaris
|
|
6
|
+
module Models
|
|
7
|
+
class Crypto
|
|
8
|
+
DEFAULT_TICKER_PARAMS = { interval: '1h', limit: 50 }.freeze
|
|
9
|
+
FILE_PATH = 'data/cryptos.csv'
|
|
10
|
+
|
|
11
|
+
attr_reader :name, :symbol
|
|
12
|
+
|
|
13
|
+
def initialize(params)
|
|
14
|
+
@name, @symbol = params.values_at(:name, :symbol)
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
def self.all
|
|
18
|
+
CSV.foreach(FILE_PATH, headers: true).map do |row|
|
|
19
|
+
Models::Crypto.new(name: row['Name'], symbol: row['Symbol'])
|
|
20
|
+
end
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
def tickers(params = nil)
|
|
24
|
+
params = DEFAULT_TICKER_PARAMS.merge(params || {})
|
|
25
|
+
.merge(symbol: "#{symbol}USDT")
|
|
26
|
+
|
|
27
|
+
candles = Clients::Binance.new(params).candlestick
|
|
28
|
+
candles.map { |entry| Models::Ticker.new(entry, self) }
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
def to_h
|
|
32
|
+
{
|
|
33
|
+
crypto: {
|
|
34
|
+
name: name,
|
|
35
|
+
symbol: symbol
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
end
|
|
39
|
+
end
|
|
40
|
+
end
|
|
41
|
+
end
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Lunaris
|
|
4
|
+
module Models
|
|
5
|
+
class CryptoMarketData
|
|
6
|
+
attr_reader :crypto, :tickers, :indicators
|
|
7
|
+
|
|
8
|
+
def initialize(crypto, tickers, indicators)
|
|
9
|
+
@crypto = crypto
|
|
10
|
+
@tickers = tickers
|
|
11
|
+
@indicators = indicators
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
def to_h
|
|
15
|
+
Builders::CryptoMarketDataBuilder.new(crypto, tickers, indicators).build
|
|
16
|
+
end
|
|
17
|
+
end
|
|
18
|
+
end
|
|
19
|
+
end
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'technical-analysis'
|
|
4
|
+
|
|
5
|
+
module Lunaris
|
|
6
|
+
module Models
|
|
7
|
+
class Indicator
|
|
8
|
+
DATA_PERIOD = {
|
|
9
|
+
adi: 20, atr: 20, bb: 25, cmf: 25,
|
|
10
|
+
ema10: 15, ema20: 25, kc: 15, macd: 39,
|
|
11
|
+
mfi: 20, obv: 20, rsi: 20, sma10: 15,
|
|
12
|
+
sma20: 25, sr: 21, tsi: 43, vwap: 25
|
|
13
|
+
}.freeze
|
|
14
|
+
|
|
15
|
+
attr_reader :cache_data, :indicators, :tickers
|
|
16
|
+
|
|
17
|
+
def initialize(tickers: nil, indicators: nil)
|
|
18
|
+
if tickers
|
|
19
|
+
@tickers = tickers
|
|
20
|
+
@cache_data = {}
|
|
21
|
+
cache_data_by_periods(DATA_PERIOD.values.uniq)
|
|
22
|
+
else
|
|
23
|
+
@indicators = indicators
|
|
24
|
+
end
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
def calculate(type, options = {})
|
|
28
|
+
indicator_class = Object.const_get("TechnicalAnalysis::#{type.capitalize}")
|
|
29
|
+
key = :"#{type}#{options[:period]}"
|
|
30
|
+
period = DATA_PERIOD[type.to_sym] || DATA_PERIOD[key]
|
|
31
|
+
|
|
32
|
+
if %i[adi obv vwap].map(&:to_s).include?(type)
|
|
33
|
+
indicator_class.calculate(cache_data[period])
|
|
34
|
+
else
|
|
35
|
+
indicator_class.calculate(cache_data[period], options)
|
|
36
|
+
end
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
def filter_by_datetime(values, datetime, index = 0)
|
|
40
|
+
unless values.any?(&:date_time)
|
|
41
|
+
all_values = []
|
|
42
|
+
|
|
43
|
+
[values.sma10, values.sma20, values.ema10, values.ema20].each_with_index do |indicator_list, index|
|
|
44
|
+
all_values << filter_by_datetime(indicator_list, datetime, index) if indicator_list&.any?
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
return all_values.flatten
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
filtered_indicators = values.select { |value| value.date_time == datetime }
|
|
51
|
+
[filtered_indicators, index].flatten
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
def filter_by_day(data, ticker_datetime)
|
|
55
|
+
ticker_time = Time.parse(ticker_datetime)
|
|
56
|
+
ticker_timestamp = ticker_time.to_date.to_time.to_i
|
|
57
|
+
|
|
58
|
+
data.select { |d| d.timestamp == ticker_timestamp }
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
private
|
|
62
|
+
|
|
63
|
+
def cache_data_by_periods(periods)
|
|
64
|
+
periods.each { |period| cache_data[period] ||= tickers.last(period) }
|
|
65
|
+
end
|
|
66
|
+
end
|
|
67
|
+
end
|
|
68
|
+
end
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Lunaris
|
|
4
|
+
module Models
|
|
5
|
+
class Ticker
|
|
6
|
+
attr_reader :date, :open_price, :close_price, :high_price, :low_price, :volume, :crypto
|
|
7
|
+
|
|
8
|
+
def initialize(candle_params, crypto)
|
|
9
|
+
@date, @open_price, @close_price, @high_price, @low_price, @volume =
|
|
10
|
+
%i[date open_price close_price high_price low_price volume].map { |k| candle_params[k.to_s] }
|
|
11
|
+
|
|
12
|
+
@crypto = crypto
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
def self.sorted_by_date(tickers)
|
|
16
|
+
tickers.sort_by { |ticker| -Time.parse(ticker.date).to_i }
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
def to_h
|
|
20
|
+
{
|
|
21
|
+
date: date,
|
|
22
|
+
open_price: open_price,
|
|
23
|
+
close_price: close_price,
|
|
24
|
+
high_price: high_price,
|
|
25
|
+
low_price: low_price,
|
|
26
|
+
volume: volume
|
|
27
|
+
}
|
|
28
|
+
end
|
|
29
|
+
end
|
|
30
|
+
end
|
|
31
|
+
end
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'json'
|
|
4
|
+
require 'fileutils'
|
|
5
|
+
|
|
6
|
+
module Lunaris
|
|
7
|
+
module Repositories
|
|
8
|
+
class FileStorageRepository
|
|
9
|
+
attr_reader :base_directory, :file_name
|
|
10
|
+
|
|
11
|
+
def initialize(base_directory, file_name)
|
|
12
|
+
@base_directory = File.join(Dir.pwd, base_directory)
|
|
13
|
+
@file_name = File.join(@base_directory, file_name)
|
|
14
|
+
@file_name.sub!('data', 'spec/data') if defined?(RSpec)
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
def save(data)
|
|
18
|
+
FileUtils.mkdir_p(File.dirname(file_name))
|
|
19
|
+
File.write(file_name, to_json(data))
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
def load
|
|
23
|
+
return JSON.parse(File.read(file_name)) if File.exist?(file_name)
|
|
24
|
+
|
|
25
|
+
nil
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
private
|
|
29
|
+
|
|
30
|
+
def to_json(data)
|
|
31
|
+
JSON.pretty_generate(data)
|
|
32
|
+
end
|
|
33
|
+
end
|
|
34
|
+
end
|
|
35
|
+
end
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Lunaris
|
|
4
|
+
module Services
|
|
5
|
+
class CryptoMarketDataPipeline
|
|
6
|
+
include Lunaris::Helpers::FileHelper
|
|
7
|
+
include Lunaris::Helpers::Loggable
|
|
8
|
+
|
|
9
|
+
attr_reader :crypto, :params
|
|
10
|
+
|
|
11
|
+
def initialize(context)
|
|
12
|
+
@crypto = context.crypto
|
|
13
|
+
@params = context.params
|
|
14
|
+
@file_base_path = base_path_hour
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
def process
|
|
18
|
+
log_info("Processing crypto #{crypto.name} ...")
|
|
19
|
+
|
|
20
|
+
tickers = retrieve_tickers
|
|
21
|
+
indicators = calculate_indicators(tickers)
|
|
22
|
+
export_data(tickers, indicators)
|
|
23
|
+
|
|
24
|
+
log_info("Export data for #{crypto.name}")
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
private
|
|
28
|
+
|
|
29
|
+
def calculate_indicators(tickers)
|
|
30
|
+
indicators = Services::IndicatorService.new(tickers).calculate_all
|
|
31
|
+
log_info("Calculation of indicators for #{crypto.name}: OK")
|
|
32
|
+
indicators
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
def export_data(tickers, indicators)
|
|
36
|
+
data = Models::CryptoMarketData.new(crypto, tickers, indicators).to_h
|
|
37
|
+
Repositories::FileStorageRepository.new(@file_base_path, "#{crypto.name}.json").save(data)
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
def retrieve_tickers
|
|
41
|
+
tickers = crypto.tickers(params)
|
|
42
|
+
log_info("Found #{tickers.count} tickers for #{crypto.name}")
|
|
43
|
+
tickers
|
|
44
|
+
end
|
|
45
|
+
end
|
|
46
|
+
end
|
|
47
|
+
end
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Lunaris
|
|
4
|
+
module Services
|
|
5
|
+
class IndicatorService
|
|
6
|
+
attr_reader :indicator_obj
|
|
7
|
+
|
|
8
|
+
INDICATOR_CLASSES = {
|
|
9
|
+
trend_following: Indicators::TrendFollowing,
|
|
10
|
+
momentum: Indicators::Momentum,
|
|
11
|
+
volatility: Indicators::Volatility,
|
|
12
|
+
volume_based: Indicators::VolumeBased,
|
|
13
|
+
sentiment: Indicators::Sentiment
|
|
14
|
+
}.freeze
|
|
15
|
+
|
|
16
|
+
def initialize(tickers)
|
|
17
|
+
price_data = load_data(tickers).freeze
|
|
18
|
+
@indicator_obj = Models::Indicator.new(tickers: price_data)
|
|
19
|
+
|
|
20
|
+
initialize_indicators
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
INDICATOR_CLASSES.each_key do |indicator|
|
|
24
|
+
define_method(indicator) do
|
|
25
|
+
instance_variable_get("@#{indicator}").calculate_all
|
|
26
|
+
end
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
def calculate_all
|
|
30
|
+
futures = INDICATOR_CLASSES.keys.map do |indicator|
|
|
31
|
+
Concurrent::Future.execute do
|
|
32
|
+
instance_variable_get("@#{indicator}").calculate_all
|
|
33
|
+
end
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
results = futures.map(&:value).reduce({}) do |acc, struct|
|
|
37
|
+
acc.merge(struct.to_h)
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
OpenStruct.new(results)
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
private
|
|
44
|
+
|
|
45
|
+
def initialize_indicators
|
|
46
|
+
INDICATOR_CLASSES.each do |indicator, klass|
|
|
47
|
+
instance_variable_set("@#{indicator}", klass.new(indicator_obj))
|
|
48
|
+
end
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
def load_data(tickers)
|
|
52
|
+
tickers.map do |t|
|
|
53
|
+
{ date_time: t.date, open: t.open_price, high: t.high_price,
|
|
54
|
+
low: t.low_price, close: t.close_price, volume: t.volume }
|
|
55
|
+
end
|
|
56
|
+
end
|
|
57
|
+
end
|
|
58
|
+
end
|
|
59
|
+
end
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Lunaris
|
|
4
|
+
module Services
|
|
5
|
+
module Indicators
|
|
6
|
+
class BaseIndicatorService
|
|
7
|
+
attr_reader :indicator_obj
|
|
8
|
+
|
|
9
|
+
def initialize(indicator_obj)
|
|
10
|
+
@indicator_obj = indicator_obj
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
def calculate_all
|
|
14
|
+
results = self.class::OPTIONS.each_with_object({}) do |(type, opts), result|
|
|
15
|
+
result[:"#{type}_values"] = calculate_indicator_for(type, opts)
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
OpenStruct.new(results)
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
private
|
|
22
|
+
|
|
23
|
+
def calculate_indicator_for(type, opts)
|
|
24
|
+
if %i[sma ema].include?(type)
|
|
25
|
+
calculate_moving_average(type)
|
|
26
|
+
else
|
|
27
|
+
calculate_indicator(type, opts)
|
|
28
|
+
end
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
def calculate_moving_average(type)
|
|
32
|
+
OpenStruct.new(
|
|
33
|
+
self.class::OPTIONS[type].transform_values { |opts| calculate_indicator(type, opts) }
|
|
34
|
+
)
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
def calculate_indicator(type, opts = nil)
|
|
38
|
+
indicator_obj.calculate(type.to_s, opts || self.class::OPTIONS[type])
|
|
39
|
+
end
|
|
40
|
+
end
|
|
41
|
+
end
|
|
42
|
+
end
|
|
43
|
+
end
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Lunaris
|
|
4
|
+
module Services
|
|
5
|
+
module Indicators
|
|
6
|
+
class Momentum < BaseIndicatorService
|
|
7
|
+
OPTIONS = {
|
|
8
|
+
rsi: { period: 14, price_key: :close },
|
|
9
|
+
sr: {},
|
|
10
|
+
tsi: { low_period: 13, high_period: 25, price_key: :close }
|
|
11
|
+
}.freeze
|
|
12
|
+
end
|
|
13
|
+
end
|
|
14
|
+
end
|
|
15
|
+
end
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'concurrent'
|
|
4
|
+
|
|
5
|
+
module Lunaris
|
|
6
|
+
module Services
|
|
7
|
+
module Indicators
|
|
8
|
+
class Sentiment < BaseIndicatorService
|
|
9
|
+
OPTIONS = {
|
|
10
|
+
adi: { period: 14, price_key: :close },
|
|
11
|
+
mfi: {}
|
|
12
|
+
}.freeze
|
|
13
|
+
|
|
14
|
+
@fgi_semaphore = Concurrent::Semaphore.new(1)
|
|
15
|
+
|
|
16
|
+
def calculate_all
|
|
17
|
+
OpenStruct.new({
|
|
18
|
+
fgi_values: fear_greed_index,
|
|
19
|
+
adi_values: calculate_indicator(:adi),
|
|
20
|
+
mfi_values: calculate_indicator(:mfi)
|
|
21
|
+
})
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
def fear_greed_index(limit = 6)
|
|
25
|
+
params = { limit: limit }
|
|
26
|
+
self.class.fgi_semaphore.acquire
|
|
27
|
+
|
|
28
|
+
begin
|
|
29
|
+
deep_struct(Clients::Fgi.new(params).values)
|
|
30
|
+
ensure
|
|
31
|
+
self.class.fgi_semaphore.release
|
|
32
|
+
end
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
private
|
|
36
|
+
|
|
37
|
+
def deep_struct(obj)
|
|
38
|
+
case obj
|
|
39
|
+
when Hash
|
|
40
|
+
OpenStruct.new(obj.transform_values { |v| deep_struct(to_i(v)) })
|
|
41
|
+
when Array
|
|
42
|
+
obj.map { |v| deep_struct(v) }
|
|
43
|
+
else
|
|
44
|
+
to_i(obj)
|
|
45
|
+
end
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
def to_i(value)
|
|
49
|
+
value.to_s.match?(/^\d+$/) ? value.to_i : value
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
class << self
|
|
53
|
+
attr_reader :fgi_semaphore
|
|
54
|
+
end
|
|
55
|
+
end
|
|
56
|
+
end
|
|
57
|
+
end
|
|
58
|
+
end
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Lunaris
|
|
4
|
+
module Services
|
|
5
|
+
module Indicators
|
|
6
|
+
class TrendFollowing < BaseIndicatorService
|
|
7
|
+
OPTIONS = {
|
|
8
|
+
sma: { 'sma10' => { period: 10, price_key: :close }, 'sma20' => { period: 20, price_key: :close } },
|
|
9
|
+
ema: { 'ema10' => { period: 10, price_key: :close }, 'ema20' => { period: 20, price_key: :close } },
|
|
10
|
+
macd: { fast_period: 12, slow_period: 26, signal_period: 9, price_key: :close }
|
|
11
|
+
}.freeze
|
|
12
|
+
end
|
|
13
|
+
end
|
|
14
|
+
end
|
|
15
|
+
end
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Lunaris
|
|
4
|
+
module Services
|
|
5
|
+
module Indicators
|
|
6
|
+
class VolumeBased < BaseIndicatorService
|
|
7
|
+
OPTIONS = {
|
|
8
|
+
obv: { period: 20, price_key: :close },
|
|
9
|
+
cmf: { period: 20 },
|
|
10
|
+
vwap: {}
|
|
11
|
+
}.freeze
|
|
12
|
+
end
|
|
13
|
+
end
|
|
14
|
+
end
|
|
15
|
+
end
|
data/lib/lunaris.rb
ADDED
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative 'lunaris/autoloader'
|
|
4
|
+
|
|
5
|
+
module Lunaris
|
|
6
|
+
NAME = 'Lunaris Ruby'
|
|
7
|
+
|
|
8
|
+
@mutex = ::Mutex.new
|
|
9
|
+
|
|
10
|
+
class << self
|
|
11
|
+
def app_info=(value)
|
|
12
|
+
::Lunaris::Base.app_info = value
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
def ping
|
|
16
|
+
{ status: 200 }
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
def version
|
|
20
|
+
VERSION
|
|
21
|
+
end
|
|
22
|
+
end
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
Lunaris::Autoloader.setup!
|
metadata
ADDED
|
@@ -0,0 +1,242 @@
|
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
|
2
|
+
name: lunaris
|
|
3
|
+
version: !ruby/object:Gem::Version
|
|
4
|
+
version: 0.0.0
|
|
5
|
+
platform: ruby
|
|
6
|
+
authors:
|
|
7
|
+
- Stefano Baldazzi
|
|
8
|
+
autorequire:
|
|
9
|
+
bindir: bin
|
|
10
|
+
cert_chain: []
|
|
11
|
+
date: 2025-06-17 00:00:00.000000000 Z
|
|
12
|
+
dependencies:
|
|
13
|
+
- !ruby/object:Gem::Dependency
|
|
14
|
+
name: aws-sdk-lambda
|
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
|
16
|
+
requirements:
|
|
17
|
+
- - "~>"
|
|
18
|
+
- !ruby/object:Gem::Version
|
|
19
|
+
version: '1'
|
|
20
|
+
type: :runtime
|
|
21
|
+
prerelease: false
|
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
23
|
+
requirements:
|
|
24
|
+
- - "~>"
|
|
25
|
+
- !ruby/object:Gem::Version
|
|
26
|
+
version: '1'
|
|
27
|
+
- !ruby/object:Gem::Dependency
|
|
28
|
+
name: concurrent-ruby
|
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
|
30
|
+
requirements:
|
|
31
|
+
- - "~>"
|
|
32
|
+
- !ruby/object:Gem::Version
|
|
33
|
+
version: 1.3.5
|
|
34
|
+
type: :runtime
|
|
35
|
+
prerelease: false
|
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
37
|
+
requirements:
|
|
38
|
+
- - "~>"
|
|
39
|
+
- !ruby/object:Gem::Version
|
|
40
|
+
version: 1.3.5
|
|
41
|
+
- !ruby/object:Gem::Dependency
|
|
42
|
+
name: csv
|
|
43
|
+
requirement: !ruby/object:Gem::Requirement
|
|
44
|
+
requirements:
|
|
45
|
+
- - ">="
|
|
46
|
+
- !ruby/object:Gem::Version
|
|
47
|
+
version: '0'
|
|
48
|
+
type: :runtime
|
|
49
|
+
prerelease: false
|
|
50
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
51
|
+
requirements:
|
|
52
|
+
- - ">="
|
|
53
|
+
- !ruby/object:Gem::Version
|
|
54
|
+
version: '0'
|
|
55
|
+
- !ruby/object:Gem::Dependency
|
|
56
|
+
name: file_utils
|
|
57
|
+
requirement: !ruby/object:Gem::Requirement
|
|
58
|
+
requirements:
|
|
59
|
+
- - ">="
|
|
60
|
+
- !ruby/object:Gem::Version
|
|
61
|
+
version: '0'
|
|
62
|
+
type: :runtime
|
|
63
|
+
prerelease: false
|
|
64
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
65
|
+
requirements:
|
|
66
|
+
- - ">="
|
|
67
|
+
- !ruby/object:Gem::Version
|
|
68
|
+
version: '0'
|
|
69
|
+
- !ruby/object:Gem::Dependency
|
|
70
|
+
name: json
|
|
71
|
+
requirement: !ruby/object:Gem::Requirement
|
|
72
|
+
requirements:
|
|
73
|
+
- - ">="
|
|
74
|
+
- !ruby/object:Gem::Version
|
|
75
|
+
version: '0'
|
|
76
|
+
type: :runtime
|
|
77
|
+
prerelease: false
|
|
78
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
79
|
+
requirements:
|
|
80
|
+
- - ">="
|
|
81
|
+
- !ruby/object:Gem::Version
|
|
82
|
+
version: '0'
|
|
83
|
+
- !ruby/object:Gem::Dependency
|
|
84
|
+
name: ostruct
|
|
85
|
+
requirement: !ruby/object:Gem::Requirement
|
|
86
|
+
requirements:
|
|
87
|
+
- - "~>"
|
|
88
|
+
- !ruby/object:Gem::Version
|
|
89
|
+
version: '0.6'
|
|
90
|
+
type: :runtime
|
|
91
|
+
prerelease: false
|
|
92
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
93
|
+
requirements:
|
|
94
|
+
- - "~>"
|
|
95
|
+
- !ruby/object:Gem::Version
|
|
96
|
+
version: '0.6'
|
|
97
|
+
- !ruby/object:Gem::Dependency
|
|
98
|
+
name: rest-client
|
|
99
|
+
requirement: !ruby/object:Gem::Requirement
|
|
100
|
+
requirements:
|
|
101
|
+
- - ">="
|
|
102
|
+
- !ruby/object:Gem::Version
|
|
103
|
+
version: '0'
|
|
104
|
+
type: :runtime
|
|
105
|
+
prerelease: false
|
|
106
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
107
|
+
requirements:
|
|
108
|
+
- - ">="
|
|
109
|
+
- !ruby/object:Gem::Version
|
|
110
|
+
version: '0'
|
|
111
|
+
- !ruby/object:Gem::Dependency
|
|
112
|
+
name: sentry-ruby
|
|
113
|
+
requirement: !ruby/object:Gem::Requirement
|
|
114
|
+
requirements:
|
|
115
|
+
- - ">="
|
|
116
|
+
- !ruby/object:Gem::Version
|
|
117
|
+
version: '0'
|
|
118
|
+
type: :runtime
|
|
119
|
+
prerelease: false
|
|
120
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
121
|
+
requirements:
|
|
122
|
+
- - ">="
|
|
123
|
+
- !ruby/object:Gem::Version
|
|
124
|
+
version: '0'
|
|
125
|
+
- !ruby/object:Gem::Dependency
|
|
126
|
+
name: technical-analysis
|
|
127
|
+
requirement: !ruby/object:Gem::Requirement
|
|
128
|
+
requirements:
|
|
129
|
+
- - ">="
|
|
130
|
+
- !ruby/object:Gem::Version
|
|
131
|
+
version: '0'
|
|
132
|
+
type: :runtime
|
|
133
|
+
prerelease: false
|
|
134
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
135
|
+
requirements:
|
|
136
|
+
- - ">="
|
|
137
|
+
- !ruby/object:Gem::Version
|
|
138
|
+
version: '0'
|
|
139
|
+
- !ruby/object:Gem::Dependency
|
|
140
|
+
name: tzinfo
|
|
141
|
+
requirement: !ruby/object:Gem::Requirement
|
|
142
|
+
requirements:
|
|
143
|
+
- - "~>"
|
|
144
|
+
- !ruby/object:Gem::Version
|
|
145
|
+
version: 2.0.6
|
|
146
|
+
type: :runtime
|
|
147
|
+
prerelease: false
|
|
148
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
149
|
+
requirements:
|
|
150
|
+
- - "~>"
|
|
151
|
+
- !ruby/object:Gem::Version
|
|
152
|
+
version: 2.0.6
|
|
153
|
+
- !ruby/object:Gem::Dependency
|
|
154
|
+
name: vcr
|
|
155
|
+
requirement: !ruby/object:Gem::Requirement
|
|
156
|
+
requirements:
|
|
157
|
+
- - ">="
|
|
158
|
+
- !ruby/object:Gem::Version
|
|
159
|
+
version: '0'
|
|
160
|
+
type: :runtime
|
|
161
|
+
prerelease: false
|
|
162
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
163
|
+
requirements:
|
|
164
|
+
- - ">="
|
|
165
|
+
- !ruby/object:Gem::Version
|
|
166
|
+
version: '0'
|
|
167
|
+
- !ruby/object:Gem::Dependency
|
|
168
|
+
name: zeitwerk
|
|
169
|
+
requirement: !ruby/object:Gem::Requirement
|
|
170
|
+
requirements:
|
|
171
|
+
- - "~>"
|
|
172
|
+
- !ruby/object:Gem::Version
|
|
173
|
+
version: '2.4'
|
|
174
|
+
type: :runtime
|
|
175
|
+
prerelease: false
|
|
176
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
177
|
+
requirements:
|
|
178
|
+
- - "~>"
|
|
179
|
+
- !ruby/object:Gem::Version
|
|
180
|
+
version: '2.4'
|
|
181
|
+
description: Lunaris is a lightweight gem for manage crypto operations.
|
|
182
|
+
email:
|
|
183
|
+
- stefanobaldazzi40@gmail.com
|
|
184
|
+
executables: []
|
|
185
|
+
extensions: []
|
|
186
|
+
extra_rdoc_files: []
|
|
187
|
+
files:
|
|
188
|
+
- README.md
|
|
189
|
+
- lib/lunaris.rb
|
|
190
|
+
- lib/lunaris/autoloader.rb
|
|
191
|
+
- lib/lunaris/base.rb
|
|
192
|
+
- lib/lunaris/config/application.rb
|
|
193
|
+
- lib/lunaris/config/logger.rb
|
|
194
|
+
- lib/lunaris/config/sentry_client.rb
|
|
195
|
+
- lib/lunaris/helpers/file_helper.rb
|
|
196
|
+
- lib/lunaris/helpers/loggable.rb
|
|
197
|
+
- lib/lunaris/resources/builders/crypto_market_data_builder.rb
|
|
198
|
+
- lib/lunaris/resources/clients/aws.rb
|
|
199
|
+
- lib/lunaris/resources/clients/binance.rb
|
|
200
|
+
- lib/lunaris/resources/clients/fgi.rb
|
|
201
|
+
- lib/lunaris/resources/controllers/market_automation_controller.rb
|
|
202
|
+
- lib/lunaris/resources/endpoint.rb
|
|
203
|
+
- lib/lunaris/resources/jobs/market_automation_job.rb
|
|
204
|
+
- lib/lunaris/resources/models/crypto.rb
|
|
205
|
+
- lib/lunaris/resources/models/crypto_market_data.rb
|
|
206
|
+
- lib/lunaris/resources/models/indicator.rb
|
|
207
|
+
- lib/lunaris/resources/models/ticker.rb
|
|
208
|
+
- lib/lunaris/resources/repositories/file_storage_repository.rb
|
|
209
|
+
- lib/lunaris/resources/services/crypto_market_data_pipeline.rb
|
|
210
|
+
- lib/lunaris/resources/services/indicator_service.rb
|
|
211
|
+
- lib/lunaris/resources/services/indicators/base_indicator_service.rb
|
|
212
|
+
- lib/lunaris/resources/services/indicators/momentum.rb
|
|
213
|
+
- lib/lunaris/resources/services/indicators/sentiment.rb
|
|
214
|
+
- lib/lunaris/resources/services/indicators/trend_following.rb
|
|
215
|
+
- lib/lunaris/resources/services/indicators/volatility.rb
|
|
216
|
+
- lib/lunaris/resources/services/indicators/volume_based.rb
|
|
217
|
+
- lib/lunaris/version.rb
|
|
218
|
+
homepage: https://github.com/Baldaz02/next-gen-ruby
|
|
219
|
+
licenses:
|
|
220
|
+
- MIT
|
|
221
|
+
metadata:
|
|
222
|
+
rubygems_mfa_required: 'true'
|
|
223
|
+
post_install_message:
|
|
224
|
+
rdoc_options: []
|
|
225
|
+
require_paths:
|
|
226
|
+
- lib
|
|
227
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
|
228
|
+
requirements:
|
|
229
|
+
- - ">="
|
|
230
|
+
- !ruby/object:Gem::Version
|
|
231
|
+
version: '2.7'
|
|
232
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
|
233
|
+
requirements:
|
|
234
|
+
- - ">="
|
|
235
|
+
- !ruby/object:Gem::Version
|
|
236
|
+
version: '0'
|
|
237
|
+
requirements: []
|
|
238
|
+
rubygems_version: 3.2.22
|
|
239
|
+
signing_key:
|
|
240
|
+
specification_version: 4
|
|
241
|
+
summary: lunaris-0.0.0
|
|
242
|
+
test_files: []
|