tradingview-screener 0.2.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/CHANGELOG.md +18 -0
- data/LICENSE.txt +21 -0
- data/README.md +179 -0
- data/lib/tradingview/screener.rb +4 -0
- data/lib/tradingview_screener/base.rb +291 -0
- data/lib/tradingview_screener/client.rb +206 -0
- data/lib/tradingview_screener/predicate.rb +107 -0
- data/lib/tradingview_screener/relation.rb +581 -0
- data/lib/tradingview_screener/screen_data.rb +343 -0
- data/lib/tradingview_screener/version.rb +5 -0
- data/lib/tradingview_screener.rb +31 -0
- metadata +116 -0
checksums.yaml
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
---
|
|
2
|
+
SHA256:
|
|
3
|
+
metadata.gz: 98dcc60a6b081a91a83d869aa27e5bb214eebc94b12cf6646db98f1a35c4fede
|
|
4
|
+
data.tar.gz: d9edd2c6cfb433d5a66a41e49d76ff2add1c82e1d4657a63cedfcae7f2a67539
|
|
5
|
+
SHA512:
|
|
6
|
+
metadata.gz: d4c6d6ca81ff9c6aeaeba7935e64fa154924efae09a40b3e369ea4612b35f9939d7eafaec53a4dec3553e79e5279d653de26f8a13bb67b6931c34e90c3e11631
|
|
7
|
+
data.tar.gz: 636af5fc960959ee4a172ff7ef9547cebf72cfdf6778c73711795249c644b8930f74bac660c3af5aa1c6f2ca8ff529c49fc7f9796cdcb8b068e89f1f1c6305da
|
data/CHANGELOG.md
ADDED
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
## 0.2.0
|
|
4
|
+
|
|
5
|
+
### Changed
|
|
6
|
+
- Gem/package name is `tradingview-screener` (RubyGems hyphen convention); require remains `tradingview_screener`.
|
|
7
|
+
- Extracted into a standalone publishable gem.
|
|
8
|
+
- Removed implicit ActiveSupport dependency (`presence` / `blank?`).
|
|
9
|
+
- Pure Ruby runtime: only stdlib + optional `socksify` for SOCKS proxies.
|
|
10
|
+
- Added a tracked Ruby version for reproducible development with RVM.
|
|
11
|
+
- Added strict gem validation and a Ruby 3.1-3.4 CI test matrix.
|
|
12
|
+
- Clarified Ruby support, unofficial API status, and the release workflow.
|
|
13
|
+
|
|
14
|
+
### Added
|
|
15
|
+
- ActiveRecord-style `Relation` / `Base` / scopes API.
|
|
16
|
+
- Built-in momentum trend, momentum scalping, and open-range breakout scopes.
|
|
17
|
+
- `ScreenData` parser/converter for saved TradingView screener URLs/HTML.
|
|
18
|
+
- HTTP and SOCKS proxy support on the scanner client.
|
data/LICENSE.txt
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Victor Yang
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
data/README.md
ADDED
|
@@ -0,0 +1,179 @@
|
|
|
1
|
+
# tradingview-screener
|
|
2
|
+
|
|
3
|
+
ActiveRecord-style Ruby client for TradingView's scanner API.
|
|
4
|
+
|
|
5
|
+
Inspired by [shner-elmo/TradingView-Screener](https://github.com/shner-elmo/TradingView-Screener), but designed like Rails ORM relations — not a Python port.
|
|
6
|
+
|
|
7
|
+
This is an unofficial client and is not affiliated with or endorsed by TradingView. The scanner endpoints are not a guaranteed public API and may change without notice.
|
|
8
|
+
|
|
9
|
+
Gem name uses hyphens (`tradingview-screener`); the require path and module use underscores:
|
|
10
|
+
|
|
11
|
+
```ruby
|
|
12
|
+
require "tradingview_screener"
|
|
13
|
+
# also works: require "tradingview/screener" # Bundler default for hyphen gems
|
|
14
|
+
TradingviewScreener::Stock
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
If Bundler does not load it automatically, set:
|
|
18
|
+
|
|
19
|
+
```ruby
|
|
20
|
+
gem "tradingview-screener", require: "tradingview_screener"
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
## Install
|
|
24
|
+
|
|
25
|
+
Ruby 3.1 or newer is required.
|
|
26
|
+
|
|
27
|
+
### From a local path
|
|
28
|
+
|
|
29
|
+
```ruby
|
|
30
|
+
# Gemfile
|
|
31
|
+
gem "tradingview-screener", path: "../tradingview-screener"
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
### From RubyGems
|
|
35
|
+
|
|
36
|
+
```ruby
|
|
37
|
+
# Gemfile
|
|
38
|
+
gem "tradingview-screener", "~> 0.2"
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
```bash
|
|
42
|
+
gem install tradingview-screener
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
SOCKS proxies are optional. If you need them:
|
|
46
|
+
|
|
47
|
+
```ruby
|
|
48
|
+
gem "socksify", "~> 1.7"
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
## Quick start
|
|
52
|
+
|
|
53
|
+
```ruby
|
|
54
|
+
require "tradingview_screener"
|
|
55
|
+
|
|
56
|
+
rows = TradingviewScreener::Stock
|
|
57
|
+
.where(close: 10..300)
|
|
58
|
+
.where(volume: TradingviewScreener::Predicates.gt(1_000_000))
|
|
59
|
+
.where(TradingviewScreener::Stock[:close].gt(TradingviewScreener::Stock[:SMA20]))
|
|
60
|
+
.order(volume: :desc)
|
|
61
|
+
.limit(20)
|
|
62
|
+
.to_a
|
|
63
|
+
|
|
64
|
+
rows.first["ticker"] # => "NASDAQ:NVDA"
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
## ORM-shaped API
|
|
68
|
+
|
|
69
|
+
| ActiveRecord | tradingview-screener |
|
|
70
|
+
|---|---|
|
|
71
|
+
| `Model.where(a: 1)` | `Stock.where(is_primary: true)` |
|
|
72
|
+
| `where(price: 10..20)` | `where(close: 10..300)` |
|
|
73
|
+
| `where(status: %w[a b])` | `where(type: %w[stock fund])` |
|
|
74
|
+
| `order(created_at: :desc)` | `order(premarket_volume: :desc)` |
|
|
75
|
+
| `limit` / `offset` | same |
|
|
76
|
+
| `select(:id, :name)` | `select("name", "close")` |
|
|
77
|
+
| `scope :active, -> { ... }` | same |
|
|
78
|
+
| `default_scope` | same |
|
|
79
|
+
| `all` / `to_a` / `count` / `pluck` / `first` | same |
|
|
80
|
+
| relation is immutable chain | same |
|
|
81
|
+
|
|
82
|
+
### Operators
|
|
83
|
+
|
|
84
|
+
```ruby
|
|
85
|
+
where(volume: TradingviewScreener::Predicates.gt(1_000_000))
|
|
86
|
+
where(market_cap_basic: TradingviewScreener::Predicates.gte(1_000_000_000))
|
|
87
|
+
where(ATRP: TradingviewScreener::Predicates.lt(6))
|
|
88
|
+
|
|
89
|
+
# Arel-like nodes
|
|
90
|
+
where(TradingviewScreener::Stock[:close].gt(TradingviewScreener::Stock[:SMA20]))
|
|
91
|
+
where(TradingviewScreener::Stock[:typespecs].has(["common"]))
|
|
92
|
+
```
|
|
93
|
+
|
|
94
|
+
### Scopes
|
|
95
|
+
|
|
96
|
+
```ruby
|
|
97
|
+
class MyScreen < TradingviewScreener::Base
|
|
98
|
+
market "america"
|
|
99
|
+
default_scope { where(is_primary: true) }
|
|
100
|
+
|
|
101
|
+
scope :liquid, -> {
|
|
102
|
+
where("AvgValue.Traded_10d" => TradingviewScreener::Predicates.gt(50_000_000))
|
|
103
|
+
}
|
|
104
|
+
scope :wide_atr, -> {
|
|
105
|
+
where(ATRP: TradingviewScreener::Predicates.gt(1))
|
|
106
|
+
.where(ATRP: TradingviewScreener::Predicates.lt(6))
|
|
107
|
+
}
|
|
108
|
+
end
|
|
109
|
+
|
|
110
|
+
MyScreen.liquid.wide_atr.order(volume: :desc).limit(50).load
|
|
111
|
+
```
|
|
112
|
+
|
|
113
|
+
### Built-in momentum scopes
|
|
114
|
+
|
|
115
|
+
```ruby
|
|
116
|
+
TradingviewScreener::Stock.momentum_trend_long.to_a
|
|
117
|
+
TradingviewScreener::Stock.momentum_trend_short.ids
|
|
118
|
+
TradingviewScreener::Stock.momentum_scalping_long.to_a
|
|
119
|
+
TradingviewScreener::Stock.open_range_breakout_short.ids
|
|
120
|
+
```
|
|
121
|
+
|
|
122
|
+
### Saved screener URL / screen_data
|
|
123
|
+
|
|
124
|
+
```ruby
|
|
125
|
+
rel = TradingviewScreener::Stock.from_screener_url(
|
|
126
|
+
"https://www.tradingview.com/screener/YOUR_SCREEN_ID/",
|
|
127
|
+
cookies: {
|
|
128
|
+
"sessionid" => ENV.fetch("TRADINGVIEW_SESSIONID"),
|
|
129
|
+
"sessionid_sign" => ENV.fetch("TRADINGVIEW_SESSIONID_SIGN")
|
|
130
|
+
}
|
|
131
|
+
)
|
|
132
|
+
rel.to_a
|
|
133
|
+
```
|
|
134
|
+
|
|
135
|
+
### Cookies / proxy
|
|
136
|
+
|
|
137
|
+
```ruby
|
|
138
|
+
TradingviewScreener::Stock.momentum_trend_long.load(
|
|
139
|
+
cookies: {
|
|
140
|
+
"sessionid" => ENV.fetch("TRADINGVIEW_SESSIONID"),
|
|
141
|
+
"sessionid_sign" => ENV.fetch("TRADINGVIEW_SESSIONID_SIGN")
|
|
142
|
+
},
|
|
143
|
+
proxy: "http://user:pass@127.0.0.1:7890"
|
|
144
|
+
)
|
|
145
|
+
```
|
|
146
|
+
|
|
147
|
+
## Debugging
|
|
148
|
+
|
|
149
|
+
```ruby
|
|
150
|
+
rel = TradingviewScreener::Stock
|
|
151
|
+
.where(TradingviewScreener::Stock[:close].gt(TradingviewScreener::Stock[:SMA20]))
|
|
152
|
+
.limit(10)
|
|
153
|
+
|
|
154
|
+
puts rel.to_sql # JSON payload sent to scanner
|
|
155
|
+
puts rel.scanner_url
|
|
156
|
+
```
|
|
157
|
+
|
|
158
|
+
## Development
|
|
159
|
+
|
|
160
|
+
```bash
|
|
161
|
+
bundle install
|
|
162
|
+
bundle exec rspec
|
|
163
|
+
bundle exec rake build # => pkg/tradingview-screener-0.2.0.gem
|
|
164
|
+
gem build tradingview-screener.gemspec --strict
|
|
165
|
+
```
|
|
166
|
+
|
|
167
|
+
Before publishing, verify that the version is not already in use and inspect the package:
|
|
168
|
+
|
|
169
|
+
```bash
|
|
170
|
+
gem build tradingview-screener.gemspec --strict
|
|
171
|
+
gem contents --show-install-dir tradingview-screener # after installing the built gem
|
|
172
|
+
gem push tradingview-screener-0.2.0.gem
|
|
173
|
+
```
|
|
174
|
+
|
|
175
|
+
RubyGems MFA is required for releases. Source code and issue tracking are available on [GitHub](https://github.com/Youngv/tradingview-screener).
|
|
176
|
+
|
|
177
|
+
## License
|
|
178
|
+
|
|
179
|
+
MIT
|
|
@@ -0,0 +1,291 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module TradingviewScreener
|
|
4
|
+
# ActiveRecord-style model base.
|
|
5
|
+
#
|
|
6
|
+
# class Stock < TradingviewScreener::Base
|
|
7
|
+
# market "america"
|
|
8
|
+
# default_scope { where(is_primary: true) }
|
|
9
|
+
# scope :liquid, -> { where(volume: gt(1_000_000)) }
|
|
10
|
+
# end
|
|
11
|
+
#
|
|
12
|
+
# Stock.liquid.where(close: 10..300).order(volume: :desc).limit(20).load
|
|
13
|
+
class Base
|
|
14
|
+
class << self
|
|
15
|
+
def inherited(subclass)
|
|
16
|
+
super
|
|
17
|
+
subclass.instance_variable_set(:@scopes, {})
|
|
18
|
+
subclass.instance_variable_set(:@default_scopes, [])
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
def market(name = nil)
|
|
22
|
+
if name.nil?
|
|
23
|
+
@default_market
|
|
24
|
+
else
|
|
25
|
+
@default_market = name.to_s
|
|
26
|
+
end
|
|
27
|
+
end
|
|
28
|
+
alias default_market market
|
|
29
|
+
|
|
30
|
+
def columns(*names)
|
|
31
|
+
if names.empty?
|
|
32
|
+
@default_columns
|
|
33
|
+
else
|
|
34
|
+
@default_columns = names.flatten.map(&:to_s)
|
|
35
|
+
end
|
|
36
|
+
end
|
|
37
|
+
alias default_columns columns
|
|
38
|
+
|
|
39
|
+
def default_limit(value = nil)
|
|
40
|
+
if value.nil?
|
|
41
|
+
@default_limit
|
|
42
|
+
else
|
|
43
|
+
@default_limit = Integer(value)
|
|
44
|
+
end
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
def default_order_values
|
|
48
|
+
@default_order
|
|
49
|
+
end
|
|
50
|
+
alias default_order default_order_values
|
|
51
|
+
|
|
52
|
+
def order_by(column, direction = :desc)
|
|
53
|
+
@default_order = [{ "sortBy" => column.to_s, "sortOrder" => direction.to_s == "asc" ? "asc" : "desc" }]
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
def default_where
|
|
57
|
+
@default_where
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
def default_filter2
|
|
61
|
+
@default_filter2
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
def filter2(value)
|
|
65
|
+
@default_filter2 = value
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
def scope(name, body = nil, &block)
|
|
69
|
+
callable = body || block
|
|
70
|
+
raise ArgumentError, "scope needs a callable" unless callable
|
|
71
|
+
|
|
72
|
+
scopes[name.to_sym] = callable
|
|
73
|
+
define_singleton_method(name) do |*args, **kwargs|
|
|
74
|
+
rel = all
|
|
75
|
+
result =
|
|
76
|
+
if callable.arity.nonzero? || kwargs.any?
|
|
77
|
+
rel.instance_exec(*args, **kwargs, &callable)
|
|
78
|
+
else
|
|
79
|
+
rel.instance_exec(&callable)
|
|
80
|
+
end
|
|
81
|
+
result.nil? ? none : result
|
|
82
|
+
end
|
|
83
|
+
end
|
|
84
|
+
|
|
85
|
+
def default_scope(body = nil, &block)
|
|
86
|
+
callable = body || block
|
|
87
|
+
raise ArgumentError, "default_scope needs a callable" unless callable
|
|
88
|
+
|
|
89
|
+
default_scopes << callable
|
|
90
|
+
end
|
|
91
|
+
|
|
92
|
+
def all
|
|
93
|
+
rel = Relation.new(self)
|
|
94
|
+
default_scopes.reduce(rel) do |memo, callable|
|
|
95
|
+
result = memo.instance_exec(&callable)
|
|
96
|
+
result.nil? ? memo : result
|
|
97
|
+
end
|
|
98
|
+
end
|
|
99
|
+
|
|
100
|
+
def none
|
|
101
|
+
all.none
|
|
102
|
+
end
|
|
103
|
+
|
|
104
|
+
def where(...) = all.where(...)
|
|
105
|
+
def rewhere(...) = all.rewhere(...)
|
|
106
|
+
def select(...) = all.select(...)
|
|
107
|
+
def order(...) = all.order(...)
|
|
108
|
+
def limit(...) = all.limit(...)
|
|
109
|
+
def offset(...) = all.offset(...)
|
|
110
|
+
def market!(...) = all.market(...)
|
|
111
|
+
def tickers(...) = all.tickers(...)
|
|
112
|
+
def merge(...) = all.merge(...)
|
|
113
|
+
def except(...) = all.except(...)
|
|
114
|
+
def load(**kwargs) = all.load(**kwargs)
|
|
115
|
+
def to_a(**kwargs) = all.to_a(**kwargs)
|
|
116
|
+
def count(**kwargs) = all.count(**kwargs)
|
|
117
|
+
def first(...) = all.first(...)
|
|
118
|
+
def last(...) = all.last(...)
|
|
119
|
+
def pluck(...) = all.pluck(...)
|
|
120
|
+
def pick(...) = all.pick(...)
|
|
121
|
+
def ids(**kwargs) = all.ids(**kwargs)
|
|
122
|
+
def exists?(**kwargs) = all.exists?(**kwargs)
|
|
123
|
+
def find_each(**opts, &block) = all.to_a(**opts).each(&block)
|
|
124
|
+
|
|
125
|
+
def [](name)
|
|
126
|
+
Field.new(name)
|
|
127
|
+
end
|
|
128
|
+
|
|
129
|
+
def unscoped
|
|
130
|
+
Relation.new(self)
|
|
131
|
+
end
|
|
132
|
+
|
|
133
|
+
def from_payload(payload)
|
|
134
|
+
Relation.from_payload(self, payload)
|
|
135
|
+
end
|
|
136
|
+
|
|
137
|
+
def from_screen_data(screen_data)
|
|
138
|
+
from_payload(ScreenData.to_payload(screen_data))
|
|
139
|
+
end
|
|
140
|
+
|
|
141
|
+
def from_screener_url(url, cookies: nil, proxy: nil, timeout: 20)
|
|
142
|
+
fetched = ScreenData.fetch(url, cookies: cookies, proxy: proxy, timeout: timeout)
|
|
143
|
+
from_payload(fetched["payload"])
|
|
144
|
+
end
|
|
145
|
+
|
|
146
|
+
protected
|
|
147
|
+
|
|
148
|
+
def scopes
|
|
149
|
+
@scopes ||= {}
|
|
150
|
+
end
|
|
151
|
+
|
|
152
|
+
def default_scopes
|
|
153
|
+
@default_scopes ||= []
|
|
154
|
+
end
|
|
155
|
+
end
|
|
156
|
+
end
|
|
157
|
+
|
|
158
|
+
# Convenience root model for US equities.
|
|
159
|
+
class Stock < Base
|
|
160
|
+
market "america"
|
|
161
|
+
order_by "market_cap_basic", :desc
|
|
162
|
+
default_limit 50
|
|
163
|
+
|
|
164
|
+
default_scope do
|
|
165
|
+
where(is_primary: true)
|
|
166
|
+
end
|
|
167
|
+
|
|
168
|
+
scope :primary, -> { where(is_primary: true) }
|
|
169
|
+
|
|
170
|
+
scope :priced_between, lambda { |min, max|
|
|
171
|
+
where(close: min..max)
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
# Captured from live MT LONG / MT SHORT TradingView screeners.
|
|
175
|
+
scope :momentum_trend_long, lambda {
|
|
176
|
+
select(
|
|
177
|
+
"ticker-view", "close", "type", "typespecs", "pricescale", "minmov", "fractional", "minmove2",
|
|
178
|
+
"currency", "change", "volume", "relative_volume_10d_calc", "market_cap_basic",
|
|
179
|
+
"fundamental_currency_code", "price_earnings_ttm", "earnings_per_share_diluted_ttm",
|
|
180
|
+
"earnings_per_share_diluted_yoy_growth_ttm", "dividends_yield_current", "sector.tr",
|
|
181
|
+
"market", "sector", "AnalystRating", "AnalystRating.tr", "premarket_change", "premarket_volume"
|
|
182
|
+
).where(
|
|
183
|
+
"AvgValue.Traded_10d" => gt(50_000_000),
|
|
184
|
+
ATRP: gt(1)
|
|
185
|
+
).where(
|
|
186
|
+
market_cap_basic: gte(1_000_000_000),
|
|
187
|
+
premarket_volume: gt(30_000),
|
|
188
|
+
"relative_volume_intraday|5" => gt(1.1),
|
|
189
|
+
ATRP: lt(6),
|
|
190
|
+
close: 10..300
|
|
191
|
+
).where(
|
|
192
|
+
TradingviewScreener::Stock[:close].gt(TradingviewScreener::Stock[:SMA20])
|
|
193
|
+
).order(premarket_volume: :desc).limit(100)
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
scope :momentum_trend_short, lambda {
|
|
197
|
+
select(
|
|
198
|
+
"ticker-view", "close", "type", "typespecs", "pricescale", "minmov", "fractional", "minmove2",
|
|
199
|
+
"currency", "change", "volume", "relative_volume_10d_calc", "market_cap_basic",
|
|
200
|
+
"fundamental_currency_code", "price_earnings_ttm", "earnings_per_share_diluted_ttm",
|
|
201
|
+
"earnings_per_share_diluted_yoy_growth_ttm", "dividends_yield_current", "sector.tr",
|
|
202
|
+
"market", "sector", "AnalystRating", "AnalystRating.tr", "premarket_change", "premarket_volume"
|
|
203
|
+
).where(
|
|
204
|
+
TradingviewScreener::Stock[:close].lt(TradingviewScreener::Stock[:SMA20]),
|
|
205
|
+
"relative_volume_intraday|5" => gt(1.1),
|
|
206
|
+
premarket_volume: gt(30_000),
|
|
207
|
+
ATRP: lt(6)
|
|
208
|
+
).where(
|
|
209
|
+
ATRP: gt(1),
|
|
210
|
+
"AvgValue.Traded_10d" => gt(50_000_000),
|
|
211
|
+
market_cap_basic: gte(1_000_000_000),
|
|
212
|
+
close: 10..300
|
|
213
|
+
).order(market_cap_basic: :desc).limit(100)
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
# Captured from live MS LONG / MS SHORT TradingView screeners.
|
|
217
|
+
scope :momentum_scalping_long, lambda {
|
|
218
|
+
select(
|
|
219
|
+
"ticker-view", "close", "type", "typespecs", "pricescale", "minmov", "fractional", "minmove2",
|
|
220
|
+
"currency", "change", "volume", "relative_volume_10d_calc", "market_cap_basic",
|
|
221
|
+
"fundamental_currency_code", "price_earnings_ttm", "earnings_per_share_diluted_ttm",
|
|
222
|
+
"earnings_per_share_diluted_yoy_growth_ttm", "dividends_yield_current", "sector.tr",
|
|
223
|
+
"market", "sector", "AnalystRating", "AnalystRating.tr", "premarket_change", "premarket_volume"
|
|
224
|
+
).where(
|
|
225
|
+
close: 10..300,
|
|
226
|
+
"relative_volume_intraday|5" => gt(1.1),
|
|
227
|
+
ATRP: lt(6),
|
|
228
|
+
market_cap_basic: gte(1_000_000_000),
|
|
229
|
+
premarket_gap: gt(0.5),
|
|
230
|
+
premarket_volume: gt(30_000)
|
|
231
|
+
).where(
|
|
232
|
+
premarket_gap: lt(8),
|
|
233
|
+
ATRP: gt(1)
|
|
234
|
+
).order(premarket_volume: :desc).limit(100)
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
scope :momentum_scalping_short, lambda {
|
|
238
|
+
select(
|
|
239
|
+
"ticker-view", "close", "type", "typespecs", "pricescale", "minmov", "fractional", "minmove2",
|
|
240
|
+
"currency", "change", "volume", "relative_volume_10d_calc", "market_cap_basic",
|
|
241
|
+
"fundamental_currency_code", "price_earnings_ttm", "earnings_per_share_diluted_ttm",
|
|
242
|
+
"earnings_per_share_diluted_yoy_growth_ttm", "dividends_yield_current", "sector.tr",
|
|
243
|
+
"market", "sector", "AnalystRating", "AnalystRating.tr", "premarket_change", "premarket_volume"
|
|
244
|
+
).where(
|
|
245
|
+
premarket_gap: gt(-8),
|
|
246
|
+
market_cap_basic: gte(1_000_000_000)
|
|
247
|
+
).where(
|
|
248
|
+
ATRP: gt(1)
|
|
249
|
+
).where(
|
|
250
|
+
ATRP: lt(6),
|
|
251
|
+
premarket_volume: gt(30_000),
|
|
252
|
+
"relative_volume_intraday|5" => gt(1.1),
|
|
253
|
+
premarket_gap: lt(-0.5),
|
|
254
|
+
close: 10..300
|
|
255
|
+
).order(premarket_volume: :desc).limit(100)
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
# Captured from live ORB LONG / ORB SHORT TradingView screeners.
|
|
259
|
+
scope :open_range_breakout_long, lambda {
|
|
260
|
+
select(
|
|
261
|
+
"ticker-view", "close", "type", "typespecs", "pricescale", "minmov", "fractional", "minmove2",
|
|
262
|
+
"currency", "change", "volume", "relative_volume_10d_calc", "market_cap_basic",
|
|
263
|
+
"fundamental_currency_code", "price_earnings_ttm", "earnings_per_share_diluted_ttm",
|
|
264
|
+
"earnings_per_share_diluted_yoy_growth_ttm", "dividends_yield_current", "sector.tr",
|
|
265
|
+
"market", "sector", "AnalystRating", "AnalystRating.tr"
|
|
266
|
+
).where(
|
|
267
|
+
market_cap_basic: gte(1_000_000_000),
|
|
268
|
+
average_volume_10d_calc: gt(3_000_000),
|
|
269
|
+
close: 10..300,
|
|
270
|
+
ATRP: gt(5),
|
|
271
|
+
premarket_gap: gt(2)
|
|
272
|
+
).order(market_cap_basic: :desc).limit(100)
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
scope :open_range_breakout_short, lambda {
|
|
276
|
+
select(
|
|
277
|
+
"ticker-view", "close", "type", "typespecs", "pricescale", "minmov", "fractional", "minmove2",
|
|
278
|
+
"currency", "change", "volume", "relative_volume_10d_calc", "market_cap_basic",
|
|
279
|
+
"fundamental_currency_code", "price_earnings_ttm", "earnings_per_share_diluted_ttm",
|
|
280
|
+
"earnings_per_share_diluted_yoy_growth_ttm", "dividends_yield_current", "sector.tr",
|
|
281
|
+
"market", "sector", "AnalystRating", "AnalystRating.tr"
|
|
282
|
+
).where(
|
|
283
|
+
close: 10..300,
|
|
284
|
+
premarket_gap: lt(-3),
|
|
285
|
+
average_volume_10d_calc: gt(3_000_000),
|
|
286
|
+
ATRP: gt(5),
|
|
287
|
+
market_cap_basic: gte(1_000_000_000)
|
|
288
|
+
).order(market_cap_basic: :desc).limit(100)
|
|
289
|
+
}
|
|
290
|
+
end
|
|
291
|
+
end
|