Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
QuantConnect
GitHub Repository: QuantConnect/Research
Path: blob/master/Explore/generate_social_media_thumbnails.py
984 views
1
import os
2
import numpy as np
3
import plotly.graph_objects as go
4
from io import BytesIO
5
from pathlib import Path
6
from PIL import Image, ImageDraw, ImageFont
7
from requests import get
8
from typing import List
9
10
def __get_text_content(url: str) -> str:
11
return get(url).content.decode('utf-8')
12
13
def __get_json_content(url: str) -> List:
14
content = __get_text_content(url) \
15
.replace("null", "None").replace("true", "True").replace("false", "False")
16
try:
17
return eval(content)
18
except:
19
exit(f'Invalid content for {url}')
20
21
def __get_profile(url: str) -> Image:
22
if not url or 'icon' in url:
23
return None
24
25
image = get(url.replace("\\",""), stream=True)
26
if image.status_code != 200:
27
return None
28
29
profile = Image.open(image.raw).convert("RGB")
30
31
h,w = profile.size
32
33
# creating luminous image
34
lum_img = Image.new('L',[h,w] ,0)
35
draw = ImageDraw.Draw(lum_img)
36
draw.pieslice([(0,0),(h,w)],0,360,fill=255)
37
img_arr = np.array(profile)
38
lum_img_arr = np.array(lum_img)
39
final_img_arr = np.dstack((img_arr, lum_img_arr))
40
return Image.fromarray(final_img_arr).resize((80,80))
41
42
def __get_equity_curve(strategy, width=1200, height=400) -> Image:
43
# Add line
44
fig = go.Figure()
45
46
x, y = [],[]
47
statistics = strategy.get('statistics')
48
if statistics:
49
for point in statistics.get('sparkline',[]):
50
x.append(point['time'])
51
y.append(point['value'])
52
53
fig.add_trace(go.Scatter(x=x, y=y, line=dict(color='#0096FF', width=3)))
54
55
fig.update_layout(
56
autosize=False,
57
width=width,
58
height=height,
59
paper_bgcolor='rgba(0,0,0,0)',
60
plot_bgcolor='rgba(0,0,0,0)',
61
margin=dict(l=0, r=0, t=0, b=0),
62
xaxis=dict(visible=False),
63
yaxis=dict(visible=False)
64
)
65
66
fig_bytes = fig.to_image(format="png")
67
return Image.open(BytesIO(fig_bytes))
68
69
def __create_landscape(template, strategy, profile) -> Image:
70
# Create a copy of the template to add elements
71
copy = template.copy()
72
73
# Add profile picture
74
copy.paste(profile, (681, 120),mask=profile)
75
76
# Add texts
77
I1 = ImageDraw.Draw(copy)
78
name = strategy.get('name')
79
author_name = strategy.get('authorName')
80
category = strategy.get('category')
81
if not category: category = ''
82
category = category.upper()
83
84
width = name_font.getlength(name)
85
if width < 530:
86
I1.text((105,120), name, font=name_font, fill='#313131')
87
else:
88
parts = name.split(' ')
89
for i in reversed(range(len(parts))):
90
if name_font.getlength(' '.join(parts[:i])) <= 530:
91
break
92
I1.text((105,120), ' '.join(parts[:i]), font=name_font, fill='#313131')
93
I1.text((105,155), ' '.join(parts[i:]), font=name_font, fill='#313131')
94
95
width = author_font.getlength(author_name)
96
if width < 250:
97
I1.text((775,157), author_name, font=author_font, fill='#313131')
98
else:
99
parts = author_name.split(' ')
100
for i in reversed(range(len(parts))):
101
if author_font.getlength(' '.join(parts[:i])) <= 250:
102
break
103
if i == 0:
104
while author_font.getlength(author_name) > 250:
105
author_name = author_name[:-1]
106
I1.text((775,157), f'{author_name}...', font=author_font, fill='#313131')
107
else:
108
I1.text((775,157), ' '.join(parts[:i]), font=author_font, fill='#313131')
109
I1.text((775,182), ' '.join(parts[i:]), font=author_font, fill='#313131')
110
111
I1.text((105,200), category, font=category_font,
112
fill=colors_by_category.get(category, '#313131'))
113
114
fig = __get_equity_curve(strategy)
115
copy.paste(fig, (0, 210),mask=fig)
116
return copy
117
118
def __create_square(template, strategy, profile) -> Image:
119
# Create a copy of the template to add elements
120
copy = template.copy()
121
122
# Add texts
123
I1 = ImageDraw.Draw(copy)
124
name = strategy.get('name')
125
author_name = strategy.get('authorName')
126
category = strategy.get('category')
127
if not category: category = ''
128
category = category.upper()
129
130
width = name_font.getlength(name)
131
if width < 650:
132
I1.text((42,160), name, font=name_font, fill='#313131')
133
else:
134
parts = name.split(' ')
135
for i in reversed(range(len(parts))):
136
if name_font.getlength(' '.join(parts[:i])) <= 650:
137
break
138
I1.text((42,160), ' '.join(parts[:i]), font=name_font, fill='#313131')
139
I1.text((42,195), ' '.join(parts[i:]), font=name_font, fill='#313131')
140
141
I1.text((42,240), category, font=category_font,
142
fill=colors_by_category.get(category, '#313131'))
143
144
# Add profile picture
145
copy.paste(profile, (42, 325),mask=profile)
146
147
I1.text((137, 360), author_name, font=author_font, fill='#313131')
148
149
fig = __get_equity_curve(strategy, 860, 285)
150
copy.paste(fig, (0, 440),mask=fig)
151
return copy
152
153
colors_by_category = {
154
'CRYPTO': '#C39E78',
155
'EQUITIES': '#BF7C7C',
156
'US EQUITIES': '#9A74BF',
157
'ETF': '#B44444',
158
'FOREX': '#5FB26F',
159
'FUTURES': '#5D6586'
160
}
161
162
if __name__ == '__main__':
163
164
os.chdir(os.path.dirname(os.path.abspath(__file__)))
165
destination_folder = Path('thumbnails')
166
destination_folder.mkdir(parents=True, exist_ok=True)
167
168
template_landscape = Image.open('template_landscape.png')
169
template_square = Image.open('template_square.png')
170
name_font = ImageFont.FreeTypeFont('Inter font/static/Inter-SemiBold.ttf', 35)
171
category_font = ImageFont.FreeTypeFont('Inter font/static/Inter-Regular.ttf', 20)
172
author_font = ImageFont.FreeTypeFont('Inter font/static/Inter-Regular.ttf', 24)
173
default_photo = Image.open('default_photo.png').resize((80,80))
174
profile_errors = set()
175
content = __get_json_content('https://www.quantconnect.com/api/v2/sharing/strategies/list/')
176
strategies = content.get('strategies', [])
177
178
for strategy in strategies:
179
id = strategy.get('projectId')
180
if not id:
181
print(f'No project Id for {strategy}')
182
continue
183
184
profile_picture = __get_profile(strategy['authorProfile'])
185
if not profile_picture:
186
profile_picture = default_photo
187
profile_errors.add(strategy.get("authorName"))
188
189
landscape = __create_landscape(template_landscape, strategy, profile_picture)
190
landscape.save(f"thumbnails/{id}.png")
191
192
square = __create_square(template_square, strategy, profile_picture)
193
square.save(f"thumbnails/{id}_square.png")
194
195
if profile_errors:
196
print("Cannot download profile images of: " + ','.join(profile_errors))
197