Skip to content
Projects
Groups
Snippets
Help
Loading...
Help
Submit feedback
Sign in
Toggle navigation
A
alpha-mind
Project
Project
Details
Activity
Releases
Cycle Analytics
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Charts
Issues
0
Issues
0
List
Board
Labels
Milestones
Merge Requests
0
Merge Requests
0
CI / CD
CI / CD
Pipelines
Jobs
Schedules
Charts
Wiki
Wiki
Snippets
Snippets
Members
Members
Collapse sidebar
Close sidebar
Activity
Graph
Charts
Create a new issue
Jobs
Commits
Issue Boards
Open sidebar
Dr.李
alpha-mind
Commits
ed3131b1
Unverified
Commit
ed3131b1
authored
Jul 19, 2018
by
iLampard
Committed by
GitHub
Jul 19, 2018
Browse files
Options
Browse Files
Download
Plain Diff
Merge pull request #1 from alpha-miner/master
merge update
parents
d52e93db
e84b1fbf
Changes
14
Expand all
Hide whitespace changes
Inline
Side-by-side
Showing
14 changed files
with
223 additions
and
279 deletions
+223
-279
api.py
alphamind/api.py
+2
-0
optimizers.pyx
alphamind/cython/optimizers.pyx
+19
-15
__init__.py
alphamind/model/__init__.py
+3
-0
linearmodel.py
alphamind/model/linearmodel.py
+5
-41
loader.py
alphamind/model/loader.py
+3
-0
modelbase.py
alphamind/model/modelbase.py
+39
-0
svm.py
alphamind/model/svm.py
+19
-0
treemodel.py
alphamind/model/treemodel.py
+6
-82
pfopt
alphamind/pfopt
+1
-1
meanvariancebuilder.py
alphamind/portfolio/meanvariancebuilder.py
+0
-1
strategy.py
alphamind/strategy/strategy.py
+122
-134
test_optimizers.py
alphamind/tests/cython/test_optimizers.py
+0
-4
test_meanvariancebuild.py
alphamind/tests/portfolio/test_meanvariancebuild.py
+3
-0
xgboost
xgboost
+1
-1
No files found.
alphamind/api.py
View file @
ed3131b1
...
...
@@ -43,6 +43,7 @@ from alphamind.model import RandomForestClassifier
from
alphamind.model
import
XGBRegressor
from
alphamind.model
import
XGBClassifier
from
alphamind.model
import
XGBTrainer
from
alphamind.model
import
NvSVRModel
from
alphamind.model
import
load_model
from
alphamind.model.data_preparing
import
fetch_data_package
from
alphamind.model.data_preparing
import
fetch_train_phase
...
...
@@ -104,6 +105,7 @@ __all__ = [
'XGBRegressor'
,
'XGBClassifier'
,
'XGBTrainer'
,
'NvSVRModel'
,
'load_model'
,
'NaiveExecutor'
,
'ThresholdExecutor'
,
...
...
alphamind/cython/optimizers.pyx
View file @
ed3131b1
...
...
@@ -72,11 +72,11 @@ cdef extern from "tvoptimizer.hpp" namespace "pfopt":
double*,
double*,
double,
double,
int,
double*,
double*,
double*) except +
double*,
string) except +
vector[double] xValue()
double feval()
int status()
...
...
@@ -96,11 +96,11 @@ cdef class CVOptimizer:
cnp.ndarray[double, ndim=2] cons_matrix=None,
double[:] clbound=None,
double[:] cubound=None,
double target_low=0.0,
double target_high=1.0,
double target_vol=1.0,
cnp.ndarray[double, ndim=2] factor_cov_matrix=None,
cnp.ndarray[double, ndim=2] factor_loading_matrix=None,
double[:] idsync_risk=None):
double[:] idsync_risk=None,
str linear_solver="ma27"):
self.n = lbound.shape[0]
self.m = 0
...
...
@@ -123,12 +123,12 @@ cdef class CVOptimizer:
&cons[0],
&clbound[0],
&cubound[0],
target_low,
target_high,
target_vol,
self.f,
&factor_cov[0] if factor_cov is not None else NULL,
&factor_loading[0] if factor_loading is not None else NULL,
&idsync_risk[0] if idsync_risk is not None else NULL)
&idsync_risk[0] if idsync_risk is not None else NULL,
bytes(linear_solver, encoding='utf8'))
else:
self.cobj = new TVOptimizer(self.n,
&expected_return[0],
...
...
@@ -139,12 +139,12 @@ cdef class CVOptimizer:
NULL,
NULL,
NULL,
target_low,
target_high,
target_vol,
self.f,
&factor_cov[0] if factor_cov is not None else NULL,
&factor_loading[0] if factor_loading is not None else NULL,
&idsync_risk[0] if idsync_risk is not None else NULL)
&idsync_risk[0] if idsync_risk is not None else NULL,
bytes(linear_solver, encoding='utf8'))
def __dealloc__(self):
del self.cobj
...
...
@@ -174,7 +174,8 @@ cdef extern from "mvoptimizer.hpp" namespace "pfopt":
int,
double*,
double*,
double*) except +
double*,
string) except +
vector[double] xValue()
double feval()
int status()
...
...
@@ -213,7 +214,8 @@ cdef class QPOptimizer:
double risk_aversion=1.0,
cnp.ndarray[double, ndim=2] factor_cov_matrix=None,
cnp.ndarray[double, ndim=2] factor_loading_matrix=None,
double[:] idsync_risk=None):
double[:] idsync_risk=None,
str linear_solver='ma27'):
self.n = lbound.shape[0]
self.m = 0
...
...
@@ -243,7 +245,8 @@ cdef class QPOptimizer:
self.f,
&factor_cov[0] if factor_cov is not None else NULL,
&factor_loading[0] if factor_loading is not None else NULL,
&idsync_risk[0] if idsync_risk is not None else NULL)
&idsync_risk[0] if idsync_risk is not None else NULL,
bytes(linear_solver, encoding='utf8'))
else:
self.cobj = new MVOptimizer(self.n,
&expected_return[0],
...
...
@@ -258,7 +261,8 @@ cdef class QPOptimizer:
self.f,
&factor_cov[0] if factor_cov is not None else NULL,
&factor_loading[0] if factor_loading is not None else NULL,
&idsync_risk[0] if idsync_risk is not None else NULL)
&idsync_risk[0] if idsync_risk is not None else NULL,
bytes(linear_solver, encoding='utf8'))
def __dealloc__(self):
del self.cobj
...
...
alphamind/model/__init__.py
View file @
ed3131b1
...
...
@@ -16,6 +16,8 @@ from alphamind.model.treemodel import XGBRegressor
from
alphamind.model.treemodel
import
XGBClassifier
from
alphamind.model.treemodel
import
XGBTrainer
from
alphamind.model.svm
import
NvSVRModel
from
alphamind.model.loader
import
load_model
...
...
@@ -28,4 +30,5 @@ __all__ = ['LinearRegression',
'XGBRegressor'
,
'XGBClassifier'
,
'XGBTrainer'
,
'NvSVRModel'
,
'load_model'
]
\ No newline at end of file
alphamind/model/linearmodel.py
View file @
ed3131b1
...
...
@@ -6,14 +6,11 @@ Created on 2017-5-10
"""
import
numpy
as
np
from
distutils.version
import
LooseVersion
from
sklearn
import
__version__
as
sklearn_version
from
sklearn.linear_model
import
LinearRegression
as
LinearRegressionImpl
from
sklearn.linear_model
import
Lasso
from
sklearn.linear_model
import
LogisticRegression
as
LogisticRegressionImpl
from
PyFin.api
import
pyFinAssert
from
alphamind.model.modelbase
import
ModelBase
from
alphamind.utilities
import
alpha_logger
from
alphamind.model.modelbase
import
create_model_base
class
ConstLinearModelImpl
(
object
):
...
...
@@ -35,7 +32,7 @@ class ConstLinearModelImpl(object):
return
1.
-
sse
/
ssto
class
ConstLinearModel
(
ModelBase
):
class
ConstLinearModel
(
create_model_base
()
):
def
__init__
(
self
,
features
=
None
,
...
...
@@ -63,7 +60,7 @@ class ConstLinearModel(ModelBase):
return
self
.
impl
.
weights
.
tolist
()
class
LinearRegression
(
ModelBase
):
class
LinearRegression
(
create_model_base
(
'sklearn'
)
):
def
__init__
(
self
,
features
=
None
,
fit_intercept
:
bool
=
False
,
fit_target
=
None
,
**
kwargs
):
super
()
.
__init__
(
features
=
features
,
fit_target
=
fit_target
)
...
...
@@ -71,26 +68,15 @@ class LinearRegression(ModelBase):
def
save
(
self
)
->
dict
:
model_desc
=
super
()
.
save
()
model_desc
[
'sklearn_version'
]
=
sklearn_version
model_desc
[
'weight'
]
=
self
.
impl
.
coef_
.
tolist
()
return
model_desc
@
classmethod
def
load
(
cls
,
model_desc
:
dict
):
obj_layout
=
super
()
.
load
(
model_desc
)
if
LooseVersion
(
sklearn_version
)
<
LooseVersion
(
model_desc
[
'sklearn_version'
]):
alpha_logger
.
warning
(
'Current sklearn version {0} is lower than the model version {1}. '
'Loaded model may work incorrectly.'
.
format
(
sklearn_version
,
model_desc
[
'sklearn_version'
]))
return
obj_layout
@
property
def
weights
(
self
):
return
self
.
impl
.
coef_
.
tolist
()
class
LassoRegression
(
ModelBase
):
class
LassoRegression
(
create_model_base
(
'sklearn'
)
):
def
__init__
(
self
,
alpha
=
0.01
,
features
=
None
,
fit_intercept
:
bool
=
False
,
fit_target
=
None
,
**
kwargs
):
super
()
.
__init__
(
features
=
features
,
fit_target
=
fit_target
)
...
...
@@ -98,26 +84,15 @@ class LassoRegression(ModelBase):
def
save
(
self
)
->
dict
:
model_desc
=
super
()
.
save
()
model_desc
[
'sklearn_version'
]
=
sklearn_version
model_desc
[
'weight'
]
=
self
.
impl
.
coef_
.
tolist
()
return
model_desc
@
classmethod
def
load
(
cls
,
model_desc
:
dict
):
obj_layout
=
super
()
.
load
(
model_desc
)
if
LooseVersion
(
sklearn_version
)
<
LooseVersion
(
model_desc
[
'sklearn_version'
]):
alpha_logger
.
warning
(
'Current sklearn version {0} is lower than the model version {1}. '
'Loaded model may work incorrectly.'
.
format
(
sklearn_version
,
model_desc
[
'sklearn_version'
]))
return
obj_layout
@
property
def
weights
(
self
):
return
self
.
impl
.
coef_
.
tolist
()
class
LogisticRegression
(
ModelBase
):
class
LogisticRegression
(
create_model_base
(
'sklearn'
)
):
def
__init__
(
self
,
features
=
None
,
fit_intercept
:
bool
=
False
,
fit_target
=
None
,
**
kwargs
):
super
()
.
__init__
(
features
=
features
,
fit_target
=
fit_target
)
...
...
@@ -125,20 +100,9 @@ class LogisticRegression(ModelBase):
def
save
(
self
)
->
dict
:
model_desc
=
super
()
.
save
()
model_desc
[
'sklearn_version'
]
=
sklearn_version
model_desc
[
'weight'
]
=
self
.
impl
.
coef_
.
tolist
()
return
model_desc
@
classmethod
def
load
(
cls
,
model_desc
:
dict
):
obj_layout
=
super
()
.
load
(
model_desc
)
if
LooseVersion
(
sklearn_version
)
<
LooseVersion
(
model_desc
[
'sklearn_version'
]):
alpha_logger
.
warning
(
'Current sklearn version {0} is lower than the model version {1}. '
'Loaded model may work incorrectly.'
.
format
(
sklearn_version
,
model_desc
[
'sklearn_version'
]))
return
obj_layout
@
property
def
weights
(
self
):
return
self
.
impl
.
coef_
.
tolist
()
...
...
alphamind/model/loader.py
View file @
ed3131b1
...
...
@@ -15,6 +15,7 @@ from alphamind.model.treemodel import RandomForestClassifier
from
alphamind.model.treemodel
import
XGBRegressor
from
alphamind.model.treemodel
import
XGBClassifier
from
alphamind.model.treemodel
import
XGBTrainer
from
alphamind.model.svm
import
NvSVRModel
def
load_model
(
model_desc
:
dict
)
->
ModelBase
:
...
...
@@ -40,5 +41,7 @@ def load_model(model_desc: dict) -> ModelBase:
return
XGBClassifier
.
load
(
model_desc
)
elif
'XGBTrainer'
in
model_name_parts
:
return
XGBTrainer
.
load
(
model_desc
)
elif
'NvSVR'
in
model_name_parts
:
return
NvSVRModel
.
load
(
model_desc
)
else
:
raise
ValueError
(
'{0} is not currently supported in model loader.'
.
format
(
model_name
))
alphamind/model/modelbase.py
View file @
ed3131b1
...
...
@@ -6,10 +6,13 @@ Created on 2017-9-4
"""
import
abc
from
distutils.version
import
LooseVersion
import
arrow
import
numpy
as
np
import
pandas
as
pd
from
simpleutils.miscellaneous
import
list_eq
from
sklearn
import
__version__
as
sklearn_version
from
xgboost
import
__version__
as
xgbboot_version
from
alphamind.utilities
import
alpha_logger
from
alphamind.utilities
import
encode
from
alphamind.utilities
import
decode
...
...
@@ -84,3 +87,39 @@ class ModelBase(metaclass=abc.ABCMeta):
obj_layout
.
fit_target
=
None
return
obj_layout
def
create_model_base
(
party_name
=
None
):
if
not
party_name
:
return
ModelBase
else
:
class
ExternalLibBase
(
ModelBase
):
_lib_name
=
party_name
def
save
(
self
)
->
dict
:
model_desc
=
super
()
.
save
()
if
self
.
_lib_name
==
'sklearn'
:
model_desc
[
self
.
_lib_name
+
"_version"
]
=
sklearn_version
elif
self
.
_lib_name
==
'xgboost'
:
model_desc
[
self
.
_lib_name
+
"_version"
]
=
xgbboot_version
else
:
raise
ValueError
(
"3rd party lib name ({0}) is not recognized"
.
format
(
self
.
_lib_name
))
return
model_desc
@
classmethod
def
load
(
cls
,
model_desc
:
dict
):
obj_layout
=
super
()
.
load
(
model_desc
)
if
cls
.
_lib_name
==
'sklearn'
:
current_version
=
sklearn_version
elif
cls
.
_lib_name
==
'xgboost'
:
current_version
=
xgbboot_version
else
:
raise
ValueError
(
"3rd party lib name ({0}) is not recognized"
.
format
(
cls
.
_lib_name
))
if
LooseVersion
(
current_version
)
<
LooseVersion
(
model_desc
[
cls
.
_lib_name
+
"_version"
]):
alpha_logger
.
warning
(
'Current {2} version {0} is lower than the model version {1}. '
'Loaded model may work incorrectly.'
.
format
(
sklearn_version
,
model_desc
[
cls
.
_lib_name
],
cls
.
_lib_name
))
return
obj_layout
return
ExternalLibBase
alphamind/model/svm.py
0 → 100644
View file @
ed3131b1
# -*- coding: utf-8 -*-
"""
Created on 2018-7-9
@author: cheng.li
"""
from
sklearn.svm
import
NuSVR
from
alphamind.model.modelbase
import
create_model_base
class
NvSVRModel
(
create_model_base
(
'sklearn'
)):
def
__init__
(
self
,
features
=
None
,
fit_target
=
None
,
**
kwargs
):
super
()
.
__init__
(
features
=
features
,
fit_target
=
fit_target
)
self
.
impl
=
NuSVR
(
**
kwargs
)
alphamind/model/treemodel.py
View file @
ed3131b1
...
...
@@ -9,7 +9,6 @@ from distutils.version import LooseVersion
import
arrow
import
numpy
as
np
import
pandas
as
pd
from
sklearn
import
__version__
as
sklearn_version
from
sklearn.ensemble
import
RandomForestRegressor
as
RandomForestRegressorImpl
from
sklearn.ensemble
import
RandomForestClassifier
as
RandomForestClassifierImpl
from
sklearn.model_selection
import
train_test_split
...
...
@@ -17,11 +16,11 @@ import xgboost as xgb
from
xgboost
import
__version__
as
xgbboot_version
from
xgboost
import
XGBRegressor
as
XGBRegressorImpl
from
xgboost
import
XGBClassifier
as
XGBClassifierImpl
from
alphamind.model.modelbase
import
ModelB
ase
from
alphamind.model.modelbase
import
create_model_b
ase
from
alphamind.utilities
import
alpha_logger
class
RandomForestRegressor
(
ModelBase
):
class
RandomForestRegressor
(
create_model_base
(
'sklearn'
)
):
def
__init__
(
self
,
n_estimators
:
int
=
100
,
...
...
@@ -34,27 +33,12 @@ class RandomForestRegressor(ModelBase):
max_features
=
max_features
,
**
kwargs
)
def
save
(
self
)
->
dict
:
model_desc
=
super
()
.
save
()
model_desc
[
'sklearn_version'
]
=
sklearn_version
return
model_desc
@
classmethod
def
load
(
cls
,
model_desc
:
dict
):
obj_layout
=
super
()
.
load
(
model_desc
)
if
LooseVersion
(
sklearn_version
)
<
LooseVersion
(
model_desc
[
'sklearn_version'
]):
alpha_logger
.
warning
(
'Current sklearn version {0} is lower than the model version {1}. '
'Loaded model may work incorrectly.'
.
format
(
sklearn_version
,
model_desc
[
'sklearn_version'
]))
return
obj_layout
@
property
def
importances
(
self
):
return
self
.
impl
.
feature_importances_
.
tolist
()
class
RandomForestClassifier
(
ModelBase
):
class
RandomForestClassifier
(
create_model_base
(
'sklearn'
)
):
def
__init__
(
self
,
n_estimators
:
int
=
100
,
...
...
@@ -67,27 +51,12 @@ class RandomForestClassifier(ModelBase):
max_features
=
max_features
,
**
kwargs
)
def
save
(
self
)
->
dict
:
model_desc
=
super
()
.
save
()
model_desc
[
'sklearn_version'
]
=
sklearn_version
return
model_desc
@
classmethod
def
load
(
cls
,
model_desc
:
dict
):
obj_layout
=
super
()
.
load
(
model_desc
)
if
LooseVersion
(
sklearn_version
)
<
LooseVersion
(
model_desc
[
'sklearn_version'
]):
alpha_logger
.
warning
(
'Current sklearn version {0} is lower than the model version {1}. '
'Loaded model may work incorrectly.'
.
format
(
sklearn_version
,
model_desc
[
'sklearn_version'
]))
return
obj_layout
@
property
def
importances
(
self
):
return
self
.
impl
.
feature_importances_
.
tolist
()
class
XGBRegressor
(
ModelBase
):
class
XGBRegressor
(
create_model_base
(
'xgboost'
)
):
def
__init__
(
self
,
n_estimators
:
int
=
100
,
...
...
@@ -104,27 +73,12 @@ class XGBRegressor(ModelBase):
n_jobs
=
n_jobs
,
**
kwargs
)
def
save
(
self
)
->
dict
:
model_desc
=
super
()
.
save
()
model_desc
[
'xgbboot_version'
]
=
xgbboot_version
return
model_desc
@
classmethod
def
load
(
cls
,
model_desc
:
dict
):
obj_layout
=
super
()
.
load
(
model_desc
)
if
LooseVersion
(
xgbboot_version
)
<
LooseVersion
(
model_desc
[
'xgbboot_version'
]):
alpha_logger
.
warning
(
'Current xgboost version {0} is lower than the model version {1}. '
'Loaded model may work incorrectly.'
.
format
(
xgbboot_version
,
model_desc
[
'xgbboot_version'
]))
return
obj_layout
@
property
def
importances
(
self
):
return
self
.
impl
.
feature_importances_
.
tolist
()
class
XGBClassifier
(
ModelBase
):
class
XGBClassifier
(
create_model_base
(
'xgboost'
)
):
def
__init__
(
self
,
n_estimators
:
int
=
100
,
...
...
@@ -141,27 +95,12 @@ class XGBClassifier(ModelBase):
n_jobs
=
n_jobs
,
**
kwargs
)
def
save
(
self
)
->
dict
:
model_desc
=
super
()
.
save
()
model_desc
[
'xgbboot_version'
]
=
xgbboot_version
return
model_desc
@
classmethod
def
load
(
cls
,
model_desc
:
dict
):
obj_layout
=
super
()
.
load
(
model_desc
)
if
LooseVersion
(
xgbboot_version
)
<
LooseVersion
(
model_desc
[
'xgbboot_version'
]):
alpha_logger
.
warning
(
'Current xgboost version {0} is lower than the model version {1}. '
'Loaded model may work incorrectly.'
.
format
(
xgbboot_version
,
model_desc
[
'xgbboot_version'
]))
return
obj_layout
@
property
def
importances
(
self
):
return
self
.
impl
.
feature_importances_
.
tolist
()
class
XGBTrainer
(
ModelBase
):
class
XGBTrainer
(
create_model_base
(
'xgboost'
)
):
def
__init__
(
self
,
objective
=
'binary:logistic'
,
...
...
@@ -226,21 +165,6 @@ class XGBTrainer(ModelBase):
d_predict
=
xgb
.
DMatrix
(
x
[
self
.
features
]
.
values
)
return
self
.
impl
.
predict
(
d_predict
)
def
save
(
self
)
->
dict
:
model_desc
=
super
()
.
save
()
model_desc
[
'xgbboot_version'
]
=
xgbboot_version
return
model_desc
@
classmethod
def
load
(
cls
,
model_desc
:
dict
):
obj_layout
=
super
()
.
load
(
model_desc
)
if
LooseVersion
(
xgbboot_version
)
<
LooseVersion
(
model_desc
[
'xgbboot_version'
]):
alpha_logger
.
warning
(
'Current xgboost version {0} is lower than the model version {1}. '
'Loaded model may work incorrectly.'
.
format
(
xgbboot_version
,
model_desc
[
'xgbboot_version'
]))
return
obj_layout
@
property
def
importances
(
self
):
imps
=
self
.
impl
.
get_fscore
()
.
items
()
...
...
pfopt
@
148fd8ee
Subproject commit
ffaf2153dfdce380c3d8aa1a69b328ab77665ad3
Subproject commit
148fd8eedca5aeff85d90e6374658d96316e7f66
alphamind/portfolio/meanvariancebuilder.py
View file @
ed3131b1
...
...
@@ -109,7 +109,6 @@ def target_vol_builder(er: np.ndarray,
cons_mat
,
clbound
,
cubound
,
0.
,
vol_target
,
risk_model
[
'factor_cov'
],
risk_model
[
'factor_loading'
],
...
...
alphamind/strategy/strategy.py
View file @
ed3131b1
This diff is collapsed.
Click to expand it.
alphamind/tests/cython/test_optimizers.py
View file @
ed3131b1
...
...
@@ -116,7 +116,6 @@ class TestOptimizers(unittest.TestCase):
None
,
None
,
None
,
target_vol
,
target_vol
)
# check against known good result
...
...
@@ -144,7 +143,6 @@ class TestOptimizers(unittest.TestCase):
cons
,
clbound
,
cubound
,
target_vol
,
target_vol
)
# check against known good result
...
...
@@ -173,7 +171,6 @@ class TestOptimizers(unittest.TestCase):
cons
,
clbound
,
cubound
,
0.
,
target_vol
,
factor_var
,
factor_load
,
...
...
@@ -204,7 +201,6 @@ class TestOptimizers(unittest.TestCase):
cons
,
clbound
,
cubound
,
0.
,
target_vol
)
# check against known good result
...
...
alphamind/tests/portfolio/test_meanvariancebuild.py
View file @
ed3131b1
...
...
@@ -57,6 +57,9 @@ class TestMeanVarianceBuild(unittest.TestCase):
status
,
_
,
x
=
mean_variance_builder
(
er
,
model
,
bm
,
lbound
,
ubound
,
None
,
None
,
lam
=
1
)
np
.
testing
.
assert_array_almost_equal
(
x
,
np
.
linalg
.
inv
(
cov
)
@
er
)
def
test_mean_variance_builder_without_constraints_with_factor_model
(
self
):
pass
def
test_mean_variance_builder_with_none_unity_lambda
(
self
):
er
=
np
.
array
([
0.01
,
0.02
,
0.03
])
cov
=
np
.
array
([[
0.02
,
0.01
,
0.02
],
...
...
xgboost
@
a187ed6c
Subproject commit
5cd851ccef8cf0a0a71094d0d0e33a9d102f1f55
Subproject commit
a187ed6c8f3aa40b47d5be80667cbbe6a6fd563d
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment