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: []
|